Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "data_file_browser_cld_data_provider.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/files/file.h"
      9 #include "base/files/file_path.h"
     10 #include "base/lazy_instance.h"
     11 #include "base/logging.h"
     12 #include "base/memory/weak_ptr.h"
     13 #include "base/path_service.h"
     14 #include "base/synchronization/lock.h"
     15 #include "base/task_runner.h"
     16 #include "components/translate/content/common/data_file_cld_data_provider_messages.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "content/public/browser/render_process_host.h"
     19 #include "content/public/browser/render_view_host.h"
     20 #include "content/public/browser/web_contents.h"
     21 #include "ipc/ipc_message.h"
     22 #include "ipc/ipc_message_macros.h"
     23 #include "ipc/ipc_platform_file.h"
     24 
     25 namespace {
     26 // The data file,  cached as long as the process stays alive.
     27 // We also track the offset at which the data starts, and its length.
     28 base::FilePath g_cached_filepath;  // guarded by g_file_lock_
     29 base::File* g_cached_file = NULL;  // guarded by g_file_lock_
     30 uint64 g_cached_data_offset = -1;  // guarded by g_file_lock_
     31 uint64 g_cached_data_length = -1;  // guarded by g_file_lock_
     32 
     33 // Guards g_cached_filepath
     34 base::LazyInstance<base::Lock> g_file_lock_;
     35 }  // namespace
     36 
     37 namespace translate {
     38 
     39 // Implementation of the static factory method from BrowserCldDataProvider,
     40 // hooking up this specific implementation for all of Chromium.
     41 BrowserCldDataProvider* CreateBrowserCldDataProviderFor(
     42     content::WebContents* web_contents) {
     43   VLOG(1) << "Creating DataFileBrowserCldDataProvider";
     44   return new DataFileBrowserCldDataProvider(web_contents);
     45 }
     46 
     47 void SetCldDataFilePath(const base::FilePath& path) {
     48   VLOG(1) << "Setting CLD data file path to: " << path.value();
     49   base::AutoLock lock(g_file_lock_.Get());
     50   if (g_cached_filepath == path)
     51     return;  // no change necessary
     52   g_cached_filepath = path;
     53   // For sanity, clean these other values up just in case.
     54   g_cached_file = NULL;
     55   g_cached_data_length = -1;
     56   g_cached_data_offset = -1;
     57 }
     58 
     59 base::FilePath GetCldDataFilePath() {
     60   base::AutoLock lock(g_file_lock_.Get());
     61   return g_cached_filepath;
     62 }
     63 
     64 DataFileBrowserCldDataProvider::DataFileBrowserCldDataProvider(
     65     content::WebContents* web_contents)
     66     : web_contents_(web_contents), weak_pointer_factory_() {
     67 }
     68 
     69 DataFileBrowserCldDataProvider::~DataFileBrowserCldDataProvider() {
     70 }
     71 
     72 bool DataFileBrowserCldDataProvider::OnMessageReceived(
     73     const IPC::Message& message) {
     74   bool handled = true;
     75   IPC_BEGIN_MESSAGE_MAP(DataFileBrowserCldDataProvider, message)
     76   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NeedCldDataFile, OnCldDataRequest)
     77   IPC_MESSAGE_UNHANDLED(handled = false)
     78   IPC_END_MESSAGE_MAP()
     79   return handled;
     80 }
     81 
     82 void DataFileBrowserCldDataProvider::OnCldDataRequest() {
     83   // Quickly try to read g_cached_file. If valid, the file handle is
     84   // cached and can be used immediately. Else, queue the caching task to the
     85   // blocking pool.
     86   VLOG(1) << "Received request for CLD data file.";
     87   base::File* handle = NULL;
     88   uint64 data_offset = 0;
     89   uint64 data_length = 0;
     90   {
     91     base::AutoLock lock(g_file_lock_.Get());
     92     handle = g_cached_file;
     93     data_offset = g_cached_data_offset;
     94     data_length = g_cached_data_length;
     95   }
     96 
     97   if (handle && handle->IsValid()) {
     98     // Cached data available. Respond to the request.
     99     VLOG(1) << "CLD data file is already cached, replying immediately.";
    100     SendCldDataResponseInternal(handle, data_offset, data_length);
    101     return;
    102   }
    103 
    104   if (weak_pointer_factory_.get() == NULL) {
    105     weak_pointer_factory_.reset(
    106         new base::WeakPtrFactory<DataFileBrowserCldDataProvider>(this));
    107     weak_pointer_factory_.get()->GetWeakPtr().get();
    108   }
    109 
    110   // Else, we don't have the data file yet. Queue a caching attempt.
    111   // The caching attempt happens in the blocking pool because it may involve
    112   // arbitrary filesystem access.
    113   // After the caching attempt is made, we call MaybeSendCLDDataAvailable
    114   // to pass the file handle to the renderer. This only results in an IPC
    115   // message if the caching attempt was successful.
    116   VLOG(1) << "CLD data file not yet cached, deferring lookup";
    117   content::BrowserThread::PostBlockingPoolTaskAndReply(
    118       FROM_HERE,
    119       base::Bind(&DataFileBrowserCldDataProvider::OnCldDataRequestInternal),
    120       base::Bind(&DataFileBrowserCldDataProvider::SendCldDataResponse,
    121                  weak_pointer_factory_.get()->GetWeakPtr()));
    122 }
    123 
    124 void DataFileBrowserCldDataProvider::SendCldDataResponse() {
    125   base::File* handle = NULL;
    126   uint64 data_offset = 0;
    127   uint64 data_length = 0;
    128   {
    129     base::AutoLock lock(g_file_lock_.Get());
    130     handle = g_cached_file;
    131     data_offset = g_cached_data_offset;
    132     data_length = g_cached_data_length;
    133   }
    134 
    135   if (handle && handle->IsValid())
    136     SendCldDataResponseInternal(handle, data_offset, data_length);
    137 }
    138 
    139 void DataFileBrowserCldDataProvider::SendCldDataResponseInternal(
    140     const base::File* handle,
    141     const uint64 data_offset,
    142     const uint64 data_length) {
    143   VLOG(1) << "Sending CLD data file response.";
    144 
    145   content::RenderViewHost* render_view_host =
    146       web_contents_->GetRenderViewHost();
    147   if (render_view_host == NULL) {
    148     // Render view destroyed, no need to bother.
    149     VLOG(1) << "Lost render view host, giving up";
    150     return;
    151   }
    152 
    153   content::RenderProcessHost* render_process_host =
    154       render_view_host->GetProcess();
    155   if (render_process_host == NULL) {
    156     // Render process destroyed, render view not yet dead. No need to bother.
    157     VLOG(1) << "Lost render process, giving up";
    158     return;
    159   }
    160 
    161   // Data available, respond to the request.
    162   const int render_process_handle = render_process_host->GetHandle();
    163   IPC::PlatformFileForTransit ipc_platform_file =
    164       IPC::GetFileHandleForProcess(handle->GetPlatformFile(),
    165                                    render_process_handle, false);
    166 
    167   // In general, sending a response from within the code path that is processing
    168   // a request is discouraged because there is potential for deadlock (if the
    169   // methods are sent synchronously) or loops (if the response can trigger a
    170   // new request). Neither of these concerns is relevant in this code, so
    171   // sending the response from within the code path of the request handler is
    172   // safe.
    173   render_view_host->Send(
    174       new ChromeViewMsg_CldDataFileAvailable(render_view_host->GetRoutingID(),
    175                                              ipc_platform_file,
    176                                              data_offset,
    177                                              data_length));
    178 }
    179 
    180 void DataFileBrowserCldDataProvider::OnCldDataRequestInternal() {
    181   // Because this function involves arbitrary file system access, it must run
    182   // on the blocking pool.
    183   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
    184   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    185   VLOG(1) << "CLD data file caching attempt starting.";
    186 
    187   {
    188     base::AutoLock lock(g_file_lock_.Get());
    189     if (g_cached_file) {
    190       VLOG(1) << "CLD data file is already cached, aborting caching attempt";
    191       return;  // Already done, duplicate request
    192     }
    193   }
    194 
    195   const base::FilePath path = GetCldDataFilePath();
    196   if (path.empty()) {
    197     VLOG(1) << "CLD data file does not yet have a known location.";
    198     return;
    199   }
    200 
    201   // If the file exists, we can send an IPC-safe construct back to the
    202   // renderer process immediately; otherwise, nothing to do here.
    203   if (!base::PathExists(path)) {
    204     VLOG(1) << "CLD data file does not exist.";
    205     return;
    206   }
    207 
    208   // Attempt to open the file for reading.
    209   scoped_ptr<base::File> file(
    210       new base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
    211   if (!file->IsValid()) {
    212     LOG(WARNING) << "CLD data file exists but cannot be opened";
    213     return;
    214   }
    215 
    216   base::File::Info file_info;
    217   if (!file->GetInfo(&file_info)) {
    218     LOG(WARNING) << "CLD data file exists but cannot be inspected";
    219     return;
    220   }
    221 
    222   // For now, our offset and length are simply 0 and the length of the file,
    223   // respectively. If we later decide to include the CLD2 data file inside of
    224   // a larger binary context, these params can be twiddled appropriately.
    225   const uint64 data_offset = 0;
    226   const uint64 data_length = file_info.size;
    227 
    228   {
    229     base::AutoLock lock(g_file_lock_.Get());
    230     if (g_cached_file) {
    231       // Idempotence: Racing another request on the blocking pool, abort.
    232       VLOG(1) << "Another thread finished caching first, aborting.";
    233     } else {
    234       // Else, this request has taken care of it all. Cache all info.
    235       VLOG(1) << "Caching CLD data file information.";
    236       g_cached_file = file.release();
    237       g_cached_data_offset = data_offset;
    238       g_cached_data_length = data_length;
    239     }
    240   }
    241 }
    242 
    243 }  // namespace translate
    244