Home | History | Annotate | Download | only in webui
      1 // Copyright (c) 2012 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 "chrome/browser/ui/webui/devtools_ui.h"
      6 
      7 #include <string>
      8 
      9 #include "base/memory/ref_counted_memory.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "chrome/browser/net/chrome_url_request_context.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/common/url_constants.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "content/public/browser/devtools_http_handler.h"
     18 #include "content/public/browser/url_data_source.h"
     19 #include "content/public/browser/web_contents.h"
     20 #include "content/public/browser/web_ui.h"
     21 #include "net/url_request/url_fetcher.h"
     22 #include "net/url_request/url_fetcher_delegate.h"
     23 #include "net/url_request/url_request_context_getter.h"
     24 #include "ui/base/resource/resource_bundle.h"
     25 
     26 using content::BrowserThread;
     27 using content::WebContents;
     28 
     29 namespace {
     30 
     31 std::string PathWithoutParams(const std::string& path) {
     32   return GURL(std::string("chrome-devtools://devtools/") + path)
     33       .path().substr(1);
     34 }
     35 
     36 const char kRemoteFrontendDomain[] = "chrome-devtools-frontend.appspot.com";
     37 const char kRemoteFrontendBase[] =
     38     "https://chrome-devtools-frontend.appspot.com/";
     39 const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n";
     40 
     41 #if defined(DEBUG_DEVTOOLS)
     42 // Local frontend url provided by InspectUI.
     43 const char kLocalFrontendURLPrefix[] = "https://localhost:9222/";
     44 #endif  // defined(DEBUG_DEVTOOLS)
     45 
     46 class FetchRequest : public net::URLFetcherDelegate {
     47  public:
     48   FetchRequest(net::URLRequestContextGetter* request_context,
     49                const GURL& url,
     50                const content::URLDataSource::GotDataCallback& callback)
     51       : callback_(callback) {
     52     if (!url.is_valid()) {
     53       OnURLFetchComplete(NULL);
     54       return;
     55     }
     56 
     57     fetcher_.reset(net::URLFetcher::Create(url, net::URLFetcher::GET, this));
     58     fetcher_->SetRequestContext(request_context);
     59     fetcher_->Start();
     60   }
     61 
     62  private:
     63   virtual ~FetchRequest() {}
     64 
     65   virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
     66     std::string response;
     67     if (source)
     68       source->GetResponseAsString(&response);
     69     else
     70       response = kHttpNotFound;
     71 
     72     callback_.Run(base::RefCountedString::TakeString(&response));
     73     delete this;
     74   }
     75 
     76   scoped_ptr<net::URLFetcher> fetcher_;
     77   content::URLDataSource::GotDataCallback callback_;
     78 };
     79 
     80 std::string GetMimeTypeForPath(const std::string& path) {
     81   std::string filename = PathWithoutParams(path);
     82   if (EndsWith(filename, ".html", false)) {
     83     return "text/html";
     84   } else if (EndsWith(filename, ".css", false)) {
     85     return "text/css";
     86   } else if (EndsWith(filename, ".js", false)) {
     87     return "application/javascript";
     88   } else if (EndsWith(filename, ".png", false)) {
     89     return "image/png";
     90   } else if (EndsWith(filename, ".gif", false)) {
     91     return "image/gif";
     92   } else if (EndsWith(filename, ".manifest", false)) {
     93     return "text/cache-manifest";
     94   }
     95   NOTREACHED();
     96   return "text/plain";
     97 }
     98 
     99 // An URLDataSource implementation that handles chrome-devtools://devtools/
    100 // requests. Three types of requests could be handled based on the URL path:
    101 // 1. /bundled/: bundled DevTools frontend is served.
    102 // 2. /remote/: Remote DevTools frontend is served from App Engine.
    103 // 3. /localhost/: Remote frontend is served from localhost:9222. This is a
    104 // debug only feature hidden beihnd a compile time flag DEBUG_DEVTOOLS.
    105 class DevToolsDataSource : public content::URLDataSource {
    106  public:
    107   explicit DevToolsDataSource(net::URLRequestContextGetter*
    108     request_context) : request_context_(request_context) {
    109   }
    110 
    111   // content::URLDataSource implementation.
    112   virtual std::string GetSource() const OVERRIDE {
    113     return chrome::kChromeUIDevToolsHost;
    114   }
    115 
    116   virtual void StartDataRequest(
    117       const std::string& path,
    118       int render_process_id,
    119       int render_view_id,
    120       const content::URLDataSource::GotDataCallback& callback) OVERRIDE {
    121     std::string bundled_path_prefix(chrome::kChromeUIDevToolsBundledPath);
    122     bundled_path_prefix += "/";
    123     if (StartsWithASCII(path, bundled_path_prefix, false)) {
    124       StartBundledDataRequest(path.substr(bundled_path_prefix.length()),
    125                               render_process_id,
    126                               render_view_id,
    127                               callback);
    128       return;
    129     }
    130     std::string remote_path_prefix(chrome::kChromeUIDevToolsRemotePath);
    131     remote_path_prefix += "/";
    132     if (StartsWithASCII(path, remote_path_prefix, false)) {
    133       StartRemoteDataRequest(path.substr(remote_path_prefix.length()),
    134                               render_process_id,
    135                               render_view_id,
    136                               callback);
    137       return;
    138     }
    139   }
    140 
    141   // Serves bundled DevTools frontend from ResourceBundle.
    142   void StartBundledDataRequest(
    143       const std::string& path,
    144       int render_process_id,
    145       int render_view_id,
    146       const content::URLDataSource::GotDataCallback& callback) {
    147     std::string filename = PathWithoutParams(path);
    148 
    149     int resource_id =
    150         content::DevToolsHttpHandler::GetFrontendResourceId(filename);
    151 
    152     DLOG_IF(WARNING, -1 == resource_id) << "Unable to find dev tool resource: "
    153         << filename << ". If you compiled with debug_devtools=1, try running"
    154         " with --debug-devtools.";
    155     const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    156     scoped_refptr<base::RefCountedStaticMemory> bytes(rb.LoadDataResourceBytes(
    157         resource_id));
    158     callback.Run(bytes.get());
    159   }
    160 
    161   // Serves remote DevTools frontend from hard-coded App Engine domain.
    162   void StartRemoteDataRequest(
    163       const std::string& path,
    164       int render_process_id,
    165       int render_view_id,
    166       const content::URLDataSource::GotDataCallback& callback) {
    167     GURL url = GURL(kRemoteFrontendBase + path);
    168     CHECK_EQ(url.host(), kRemoteFrontendDomain);
    169     new FetchRequest(request_context_.get(), url, callback);
    170   }
    171 
    172   virtual std::string GetMimeType(const std::string& path) const OVERRIDE {
    173     return GetMimeTypeForPath(path);
    174   }
    175 
    176   virtual bool ShouldAddContentSecurityPolicy() const OVERRIDE {
    177     return false;
    178   }
    179 
    180  private:
    181   virtual ~DevToolsDataSource() {}
    182   scoped_refptr<net::URLRequestContextGetter> request_context_;
    183 
    184   DISALLOW_COPY_AND_ASSIGN(DevToolsDataSource);
    185 };
    186 
    187 }  // namespace
    188 
    189 // static
    190 GURL DevToolsUI::GetProxyURL(const std::string& frontend_url) {
    191   GURL url(frontend_url);
    192 #if defined(DEBUG_DEVTOOLS)
    193   if (frontend_url.find(kLocalFrontendURLPrefix) == 0) {
    194     std::string path = url.path();
    195     std::string local_path_prefix = "/";
    196     local_path_prefix += chrome::kChromeUIDevToolsHost;
    197     local_path_prefix += "/";
    198     if (StartsWithASCII(path, local_path_prefix, false)) {
    199       std::string local_path = path.substr(local_path_prefix.length());
    200       return GURL(base::StringPrintf("%s://%s/%s/%s",
    201                                      chrome::kChromeDevToolsScheme,
    202                                      chrome::kChromeUIDevToolsHost,
    203                                      chrome::kChromeUIDevToolsBundledPath,
    204                                      local_path.c_str()));
    205     }
    206   }
    207 #endif  // defined(DEBUG_DEVTOOLS)
    208   CHECK(url.is_valid());
    209   CHECK_EQ(url.host(), kRemoteFrontendDomain);
    210   return GURL(base::StringPrintf("%s://%s/%s/%s",
    211               chrome::kChromeDevToolsScheme,
    212               chrome::kChromeUIDevToolsHost,
    213               chrome::kChromeUIDevToolsRemotePath,
    214               url.path().substr(1).c_str()));
    215 }
    216 
    217 DevToolsUI::DevToolsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
    218   web_ui->SetBindings(0);
    219   Profile* profile = Profile::FromWebUI(web_ui);
    220   content::URLDataSource::Add(
    221       profile,
    222       new DevToolsDataSource(profile->GetRequestContext()));
    223 }
    224