devtools_window.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/devtools/devtools_window.h"
6
7#include <algorithm>
8
9#include "base/command_line.h"
10#include "base/json/json_writer.h"
11#include "base/lazy_instance.h"
12#include "base/stringprintf.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/utf_string_conversions.h"
15#include "base/values.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/extensions/api/debugger/debugger_api.h"
18#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/browser/extensions/extension_system.h"
20#include "chrome/browser/file_select_helper.h"
21#include "chrome/browser/infobars/confirm_infobar_delegate.h"
22#include "chrome/browser/prefs/pref_service_syncable.h"
23#include "chrome/browser/prefs/scoped_user_pref_update.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/sessions/session_tab_helper.h"
26#include "chrome/browser/themes/theme_properties.h"
27#include "chrome/browser/themes/theme_service.h"
28#include "chrome/browser/themes/theme_service_factory.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_iterator.h"
31#include "chrome/browser/ui/browser_list.h"
32#include "chrome/browser/ui/browser_window.h"
33#include "chrome/browser/ui/host_desktop.h"
34#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
35#include "chrome/browser/ui/tabs/tab_strip_model.h"
36#include "chrome/browser/ui/webui/devtools_ui.h"
37#include "chrome/common/chrome_notification_types.h"
38#include "chrome/common/chrome_switches.h"
39#include "chrome/common/extensions/manifest_url_handler.h"
40#include "chrome/common/pref_names.h"
41#include "chrome/common/render_messages.h"
42#include "chrome/common/url_constants.h"
43#include "components/user_prefs/pref_registry_syncable.h"
44#include "content/public/browser/child_process_security_policy.h"
45#include "content/public/browser/devtools_agent_host.h"
46#include "content/public/browser/devtools_client_host.h"
47#include "content/public/browser/devtools_manager.h"
48#include "content/public/browser/favicon_status.h"
49#include "content/public/browser/load_notification_details.h"
50#include "content/public/browser/navigation_controller.h"
51#include "content/public/browser/navigation_entry.h"
52#include "content/public/browser/notification_source.h"
53#include "content/public/browser/render_process_host.h"
54#include "content/public/browser/render_view_host.h"
55#include "content/public/browser/web_contents.h"
56#include "content/public/browser/web_contents_observer.h"
57#include "content/public/browser/web_contents_view.h"
58#include "content/public/common/bindings_policy.h"
59#include "content/public/common/content_client.h"
60#include "content/public/common/page_transition_types.h"
61#include "content/public/common/url_constants.h"
62#include "grit/generated_resources.h"
63#include "ui/base/l10n/l10n_util.h"
64
65typedef std::vector<DevToolsWindow*> DevToolsWindowList;
66namespace {
67base::LazyInstance<DevToolsWindowList>::Leaky
68     g_instances = LAZY_INSTANCE_INITIALIZER;
69}  // namespace
70
71using base::Bind;
72using base::Callback;
73using content::DevToolsAgentHost;
74using content::DevToolsClientHost;
75using content::DevToolsManager;
76using content::FileChooserParams;
77using content::NativeWebKeyboardEvent;
78using content::NavigationController;
79using content::NavigationEntry;
80using content::OpenURLParams;
81using content::RenderViewHost;
82using content::WebContents;
83
84const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
85
86const char kOldPrefBottom[] = "bottom";
87const char kOldPrefRight[] = "right";
88
89const char kPrefBottom[] = "dock_bottom";
90const char kPrefRight[] = "dock_right";
91const char kPrefUndocked[] = "undocked";
92
93const char kDockSideBottom[] = "bottom";
94const char kDockSideRight[] = "right";
95const char kDockSideUndocked[] = "undocked";
96const char kDockSideMinimized[] = "minimized";
97
98// Minimal height of devtools pane or content pane when devtools are docked
99// to the browser window.
100const int kMinDevToolsHeight = 50;
101const int kMinDevToolsWidth = 150;
102const int kMinContentsSize = 50;
103const int kMinimizedDevToolsHeight = 24;
104
105class DevToolsWindow::InspectedWebContentsObserver
106    : public content::WebContentsObserver {
107 public:
108  explicit InspectedWebContentsObserver(content::WebContents* web_contents)
109    : WebContentsObserver(web_contents) {
110  }
111
112  content::WebContents* Get() { return web_contents(); }
113};
114
115class DevToolsWindow::FrontendWebContentsObserver
116    : public content::WebContentsObserver {
117 public:
118  explicit FrontendWebContentsObserver(content::WebContents* web_contents)
119    : WebContentsObserver(web_contents) {
120  }
121 private:
122  // Overriden from contents::WebContentsObserver.
123  virtual void AboutToNavigateRenderView(
124      RenderViewHost* render_view_host) OVERRIDE {
125    content::DevToolsClientHost::SetupDevToolsFrontendClient(render_view_host);
126  }
127};
128
129typedef Callback<void(bool)> ConfirmInfoBarCallback;
130
131class DevToolsConfirmInfoBarDelegate : public ConfirmInfoBarDelegate {
132 public:
133  DevToolsConfirmInfoBarDelegate(
134      InfoBarService* infobar_service,
135      const ConfirmInfoBarCallback& callback,
136      string16 message)
137      : ConfirmInfoBarDelegate(infobar_service),
138        callback_(callback),
139        message_(message) {
140  }
141
142  virtual string16 GetMessageText() const OVERRIDE { return message_; }
143
144  virtual bool Accept() OVERRIDE {
145    callback_.Run(true);
146    callback_.Reset();
147    return true;
148  }
149
150  virtual bool Cancel() OVERRIDE {
151    callback_.Run(false);
152    callback_.Reset();
153    return true;
154  }
155
156  virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE {
157    return l10n_util::GetStringUTF16((button == BUTTON_OK)
158                                         ? IDS_DEV_TOOLS_CONFIRM_ALLOW_BUTTON
159                                         : IDS_DEV_TOOLS_CONFIRM_DENY_BUTTON);
160  }
161
162 private:
163  virtual ~DevToolsConfirmInfoBarDelegate() {
164    if (!callback_.is_null()) {
165      callback_.Run(false);
166    }
167  }
168
169  ConfirmInfoBarCallback callback_;
170  string16 message_;
171};
172
173// static
174std::string DevToolsWindow::GetDevToolsWindowPlacementPrefKey() {
175  std::string wp_key;
176  wp_key.append(prefs::kBrowserWindowPlacement);
177  wp_key.append("_");
178  wp_key.append(kDevToolsApp);
179  return wp_key;
180}
181
182// static
183void DevToolsWindow::RegisterUserPrefs(
184    user_prefs::PrefRegistrySyncable* registry) {
185  registry->RegisterBooleanPref(
186      prefs::kDevToolsOpenDocked,
187      true,
188      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
189  registry->RegisterStringPref(
190      prefs::kDevToolsDockSide,
191      kDockSideBottom,
192      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
193  registry->RegisterDictionaryPref(
194      prefs::kDevToolsEditedFiles,
195      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
196  registry->RegisterDictionaryPref(
197      prefs::kDevToolsFileSystemPaths,
198      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
199
200  registry->RegisterDictionaryPref(
201      GetDevToolsWindowPlacementPrefKey().c_str(),
202      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
203}
204
205// static
206DevToolsWindow* DevToolsWindow::GetDockedInstanceForInspectedTab(
207    WebContents* inspected_web_contents) {
208  if (!inspected_web_contents)
209    return NULL;
210
211  if (!DevToolsAgentHost::HasFor(inspected_web_contents->GetRenderViewHost()))
212    return NULL;
213  scoped_refptr<DevToolsAgentHost> agent(DevToolsAgentHost::GetOrCreateFor(
214      inspected_web_contents->GetRenderViewHost()));
215  DevToolsWindow* window = FindDevToolsWindow(agent);
216  return window && window->IsDocked() ? window : NULL;
217}
218
219// static
220bool DevToolsWindow::IsDevToolsWindow(RenderViewHost* window_rvh) {
221  return AsDevToolsWindow(window_rvh) != NULL;
222}
223
224// static
225DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker(
226    Profile* profile,
227    DevToolsAgentHost* worker_agent) {
228  DevToolsWindow* window = FindDevToolsWindow(worker_agent);
229  if (!window) {
230    window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
231    // Will disconnect the current client host if there is one.
232    DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
233        worker_agent,
234        window->frontend_host_.get());
235  }
236  window->Show(DEVTOOLS_TOGGLE_ACTION_SHOW);
237  return window;
238}
239
240// static
241DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
242    Profile* profile) {
243  return Create(profile, GURL(), NULL, DEVTOOLS_DOCK_SIDE_UNDOCKED, true);
244}
245
246// static
247DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
248    RenderViewHost* inspected_rvh) {
249  return ToggleDevToolsWindow(inspected_rvh, true,
250                              DEVTOOLS_TOGGLE_ACTION_SHOW);
251}
252
253// static
254DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
255    Browser* browser,
256    DevToolsToggleAction action) {
257  if (action == DEVTOOLS_TOGGLE_ACTION_TOGGLE && browser->is_devtools()) {
258    browser->tab_strip_model()->CloseAllTabs();
259    return NULL;
260  }
261  RenderViewHost* inspected_rvh =
262      browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost();
263
264  return ToggleDevToolsWindow(inspected_rvh,
265                       action == DEVTOOLS_TOGGLE_ACTION_INSPECT,
266                       action);
267}
268
269// static
270void DevToolsWindow::InspectElement(RenderViewHost* inspected_rvh,
271                                    int x,
272                                    int y) {
273  scoped_refptr<DevToolsAgentHost> agent(
274      DevToolsAgentHost::GetOrCreateFor(inspected_rvh));
275  agent->InspectElement(x, y);
276  // TODO(loislo): we should initiate DevTools window opening from within
277  // renderer. Otherwise, we still can hit a race condition here.
278  OpenDevToolsWindow(inspected_rvh);
279}
280
281// static
282void DevToolsWindow::OpenExternalFrontend(
283    Profile* profile,
284    const std::string& frontend_url,
285    content::DevToolsAgentHost* agent_host) {
286  DevToolsWindow* window = FindDevToolsWindow(agent_host);
287  if (!window) {
288    window = Create(profile, DevToolsUI::GetProxyURL(frontend_url), NULL,
289                    DEVTOOLS_DOCK_SIDE_UNDOCKED, false);
290    DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
291        agent_host, window->frontend_host_.get());
292  }
293  window->Show(DEVTOOLS_TOGGLE_ACTION_SHOW);
294}
295
296// static
297DevToolsWindow* DevToolsWindow::Create(
298    Profile* profile,
299    const GURL& frontend_url,
300    RenderViewHost* inspected_rvh,
301    DevToolsDockSide dock_side,
302    bool shared_worker_frontend) {
303  // Create WebContents with devtools.
304  GURL url = GetDevToolsURL(profile, frontend_url, dock_side,
305                            shared_worker_frontend);
306  return new DevToolsWindow(profile, url, inspected_rvh, dock_side);
307}
308
309DevToolsWindow::DevToolsWindow(Profile* profile,
310                               const GURL& url,
311                               RenderViewHost* inspected_rvh,
312                               DevToolsDockSide dock_side)
313    : profile_(profile),
314      browser_(NULL),
315      dock_side_(dock_side),
316      is_loaded_(false),
317      action_on_load_(DEVTOOLS_TOGGLE_ACTION_SHOW),
318      weak_factory_(this),
319      width_(-1),
320      height_(-1),
321      dock_side_before_minimized_(dock_side) {
322  web_contents_ = WebContents::Create(WebContents::CreateParams(profile));
323  frontend_contents_observer_.reset(
324      new FrontendWebContentsObserver(web_contents_));
325
326  web_contents_->GetController().LoadURL(url, content::Referrer(),
327      content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
328
329  RenderViewHost* render_view_host = web_contents_->GetRenderViewHost();
330  if (url.host() == chrome::kChromeUIDevToolsBundledHost) {
331    // Only allow file scheme in embedded front-end by default.
332    int process_id = render_view_host->GetProcess()->GetID();
333    content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme(
334        process_id, chrome::kFileScheme);
335  }
336
337  frontend_host_.reset(
338      DevToolsClientHost::CreateDevToolsFrontendHost(web_contents_, this));
339  file_helper_.reset(new DevToolsFileHelper(web_contents_, profile));
340
341  g_instances.Get().push_back(this);
342  // Wipe out page icon so that the default application icon is used.
343  NavigationEntry* entry = web_contents_->GetController().GetActiveEntry();
344  entry->GetFavicon().image = gfx::Image();
345  entry->GetFavicon().valid = true;
346
347  // Register on-load actions.
348  registrar_.Add(
349      this,
350      content::NOTIFICATION_LOAD_STOP,
351      content::Source<NavigationController>(&web_contents_->GetController()));
352  registrar_.Add(
353      this,
354      chrome::NOTIFICATION_TAB_CLOSING,
355      content::Source<NavigationController>(&web_contents_->GetController()));
356  registrar_.Add(
357      this,
358      chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
359      content::Source<ThemeService>(
360          ThemeServiceFactory::GetForProfile(profile_)));
361  // There is no inspected_rvh in case of shared workers.
362  if (inspected_rvh)
363    inspected_contents_observer_.reset(new InspectedWebContentsObserver(
364        WebContents::FromRenderViewHost(inspected_rvh)));
365}
366
367DevToolsWindow::~DevToolsWindow() {
368  DevToolsWindowList& instances = g_instances.Get();
369  DevToolsWindowList::iterator it = std::find(instances.begin(),
370                                              instances.end(),
371                                              this);
372  DCHECK(it != instances.end());
373  instances.erase(it);
374}
375
376content::WebContents* DevToolsWindow::GetInspectedWebContents() {
377  if (!inspected_contents_observer_)
378    return NULL;
379  return inspected_contents_observer_->Get();
380}
381
382void DevToolsWindow::InspectedContentsClosing() {
383  Hide();
384}
385
386void DevToolsWindow::Hide() {
387  if (IsDocked()) {
388    // Update dev tools to reflect removed dev tools window.
389    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
390    if (inspected_window)
391      inspected_window->UpdateDevTools();
392    // In case of docked web_contents_, we own it so delete here.
393    delete web_contents_;
394
395    delete this;
396  } else {
397    // First, initiate self-destruct to free all the registrars.
398    // Then close all tabs. Browser will take care of deleting web_contents_
399    // for us.
400    Browser* browser = browser_;
401    delete this;
402    browser->tab_strip_model()->CloseAllTabs();
403  }
404}
405
406void DevToolsWindow::Show(DevToolsToggleAction action) {
407  if (IsDocked()) {
408    Browser* inspected_browser;
409    int inspected_tab_index;
410    // Tell inspected browser to update splitter and switch to inspected panel.
411    if (!IsInspectedBrowserPopup() &&
412        FindInspectedBrowserAndTabIndex(&inspected_browser,
413                                        &inspected_tab_index)) {
414      BrowserWindow* inspected_window = inspected_browser->window();
415      web_contents_->SetDelegate(this);
416      inspected_window->UpdateDevTools();
417      web_contents_->GetView()->SetInitialFocus();
418      inspected_window->Show();
419      TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
420      tab_strip_model->ActivateTabAt(inspected_tab_index, true);
421      PrefsTabHelper::CreateForWebContents(web_contents_);
422      GetRenderViewHost()->SyncRendererPrefs();
423      ScheduleAction(action);
424      return;
425    } else {
426      // Sometimes we don't know where to dock. Stay undocked.
427      dock_side_ = DEVTOOLS_DOCK_SIDE_UNDOCKED;
428    }
429  }
430
431  // Avoid consecutive window switching if the devtools window has been opened
432  // and the Inspect Element shortcut is pressed in the inspected tab.
433  bool should_show_window =
434      !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT;
435
436  if (!browser_)
437    CreateDevToolsBrowser();
438
439  if (should_show_window) {
440    browser_->window()->Show();
441    web_contents_->GetView()->SetInitialFocus();
442  }
443
444  ScheduleAction(action);
445}
446
447DevToolsClientHost* DevToolsWindow::GetDevToolsClientHostForTest() {
448  return frontend_host_.get();
449}
450
451int DevToolsWindow::GetWidth(int container_width) {
452  if (width_ == -1) {
453    width_ = profile_->GetPrefs()->
454        GetInteger(prefs::kDevToolsVSplitLocation);
455  }
456
457  // By default, size devtools as 1/3 of the browser window.
458  if (width_ == -1)
459    width_ = container_width / 3;
460
461  // Respect the minimum devtools width preset.
462  width_ = std::max(kMinDevToolsWidth, width_);
463
464  // But it should never compromise the content window size unless the entire
465  // window is tiny.
466  width_ = std::min(container_width - kMinContentsSize, width_);
467  return width_;
468}
469
470int DevToolsWindow::GetHeight(int container_height) {
471  if (height_ == -1) {
472    height_ = profile_->GetPrefs()->
473        GetInteger(prefs::kDevToolsHSplitLocation);
474  }
475
476  // By default, size devtools as 1/3 of the browser window.
477  if (height_ == -1)
478    height_ = container_height / 3;
479
480  // Respect the minimum devtools width preset.
481  height_ = std::max(kMinDevToolsHeight, height_);
482
483  // But it should never compromise the content window size.
484  height_ = std::min(container_height - kMinContentsSize, height_);
485  return height_;
486}
487
488int DevToolsWindow::GetMinimumWidth() {
489  return kMinDevToolsWidth;
490}
491
492int DevToolsWindow::GetMinimumHeight() {
493  return kMinDevToolsHeight;
494}
495
496void DevToolsWindow::SetWidth(int width) {
497  width_ = width;
498  profile_->GetPrefs()->SetInteger(prefs::kDevToolsVSplitLocation, width);
499}
500
501void DevToolsWindow::SetHeight(int height) {
502  height_ = height;
503  profile_->GetPrefs()->SetInteger(prefs::kDevToolsHSplitLocation, height);
504}
505
506int DevToolsWindow::GetMinimizedHeight() {
507  return kMinimizedDevToolsHeight;
508}
509
510RenderViewHost* DevToolsWindow::GetRenderViewHost() {
511  return web_contents_->GetRenderViewHost();
512}
513
514void DevToolsWindow::CreateDevToolsBrowser() {
515  std::string wp_key = GetDevToolsWindowPlacementPrefKey();
516  PrefService* prefs = profile_->GetPrefs();
517  const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str());
518  if (!wp_pref || wp_pref->empty()) {
519    DictionaryPrefUpdate update(prefs, wp_key.c_str());
520    DictionaryValue* defaults = update.Get();
521    defaults->SetInteger("left", 100);
522    defaults->SetInteger("top", 100);
523    defaults->SetInteger("right", 740);
524    defaults->SetInteger("bottom", 740);
525    defaults->SetBoolean("maximized", false);
526    defaults->SetBoolean("always_on_top", false);
527  }
528
529  chrome::HostDesktopType host_desktop_type =
530      chrome::GetHostDesktopTypeForNativeView(
531          web_contents_->GetView()->GetNativeView());
532
533  browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
534                             profile_, host_desktop_type));
535  browser_->tab_strip_model()->AddWebContents(
536      web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL,
537      TabStripModel::ADD_ACTIVE);
538  GetRenderViewHost()->SyncRendererPrefs();
539}
540
541bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser,
542                                                     int* tab) {
543  content::WebContents* inspected_web_contents = GetInspectedWebContents();
544  if (!inspected_web_contents)
545    return false;
546
547  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
548    int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
549        inspected_web_contents);
550    if (tab_index != TabStripModel::kNoTab) {
551      *browser = *it;
552      *tab = tab_index;
553      return true;
554    }
555  }
556  return false;
557}
558
559BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
560  Browser* browser = NULL;
561  int tab;
562  return FindInspectedBrowserAndTabIndex(&browser, &tab) ?
563      browser->window() : NULL;
564}
565
566bool DevToolsWindow::IsInspectedBrowserPopup() {
567  Browser* browser = NULL;
568  int tab;
569  if (!FindInspectedBrowserAndTabIndex(&browser, &tab))
570    return false;
571
572  return browser->is_type_popup();
573}
574
575void DevToolsWindow::UpdateFrontendDockSide() {
576  base::StringValue dock_side(SideToString(dock_side_));
577  CallClientFunction("InspectorFrontendAPI.setDockSide", &dock_side);
578  base::FundamentalValue docked(IsDocked());
579  CallClientFunction("InspectorFrontendAPI.setAttachedWindow", &docked);
580}
581
582
583void DevToolsWindow::AddDevToolsExtensionsToClient() {
584  content::WebContents* inspected_web_contents = GetInspectedWebContents();
585  if (inspected_web_contents) {
586    SessionTabHelper* session_tab_helper =
587        SessionTabHelper::FromWebContents(inspected_web_contents);
588    if (session_tab_helper) {
589      base::FundamentalValue tabId(session_tab_helper->session_id().id());
590      CallClientFunction("WebInspector.setInspectedTabId", &tabId);
591    }
592  }
593  ListValue results;
594  Profile* profile =
595      Profile::FromBrowserContext(web_contents_->GetBrowserContext());
596  const ExtensionService* extension_service = extensions::ExtensionSystem::Get(
597      profile->GetOriginalProfile())->extension_service();
598  if (!extension_service)
599    return;
600
601  const ExtensionSet* extensions = extension_service->extensions();
602
603  for (ExtensionSet::const_iterator extension = extensions->begin();
604       extension != extensions->end(); ++extension) {
605    if (extensions::ManifestURL::GetDevToolsPage(*extension).is_empty())
606      continue;
607    DictionaryValue* extension_info = new DictionaryValue();
608    extension_info->Set("startPage", new StringValue(
609        extensions::ManifestURL::GetDevToolsPage(*extension).spec()));
610    extension_info->Set("name", new StringValue((*extension)->name()));
611    bool allow_experimental = (*extension)->HasAPIPermission(
612        extensions::APIPermission::kExperimental);
613    extension_info->Set("exposeExperimentalAPIs",
614        new base::FundamentalValue(allow_experimental));
615    results.Append(extension_info);
616  }
617  CallClientFunction("WebInspector.addExtensions", &results);
618}
619
620WebContents* DevToolsWindow::OpenURLFromTab(WebContents* source,
621                                            const OpenURLParams& params) {
622  if (!params.url.SchemeIs(chrome::kChromeDevToolsScheme)) {
623    content::WebContents* inspected_web_contents = GetInspectedWebContents();
624    if (inspected_web_contents)
625      return inspected_web_contents->OpenURL(params);
626    return NULL;
627  }
628
629  DevToolsManager* manager = DevToolsManager::GetInstance();
630  scoped_refptr<DevToolsAgentHost> agent_host(
631      manager->GetDevToolsAgentHostFor(frontend_host_.get()));
632  if (!agent_host)
633    return NULL;
634  manager->ClientHostClosing(frontend_host_.get());
635  manager->RegisterDevToolsClientHostFor(agent_host, frontend_host_.get());
636
637  chrome::NavigateParams nav_params(profile_, params.url, params.transition);
638  FillNavigateParamsFromOpenURLParams(&nav_params, params);
639  nav_params.source_contents = source;
640  nav_params.tabstrip_add_types = TabStripModel::ADD_NONE;
641  nav_params.window_action = chrome::NavigateParams::SHOW_WINDOW;
642  nav_params.user_gesture = true;
643  chrome::Navigate(&nav_params);
644  return nav_params.target_contents;
645}
646
647void DevToolsWindow::CallClientFunction(const std::string& function_name,
648                                        const Value* arg1,
649                                        const Value* arg2) {
650  std::string params;
651  if (arg1) {
652    std::string json;
653    base::JSONWriter::Write(arg1, &json);
654    params.append(json);
655    if (arg2) {
656      base::JSONWriter::Write(arg2, &json);
657      params.append(", " + json);
658    }
659  }
660  string16 javascript = ASCIIToUTF16(function_name + "(" + params + ");");
661  web_contents_->GetRenderViewHost()->
662      ExecuteJavascriptInWebFrame(string16(), javascript);
663}
664
665void DevToolsWindow::Observe(int type,
666                             const content::NotificationSource& source,
667                             const content::NotificationDetails& details) {
668  if (type == content::NOTIFICATION_LOAD_STOP && !is_loaded_) {
669    is_loaded_ = true;
670    UpdateTheme();
671    DoAction();
672    AddDevToolsExtensionsToClient();
673  } else if (type == chrome::NOTIFICATION_TAB_CLOSING) {
674    if (content::Source<NavigationController>(source).ptr() ==
675            &web_contents_->GetController()) {
676      // This happens when browser closes all of its tabs as a result
677      // of window.Close event.
678      // Notify manager that this DevToolsClientHost no longer exists and
679      // initiate self-destuct here.
680      DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_.get());
681      UpdateBrowserToolbar();
682      delete this;
683    }
684  } else if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
685    UpdateTheme();
686  }
687}
688
689void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) {
690  action_on_load_ = action;
691  if (is_loaded_)
692    DoAction();
693}
694
695void DevToolsWindow::DoAction() {
696  UpdateFrontendDockSide();
697  switch (action_on_load_) {
698    case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE:
699      CallClientFunction("InspectorFrontendAPI.showConsole", NULL);
700      break;
701    case DEVTOOLS_TOGGLE_ACTION_INSPECT:
702      CallClientFunction("InspectorFrontendAPI.enterInspectElementMode", NULL);
703    case DEVTOOLS_TOGGLE_ACTION_SHOW:
704    case DEVTOOLS_TOGGLE_ACTION_TOGGLE:
705      // Do nothing.
706      break;
707    default:
708      NOTREACHED();
709  }
710  action_on_load_ = DEVTOOLS_TOGGLE_ACTION_SHOW;
711}
712
713std::string SkColorToRGBAString(SkColor color) {
714  // We convert the alpha using DoubleToString because StringPrintf will use
715  // locale specific formatters (e.g., use , instead of . in German).
716  return base::StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color),
717      SkColorGetG(color), SkColorGetB(color),
718      base::DoubleToString(SkColorGetA(color) / 255.0).c_str());
719}
720
721// static
722GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
723                                    const GURL& base_url,
724                                    DevToolsDockSide dock_side,
725                                    bool shared_worker_frontend) {
726  ThemeService* tp = ThemeServiceFactory::GetForProfile(profile);
727  CHECK(tp);
728
729  SkColor color_toolbar =
730      tp->GetColor(ThemeProperties::COLOR_TOOLBAR);
731  SkColor color_tab_text =
732      tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
733
734  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
735  bool experiments_enabled =
736      command_line.HasSwitch(switches::kEnableDevToolsExperiments);
737
738  std::string frontend_url = base_url.is_empty() ?
739      chrome::kChromeUIDevToolsURL : base_url.spec();
740  std::string params_separator =
741      frontend_url.find("?") == std::string::npos ? "?" : "&";
742  std::string url_string = base::StringPrintf("%s%s"
743      "dockSide=%s&toolbarColor=%s&textColor=%s%s%s",
744      frontend_url.c_str(),
745      params_separator.c_str(),
746      SideToString(dock_side).c_str(),
747      SkColorToRGBAString(color_toolbar).c_str(),
748      SkColorToRGBAString(color_tab_text).c_str(),
749      shared_worker_frontend ? "&isSharedWorker=true" : "",
750      experiments_enabled ? "&experiments=true" : "");
751  return GURL(url_string);
752}
753
754void DevToolsWindow::UpdateTheme() {
755  ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_);
756  CHECK(tp);
757
758  SkColor color_toolbar =
759      tp->GetColor(ThemeProperties::COLOR_TOOLBAR);
760  SkColor color_tab_text =
761      tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT);
762  std::string command = base::StringPrintf(
763      "InspectorFrontendAPI.setToolbarColors(\"%s\", \"%s\")",
764      SkColorToRGBAString(color_toolbar).c_str(),
765      SkColorToRGBAString(color_tab_text).c_str());
766  web_contents_->GetRenderViewHost()->
767      ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(command));
768}
769
770void DevToolsWindow::AddNewContents(WebContents* source,
771                                    WebContents* new_contents,
772                                    WindowOpenDisposition disposition,
773                                    const gfx::Rect& initial_pos,
774                                    bool user_gesture,
775                                    bool* was_blocked) {
776  content::WebContents* inspected_web_contents = GetInspectedWebContents();
777  if (inspected_web_contents) {
778    inspected_web_contents->GetDelegate()->AddNewContents(
779        source, new_contents, disposition, initial_pos, user_gesture,
780        was_blocked);
781  }
782}
783
784bool DevToolsWindow::PreHandleKeyboardEvent(
785    WebContents* source,
786    const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
787  if (IsDocked()) {
788    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
789    if (inspected_window)
790      return inspected_window->PreHandleKeyboardEvent(
791          event, is_keyboard_shortcut);
792  }
793  return false;
794}
795
796void DevToolsWindow::HandleKeyboardEvent(WebContents* source,
797                                         const NativeWebKeyboardEvent& event) {
798  if (IsDocked()) {
799    if (event.windowsKeyCode == 0x08) {
800      // Do not navigate back in history on Windows (http://crbug.com/74156).
801      return;
802    }
803    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
804    if (inspected_window)
805      inspected_window->HandleKeyboardEvent(event);
806  }
807}
808
809// static
810DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
811    RenderViewHost* inspected_rvh,
812    bool force_open,
813    DevToolsToggleAction action) {
814  scoped_refptr<DevToolsAgentHost> agent(
815      DevToolsAgentHost::GetOrCreateFor(inspected_rvh));
816  DevToolsManager* manager = DevToolsManager::GetInstance();
817  DevToolsWindow* window = FindDevToolsWindow(agent);
818  bool do_open = force_open;
819  if (!window) {
820    Profile* profile = Profile::FromBrowserContext(
821        inspected_rvh->GetProcess()->GetBrowserContext());
822    DevToolsDockSide dock_side = GetDockSideFromPrefs(profile);
823    window = Create(profile, GURL(), inspected_rvh, dock_side, false);
824    manager->RegisterDevToolsClientHostFor(agent, window->frontend_host_.get());
825    do_open = true;
826  }
827
828  // Update toolbar to reflect DevTools changes.
829  window->UpdateBrowserToolbar();
830
831  // If window is docked and visible, we hide it on toggle. If window is
832  // undocked, we show (activate) it. If window is minimized, we maximize it.
833  if (window->dock_side_ == DEVTOOLS_DOCK_SIDE_MINIMIZED)
834    window->Restore();
835  else if (!window->IsDocked() || do_open)
836    window->Show(action);
837  else
838    window->CloseWindow();
839
840  return window;
841}
842
843// static
844DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
845    DevToolsAgentHost* agent_host) {
846  DevToolsManager* manager = DevToolsManager::GetInstance();
847  DevToolsWindowList& instances = g_instances.Get();
848  for (DevToolsWindowList::iterator it = instances.begin();
849       it != instances.end(); ++it) {
850    if (manager->GetDevToolsAgentHostFor((*it)->frontend_host_.get()) ==
851        agent_host)
852      return *it;
853  }
854  return NULL;
855}
856
857// static
858DevToolsWindow* DevToolsWindow::AsDevToolsWindow(RenderViewHost* window_rvh) {
859  if (g_instances == NULL)
860    return NULL;
861  DevToolsWindowList& instances = g_instances.Get();
862  for (DevToolsWindowList::iterator it = instances.begin();
863       it != instances.end(); ++it) {
864    if ((*it)->web_contents_->GetRenderViewHost() == window_rvh)
865      return *it;
866  }
867  return NULL;
868}
869
870void DevToolsWindow::ActivateWindow() {
871  if (!IsDocked()) {
872    if (!browser_->window()->IsActive()) {
873      browser_->window()->Activate();
874    }
875  } else {
876    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
877    if (inspected_window)
878      web_contents_->GetView()->Focus();
879  }
880}
881
882void DevToolsWindow::ChangeAttachedWindowHeight(unsigned height) {
883  if (dock_side_ != DEVTOOLS_DOCK_SIDE_BOTTOM)
884    return;
885
886  SetHeight(height);
887  // Update inspected window to adjust heights.
888  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
889  if (inspected_window)
890    inspected_window->UpdateDevTools();
891}
892
893void DevToolsWindow::CloseWindow() {
894  DCHECK(IsDocked());
895  DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_.get());
896  Hide();
897}
898
899void DevToolsWindow::MoveWindow(int x, int y) {
900  if (!IsDocked()) {
901    gfx::Rect bounds = browser_->window()->GetBounds();
902    bounds.Offset(x, y);
903    browser_->window()->SetBounds(bounds);
904  }
905}
906
907void DevToolsWindow::SetDockSide(const std::string& side) {
908  DevToolsDockSide requested_side = SideFromString(side);
909  bool dock_requested = requested_side != DEVTOOLS_DOCK_SIDE_UNDOCKED;
910  bool is_docked = IsDocked();
911
912  content::WebContents* inspected_web_contents = GetInspectedWebContents();
913  if (dock_requested && (!inspected_web_contents ||
914      !GetInspectedBrowserWindow() || IsInspectedBrowserPopup())) {
915      // Cannot dock, avoid window flashing due to close-reopen cycle.
916    return;
917  }
918
919  if (dock_side_ != DEVTOOLS_DOCK_SIDE_MINIMIZED &&
920      requested_side == DEVTOOLS_DOCK_SIDE_MINIMIZED) {
921    dock_side_before_minimized_ = dock_side_;
922  }
923
924  dock_side_ = requested_side;
925  if (dock_requested) {
926    if (!is_docked) {
927      // Detach window from the external devtools browser. It will lead to
928      // the browser object's close and delete. Remove observer first.
929      TabStripModel* tab_strip_model = browser_->tab_strip_model();
930      tab_strip_model->DetachWebContentsAt(
931          tab_strip_model->GetIndexOfWebContents(web_contents_));
932      browser_ = NULL;
933    }
934  } else if (is_docked) {
935    // Update inspected window to hide split and reset it.
936    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
937    if (inspected_window)
938      inspected_window->UpdateDevTools();
939  }
940
941  if (dock_side_ != DEVTOOLS_DOCK_SIDE_MINIMIZED) {
942    std::string pref_value = kPrefBottom;
943    switch (dock_side_) {
944      case DEVTOOLS_DOCK_SIDE_UNDOCKED:
945          pref_value = kPrefUndocked;
946          break;
947      case DEVTOOLS_DOCK_SIDE_RIGHT:
948          pref_value = kPrefRight;
949          break;
950      case DEVTOOLS_DOCK_SIDE_BOTTOM:
951          pref_value = kPrefBottom;
952          break;
953      case DEVTOOLS_DOCK_SIDE_MINIMIZED:
954          // We don't persist minimized state.
955          break;
956    }
957    profile_->GetPrefs()->SetString(prefs::kDevToolsDockSide, pref_value);
958  }
959
960  Show(DEVTOOLS_TOGGLE_ACTION_SHOW);
961}
962
963void DevToolsWindow::Restore() {
964  if (dock_side_ == DEVTOOLS_DOCK_SIDE_MINIMIZED)
965    SetDockSide(SideToString(dock_side_before_minimized_));
966}
967
968void DevToolsWindow::OpenInNewTab(const std::string& url) {
969  OpenURLParams params(GURL(url),
970                       content::Referrer(),
971                       NEW_FOREGROUND_TAB,
972                       content::PAGE_TRANSITION_LINK,
973                       false /* is_renderer_initiated */);
974  content::WebContents* inspected_web_contents = GetInspectedWebContents();
975  if (inspected_web_contents) {
976    inspected_web_contents->OpenURL(params);
977  } else {
978    chrome::HostDesktopType host_desktop_type;
979    if (browser_) {
980      host_desktop_type = browser_->host_desktop_type();
981    } else {
982      // There should always be a browser when there are no inspected web
983      // contents.
984      NOTREACHED();
985      host_desktop_type = chrome::GetActiveDesktop();
986    }
987
988    const BrowserList* browser_list =
989        BrowserList::GetInstance(host_desktop_type);
990    for (BrowserList::const_iterator it = browser_list->begin();
991         it != browser_list->end(); ++it) {
992      if ((*it)->type() == Browser::TYPE_TABBED) {
993        (*it)->OpenURL(params);
994        break;
995      }
996    }
997  }
998}
999
1000void DevToolsWindow::SaveToFile(const std::string& url,
1001                                const std::string& content,
1002                                bool save_as) {
1003  file_helper_->Save(url, content, save_as, Bind(&DevToolsWindow::FileSavedAs,
1004                                                 weak_factory_.GetWeakPtr(),
1005                                                 url));
1006}
1007
1008void DevToolsWindow::AppendToFile(const std::string& url,
1009                                  const std::string& content) {
1010  file_helper_->Append(url, content, Bind(&DevToolsWindow::AppendedTo,
1011                                          weak_factory_.GetWeakPtr(),
1012                                          url));
1013}
1014
1015namespace {
1016
1017DictionaryValue* CreateFileSystemValue(
1018    DevToolsFileHelper::FileSystem file_system) {
1019  DictionaryValue* file_system_value = new DictionaryValue();
1020  file_system_value->SetString("fileSystemName", file_system.file_system_name);
1021  file_system_value->SetString("rootURL", file_system.root_url);
1022  file_system_value->SetString("fileSystemPath", file_system.file_system_path);
1023  return file_system_value;
1024}
1025
1026} // namespace
1027
1028void DevToolsWindow::RequestFileSystems() {
1029  CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme));
1030  file_helper_->RequestFileSystems(
1031      Bind(&DevToolsWindow::FileSystemsLoaded, weak_factory_.GetWeakPtr()));
1032}
1033
1034void DevToolsWindow::AddFileSystem() {
1035  CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme));
1036  file_helper_->AddFileSystem(
1037      Bind(&DevToolsWindow::FileSystemAdded, weak_factory_.GetWeakPtr()),
1038      Bind(&DevToolsWindow::ShowDevToolsConfirmInfoBar,
1039           weak_factory_.GetWeakPtr()));
1040}
1041
1042void DevToolsWindow::RemoveFileSystem(const std::string& file_system_path) {
1043  CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme));
1044  file_helper_->RemoveFileSystem(file_system_path);
1045  StringValue file_system_path_value(file_system_path);
1046  CallClientFunction("InspectorFrontendAPI.fileSystemRemoved",
1047                     &file_system_path_value);
1048}
1049
1050void DevToolsWindow::FileSavedAs(const std::string& url) {
1051  StringValue url_value(url);
1052  CallClientFunction("InspectorFrontendAPI.savedURL", &url_value);
1053}
1054
1055void DevToolsWindow::AppendedTo(const std::string& url) {
1056  StringValue url_value(url);
1057  CallClientFunction("InspectorFrontendAPI.appendedToURL", &url_value);
1058}
1059
1060void DevToolsWindow::FileSystemsLoaded(
1061    const std::vector<DevToolsFileHelper::FileSystem>& file_systems) {
1062  ListValue file_systems_value;
1063  for (size_t i = 0; i < file_systems.size(); ++i) {
1064    file_systems_value.Append(CreateFileSystemValue(file_systems[i]));
1065  }
1066  CallClientFunction("InspectorFrontendAPI.fileSystemsLoaded",
1067                     &file_systems_value);
1068}
1069
1070void DevToolsWindow::FileSystemAdded(
1071    const DevToolsFileHelper::FileSystem& file_system) {
1072  StringValue error_string_value("");
1073  DictionaryValue* file_system_value = NULL;
1074  if (!file_system.file_system_path.empty())
1075    file_system_value = CreateFileSystemValue(file_system);
1076  CallClientFunction("InspectorFrontendAPI.fileSystemAdded",
1077                     &error_string_value,
1078                     file_system_value);
1079  if (file_system_value)
1080    delete file_system_value;
1081}
1082
1083void DevToolsWindow::ShowDevToolsConfirmInfoBar(
1084    const string16& message,
1085    const ConfirmInfoBarCallback& callback) {
1086  InfoBarService* infobar_service = IsDocked() ?
1087      InfoBarService::FromWebContents(GetInspectedWebContents()) :
1088      InfoBarService::FromWebContents(web_contents_);
1089
1090  if (infobar_service) {
1091    infobar_service->AddInfoBar(scoped_ptr<InfoBarDelegate>(
1092        new DevToolsConfirmInfoBarDelegate(
1093            infobar_service,
1094            callback,
1095            message)));
1096  } else {
1097    callback.Run(false);
1098  }
1099}
1100
1101content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() {
1102  content::WebContents* inspected_web_contents = GetInspectedWebContents();
1103  if (inspected_web_contents && inspected_web_contents->GetDelegate()) {
1104    return inspected_web_contents->GetDelegate()->
1105        GetJavaScriptDialogManager();
1106  }
1107  return content::WebContentsDelegate::GetJavaScriptDialogManager();
1108}
1109
1110void DevToolsWindow::RunFileChooser(WebContents* web_contents,
1111                                    const FileChooserParams& params) {
1112  FileSelectHelper::RunFileChooser(web_contents, params);
1113}
1114
1115void DevToolsWindow::WebContentsFocused(WebContents* contents) {
1116  Browser* inspected_browser = NULL;
1117  int inspected_tab_index = -1;
1118
1119  if (IsDocked() && FindInspectedBrowserAndTabIndex(&inspected_browser,
1120                                                    &inspected_tab_index)) {
1121    inspected_browser->window()->WebContentsFocused(contents);
1122  }
1123}
1124
1125void DevToolsWindow::UpdateBrowserToolbar() {
1126  content::WebContents* inspected_web_contents = GetInspectedWebContents();
1127  if (!inspected_web_contents)
1128    return;
1129  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1130  if (inspected_window)
1131    inspected_window->UpdateToolbar(inspected_web_contents, false);
1132}
1133
1134bool DevToolsWindow::IsDocked() {
1135  return dock_side_ != DEVTOOLS_DOCK_SIDE_UNDOCKED;
1136}
1137
1138// static
1139DevToolsDockSide DevToolsWindow::GetDockSideFromPrefs(Profile* profile) {
1140  std::string dock_side =
1141      profile->GetPrefs()->GetString(prefs::kDevToolsDockSide);
1142
1143  // Migrate prefs
1144  if (dock_side == kOldPrefBottom || dock_side == kOldPrefRight) {
1145    bool docked = profile->GetPrefs()->GetBoolean(prefs::kDevToolsOpenDocked);
1146    if (dock_side == kOldPrefBottom)
1147      return docked ? DEVTOOLS_DOCK_SIDE_BOTTOM : DEVTOOLS_DOCK_SIDE_UNDOCKED;
1148    else
1149      return docked ? DEVTOOLS_DOCK_SIDE_RIGHT : DEVTOOLS_DOCK_SIDE_UNDOCKED;
1150  }
1151
1152  if (dock_side == kPrefUndocked)
1153    return DEVTOOLS_DOCK_SIDE_UNDOCKED;
1154  else if (dock_side == kPrefRight)
1155    return DEVTOOLS_DOCK_SIDE_RIGHT;
1156  // Default to docked to bottom
1157  return DEVTOOLS_DOCK_SIDE_BOTTOM;
1158}
1159
1160// static
1161std::string DevToolsWindow::SideToString(DevToolsDockSide dock_side) {
1162  std::string dock_side_string;
1163  switch (dock_side) {
1164    case DEVTOOLS_DOCK_SIDE_UNDOCKED: return kDockSideUndocked;
1165    case DEVTOOLS_DOCK_SIDE_RIGHT: return kDockSideRight;
1166    case DEVTOOLS_DOCK_SIDE_BOTTOM: return kDockSideBottom;
1167    case DEVTOOLS_DOCK_SIDE_MINIMIZED: return kDockSideMinimized;
1168  }
1169  return kDockSideUndocked;
1170}
1171
1172// static
1173DevToolsDockSide DevToolsWindow::SideFromString(
1174    const std::string& dock_side) {
1175  if (dock_side == kDockSideRight)
1176    return DEVTOOLS_DOCK_SIDE_RIGHT;
1177  if (dock_side == kDockSideBottom)
1178    return DEVTOOLS_DOCK_SIDE_BOTTOM;
1179  if (dock_side == kDockSideMinimized)
1180    return DEVTOOLS_DOCK_SIDE_MINIMIZED;
1181  return DEVTOOLS_DOCK_SIDE_UNDOCKED;
1182}
1183