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/android/dev_tools_server.h" 6 7#include <pwd.h> 8#include <cstring> 9 10#include "base/android/jni_string.h" 11#include "base/basictypes.h" 12#include "base/bind.h" 13#include "base/callback.h" 14#include "base/command_line.h" 15#include "base/compiler_specific.h" 16#include "base/logging.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/stringprintf.h" 19#include "base/strings/utf_string_conversions.h" 20#include "chrome/browser/android/tab_android.h" 21#include "chrome/browser/browser_process.h" 22#include "chrome/browser/devtools/devtools_adb_bridge.h" 23#include "chrome/browser/history/top_sites.h" 24#include "chrome/browser/profiles/profile_manager.h" 25#include "chrome/browser/ui/android/tab_model/tab_model.h" 26#include "chrome/browser/ui/android/tab_model/tab_model_list.h" 27#include "content/public/browser/android/devtools_auth.h" 28#include "content/public/browser/browser_thread.h" 29#include "content/public/browser/devtools_agent_host.h" 30#include "content/public/browser/devtools_http_handler.h" 31#include "content/public/browser/devtools_http_handler_delegate.h" 32#include "content/public/browser/devtools_target.h" 33#include "content/public/browser/favicon_status.h" 34#include "content/public/browser/navigation_entry.h" 35#include "content/public/browser/render_view_host.h" 36#include "content/public/browser/web_contents.h" 37#include "content/public/browser/web_contents_delegate.h" 38#include "content/public/common/content_switches.h" 39#include "content/public/common/url_constants.h" 40#include "grit/devtools_discovery_page_resources.h" 41#include "jni/DevToolsServer_jni.h" 42#include "net/socket/unix_domain_socket_posix.h" 43#include "net/url_request/url_request_context_getter.h" 44#include "ui/base/resource/resource_bundle.h" 45#include "webkit/common/user_agent/user_agent_util.h" 46 47using content::DevToolsAgentHost; 48using content::RenderViewHost; 49using content::WebContents; 50 51namespace { 52 53const char kFrontEndURL[] = 54 "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html"; 55const char kDefaultSocketNamePrefix[] = "chrome"; 56const char kTetheringSocketName[] = "chrome_devtools_tethering_%d_%d"; 57 58const char kTargetTypePage[] = "page"; 59const char kTargetTypeOther[] = "other"; 60 61static GURL GetFaviconURL(WebContents* web_contents) { 62 content::NavigationController& controller = web_contents->GetController(); 63 content::NavigationEntry* entry = controller.GetActiveEntry(); 64 if (entry != NULL && entry->GetURL().is_valid()) 65 return entry->GetFavicon().url; 66 return GURL(); 67} 68 69class TargetBase : public content::DevToolsTarget { 70 public: 71 // content::DevToolsTarget implementation: 72 virtual std::string GetTitle() const OVERRIDE { return title_; } 73 74 virtual std::string GetDescription() const OVERRIDE { return std::string(); } 75 76 virtual GURL GetUrl() const OVERRIDE { return url_; } 77 78 virtual GURL GetFaviconUrl() const OVERRIDE { return favicon_url_; } 79 80 virtual base::TimeTicks GetLastActivityTime() const OVERRIDE { 81 return last_activity_time_; 82 } 83 84 protected: 85 explicit TargetBase(WebContents* web_contents) 86 : title_(UTF16ToUTF8(web_contents->GetTitle())), 87 url_(web_contents->GetURL()), 88 favicon_url_(GetFaviconURL(web_contents)), 89 last_activity_time_(web_contents->GetLastSelectedTime()) { 90 } 91 92 TargetBase(const base::string16& title, const GURL& url) 93 : title_(UTF16ToUTF8(title)), 94 url_(url) 95 {} 96 97 private: 98 const std::string title_; 99 const GURL url_; 100 const GURL favicon_url_; 101 const base::TimeTicks last_activity_time_; 102}; 103 104class TabTarget : public TargetBase { 105 public: 106 static TabTarget* CreateForWebContents(int tab_id, 107 WebContents* web_contents) { 108 return new TabTarget(tab_id, web_contents); 109 } 110 111 static TabTarget* CreateForUnloadedTab(int tab_id, 112 const base::string16& title, 113 const GURL& url) { 114 return new TabTarget(tab_id, title, url); 115 } 116 117 // content::DevToolsTarget implementation: 118 virtual std::string GetId() const OVERRIDE { 119 return base::IntToString(tab_id_); 120 } 121 122 virtual std::string GetType() const OVERRIDE { return kTargetTypePage; } 123 124 virtual bool IsAttached() const OVERRIDE { 125 TabModel* model; 126 int index; 127 if (!FindTab(&model, &index)) 128 return false; 129 WebContents* web_contents = model->GetWebContentsAt(index); 130 if (!web_contents) 131 return false; 132 return DevToolsAgentHost::IsDebuggerAttached(web_contents); 133 } 134 135 virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE { 136 TabModel* model; 137 int index; 138 if (!FindTab(&model, &index)) 139 return NULL; 140 WebContents* web_contents = model->GetWebContentsAt(index); 141 if (!web_contents) { 142 // The tab has been pushed out of memory, pull it back. 143 TabAndroid* tab = model->GetTabAt(index); 144 tab->RestoreIfNeeded(); 145 web_contents = model->GetWebContentsAt(index); 146 if (!web_contents) 147 return NULL; 148 } 149 RenderViewHost* rvh = web_contents->GetRenderViewHost(); 150 return rvh ? DevToolsAgentHost::GetOrCreateFor(rvh) : NULL; 151 } 152 153 virtual bool Activate() const OVERRIDE { 154 TabModel* model; 155 int index; 156 if (!FindTab(&model, &index)) 157 return false; 158 model->SetActiveIndex(index); 159 return true; 160 } 161 162 virtual bool Close() const OVERRIDE { 163 TabModel* model; 164 int index; 165 if (!FindTab(&model, &index)) 166 return false; 167 model->CloseTabAt(index); 168 return true; 169 } 170 171 private: 172 TabTarget(int tab_id, WebContents* web_contents) 173 : TargetBase(web_contents), 174 tab_id_(tab_id) { 175 } 176 177 TabTarget(int tab_id, const base::string16& title, const GURL& url) 178 : TargetBase(title, url), 179 tab_id_(tab_id) { 180 } 181 182 bool FindTab(TabModel** model_result, int* index_result) const { 183 for (TabModelList::const_iterator iter = TabModelList::begin(); 184 iter != TabModelList::end(); ++iter) { 185 TabModel* model = *iter; 186 for (int i = 0; i < model->GetTabCount(); ++i) { 187 TabAndroid* tab = model->GetTabAt(i); 188 if (tab->GetAndroidId() == tab_id_) { 189 *model_result = model; 190 *index_result = i; 191 return true; 192 } 193 } 194 } 195 return false; 196 } 197 198 const int tab_id_; 199}; 200 201class NonTabTarget : public TargetBase { 202 public: 203 explicit NonTabTarget(WebContents* web_contents) 204 : TargetBase(web_contents), 205 agent_host_(DevToolsAgentHost::GetOrCreateFor( 206 web_contents->GetRenderViewHost())) { 207 } 208 209 // content::DevToolsTarget implementation: 210 virtual std::string GetId() const OVERRIDE { 211 return agent_host_->GetId(); 212 } 213 214 virtual std::string GetType() const OVERRIDE { 215 if (TabModelList::begin() == TabModelList::end()) { 216 // If there are no tab models we must be running in ChromiumTestShell. 217 // Return the 'page' target type for backwards compatibility. 218 return kTargetTypePage; 219 } 220 return kTargetTypeOther; 221 } 222 223 virtual bool IsAttached() const OVERRIDE { 224 return agent_host_->IsAttached(); 225 } 226 227 virtual scoped_refptr<DevToolsAgentHost> GetAgentHost() const OVERRIDE { 228 return agent_host_; 229 } 230 231 virtual bool Activate() const OVERRIDE { 232 RenderViewHost* rvh = agent_host_->GetRenderViewHost(); 233 if (!rvh) 234 return false; 235 WebContents* web_contents = WebContents::FromRenderViewHost(rvh); 236 if (!web_contents) 237 return false; 238 web_contents->GetDelegate()->ActivateContents(web_contents); 239 return true; 240 } 241 242 virtual bool Close() const OVERRIDE { 243 RenderViewHost* rvh = agent_host_->GetRenderViewHost(); 244 if (!rvh) 245 return false; 246 rvh->ClosePage(); 247 return true; 248 } 249 250 private: 251 scoped_refptr<DevToolsAgentHost> agent_host_; 252}; 253 254// Delegate implementation for the devtools http handler on android. A new 255// instance of this gets created each time devtools is enabled. 256class DevToolsServerDelegate : public content::DevToolsHttpHandlerDelegate { 257 public: 258 DevToolsServerDelegate() 259 : last_tethering_socket_(0) { 260 } 261 262 virtual std::string GetDiscoveryPageHTML() OVERRIDE { 263 // TopSites updates itself after a delay. Ask TopSites to update itself 264 // when we're about to show the remote debugging landing page. 265 content::BrowserThread::PostTask( 266 content::BrowserThread::UI, 267 FROM_HERE, 268 base::Bind(&DevToolsServerDelegate::PopulatePageThumbnails)); 269 return ResourceBundle::GetSharedInstance().GetRawDataResource( 270 IDR_DEVTOOLS_DISCOVERY_PAGE_HTML).as_string(); 271 } 272 273 virtual bool BundlesFrontendResources() OVERRIDE { 274 return false; 275 } 276 277 virtual base::FilePath GetDebugFrontendDir() OVERRIDE { 278 return base::FilePath(); 279 } 280 281 virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE { 282 Profile* profile = 283 ProfileManager::GetLastUsedProfile()->GetOriginalProfile(); 284 history::TopSites* top_sites = profile->GetTopSites(); 285 if (top_sites) { 286 scoped_refptr<base::RefCountedMemory> data; 287 if (top_sites->GetPageThumbnail(url, false, &data)) 288 return std::string(reinterpret_cast<const char*>(data->front()), 289 data->size()); 290 } 291 return ""; 292 } 293 294 virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget( 295 const GURL& url) OVERRIDE { 296 Profile* profile = 297 g_browser_process->profile_manager()->GetDefaultProfile(); 298 TabModel* tab_model = TabModelList::GetTabModelWithProfile(profile); 299 if (!tab_model) 300 return scoped_ptr<content::DevToolsTarget>(); 301 WebContents* web_contents = tab_model->CreateNewTabForDevTools(url); 302 if (!web_contents) 303 return scoped_ptr<content::DevToolsTarget>(); 304 305 for (int i = 0; i < tab_model->GetTabCount(); ++i) { 306 if (web_contents != tab_model->GetWebContentsAt(i)) 307 continue; 308 TabAndroid* tab = tab_model->GetTabAt(i); 309 return scoped_ptr<content::DevToolsTarget>( 310 TabTarget::CreateForWebContents(tab->GetAndroidId(), web_contents)); 311 } 312 313 // Newly created tab not found, return no target. 314 return scoped_ptr<content::DevToolsTarget>(); 315 } 316 317 virtual void EnumerateTargets(TargetCallback callback) OVERRIDE { 318 TargetList targets; 319 320 // Enumerate existing tabs, including the ones with no WebContents. 321 std::set<WebContents*> tab_web_contents; 322 for (TabModelList::const_iterator iter = TabModelList::begin(); 323 iter != TabModelList::end(); ++iter) { 324 TabModel* model = *iter; 325 for (int i = 0; i < model->GetTabCount(); ++i) { 326 TabAndroid* tab = model->GetTabAt(i); 327 WebContents* web_contents = model->GetWebContentsAt(i); 328 if (web_contents) { 329 tab_web_contents.insert(web_contents); 330 targets.push_back(TabTarget::CreateForWebContents(tab->GetAndroidId(), 331 web_contents)); 332 } else { 333 targets.push_back(TabTarget::CreateForUnloadedTab(tab->GetAndroidId(), 334 tab->GetTitle(), 335 tab->GetURL())); 336 } 337 } 338 } 339 340 // Add targets for WebContents not associated with any tabs. 341 std::vector<RenderViewHost*> rvh_list = 342 DevToolsAgentHost::GetValidRenderViewHosts(); 343 for (std::vector<RenderViewHost*>::iterator it = rvh_list.begin(); 344 it != rvh_list.end(); ++it) { 345 WebContents* web_contents = WebContents::FromRenderViewHost(*it); 346 if (!web_contents) 347 continue; 348 if (tab_web_contents.find(web_contents) != tab_web_contents.end()) 349 continue; 350 targets.push_back(new NonTabTarget(web_contents)); 351 } 352 353 callback.Run(targets); 354 } 355 356 virtual scoped_ptr<net::StreamListenSocket> CreateSocketForTethering( 357 net::StreamListenSocket::Delegate* delegate, 358 std::string* name) OVERRIDE { 359 *name = base::StringPrintf( 360 kTetheringSocketName, getpid(), ++last_tethering_socket_); 361 return net::UnixDomainSocket::CreateAndListenWithAbstractNamespace( 362 *name, 363 "", 364 delegate, 365 base::Bind(&content::CanUserConnectToDevTools)) 366 .PassAs<net::StreamListenSocket>(); 367 } 368 369 private: 370 static void PopulatePageThumbnails() { 371 Profile* profile = 372 ProfileManager::GetLastUsedProfile()->GetOriginalProfile(); 373 history::TopSites* top_sites = profile->GetTopSites(); 374 if (top_sites) 375 top_sites->SyncWithHistory(); 376 } 377 378 int last_tethering_socket_; 379 380 DISALLOW_COPY_AND_ASSIGN(DevToolsServerDelegate); 381}; 382 383} // namespace 384 385DevToolsServer::DevToolsServer() 386 : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat, 387 kDefaultSocketNamePrefix)), 388 protocol_handler_(NULL) { 389 // Override the default socket name if one is specified on the command line. 390 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 391 if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) { 392 socket_name_ = command_line.GetSwitchValueASCII( 393 switches::kRemoteDebuggingSocketName); 394 } 395} 396 397DevToolsServer::DevToolsServer(const std::string& socket_name_prefix) 398 : socket_name_(base::StringPrintf(kDevToolsChannelNameFormat, 399 socket_name_prefix.c_str())), 400 protocol_handler_(NULL) { 401 // Override the socket name if one is specified on the command line. 402 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 403 if (command_line.HasSwitch(switches::kRemoteDebuggingSocketName)) { 404 socket_name_ = command_line.GetSwitchValueASCII( 405 switches::kRemoteDebuggingSocketName); 406 } 407} 408 409DevToolsServer::~DevToolsServer() { 410 Stop(); 411} 412 413void DevToolsServer::Start() { 414 if (protocol_handler_) 415 return; 416 417 protocol_handler_ = content::DevToolsHttpHandler::Start( 418 new net::UnixDomainSocketWithAbstractNamespaceFactory( 419 socket_name_, 420 base::StringPrintf("%s_%d", socket_name_.c_str(), getpid()), 421 base::Bind(&content::CanUserConnectToDevTools)), 422 base::StringPrintf(kFrontEndURL, 423 webkit_glue::GetWebKitRevision().c_str()), 424 new DevToolsServerDelegate()); 425} 426 427void DevToolsServer::Stop() { 428 if (!protocol_handler_) 429 return; 430 // Note that the call to Stop() below takes care of |protocol_handler_| 431 // deletion. 432 protocol_handler_->Stop(); 433 protocol_handler_ = NULL; 434} 435 436bool DevToolsServer::IsStarted() const { 437 return protocol_handler_; 438} 439 440bool RegisterDevToolsServer(JNIEnv* env) { 441 return RegisterNativesImpl(env); 442} 443 444static jint InitRemoteDebugging(JNIEnv* env, 445 jobject obj, 446 jstring socket_name_prefix) { 447 DevToolsServer* server = new DevToolsServer( 448 base::android::ConvertJavaStringToUTF8(env, socket_name_prefix)); 449 return reinterpret_cast<jint>(server); 450} 451 452static void DestroyRemoteDebugging(JNIEnv* env, jobject obj, jint server) { 453 delete reinterpret_cast<DevToolsServer*>(server); 454} 455 456static jboolean IsRemoteDebuggingEnabled(JNIEnv* env, 457 jobject obj, 458 jint server) { 459 return reinterpret_cast<DevToolsServer*>(server)->IsStarted(); 460} 461 462static void SetRemoteDebuggingEnabled(JNIEnv* env, 463 jobject obj, 464 jint server, 465 jboolean enabled) { 466 DevToolsServer* devtools_server = reinterpret_cast<DevToolsServer*>(server); 467 if (enabled) { 468 devtools_server->Start(); 469 } else { 470 devtools_server->Stop(); 471 } 472} 473