1 // Copyright (c) 2011 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 "base/command_line.h"
6 #include "base/json/json_writer.h"
7 #include "base/string_number_conversions.h"
8 #include "base/utf_string_conversions.h"
9 #include "base/values.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/debugger/devtools_manager.h"
12 #include "chrome/browser/debugger/devtools_window.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/load_notification_details.h"
15 #include "chrome/browser/prefs/pref_service.h"
16 #include "chrome/browser/prefs/scoped_user_pref_update.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/tabs/tab_strip_model.h"
19 #include "chrome/browser/themes/theme_service.h"
20 #include "chrome/browser/themes/theme_service_factory.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/render_messages.h"
27 #include "chrome/common/url_constants.h"
28 #include "content/browser/in_process_webkit/session_storage_namespace.h"
29 #include "content/browser/renderer_host/render_view_host.h"
30 #include "content/browser/tab_contents/navigation_controller.h"
31 #include "content/browser/tab_contents/navigation_entry.h"
32 #include "content/browser/tab_contents/tab_contents.h"
33 #include "content/browser/tab_contents/tab_contents_view.h"
34 #include "content/common/bindings_policy.h"
35 #include "content/common/notification_service.h"
36 #include "grit/generated_resources.h"
37
38 const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
39
40 // static
GetDevToolsContents(TabContents * inspected_tab)41 TabContentsWrapper* DevToolsWindow::GetDevToolsContents(
42 TabContents* inspected_tab) {
43 if (!inspected_tab) {
44 return NULL;
45 }
46
47 if (!DevToolsManager::GetInstance())
48 return NULL; // Happens only in tests.
49
50 DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
51 GetDevToolsClientHostFor(inspected_tab->render_view_host());
52 if (!client_host) {
53 return NULL;
54 }
55
56 DevToolsWindow* window = client_host->AsDevToolsWindow();
57 if (!window || !window->is_docked()) {
58 return NULL;
59 }
60 return window->tab_contents();
61 }
62
DevToolsWindow(Profile * profile,RenderViewHost * inspected_rvh,bool docked)63 DevToolsWindow::DevToolsWindow(Profile* profile,
64 RenderViewHost* inspected_rvh,
65 bool docked)
66 : profile_(profile),
67 browser_(NULL),
68 docked_(docked),
69 is_loaded_(false),
70 action_on_load_(DEVTOOLS_TOGGLE_ACTION_NONE) {
71 // Create TabContents with devtools.
72 tab_contents_ =
73 Browser::TabContentsFactory(profile, NULL, MSG_ROUTING_NONE, NULL, NULL);
74 tab_contents_->tab_contents()->
75 render_view_host()->AllowBindings(BindingsPolicy::WEB_UI);
76 tab_contents_->controller().LoadURL(
77 GetDevToolsUrl(), GURL(), PageTransition::START_PAGE);
78
79 // Wipe out page icon so that the default application icon is used.
80 NavigationEntry* entry = tab_contents_->controller().GetActiveEntry();
81 entry->favicon().set_bitmap(SkBitmap());
82 entry->favicon().set_is_valid(true);
83
84 // Register on-load actions.
85 registrar_.Add(this,
86 NotificationType::LOAD_STOP,
87 Source<NavigationController>(&tab_contents_->controller()));
88 registrar_.Add(this,
89 NotificationType::TAB_CLOSING,
90 Source<NavigationController>(&tab_contents_->controller()));
91 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
92 NotificationService::AllSources());
93 inspected_tab_ = inspected_rvh->delegate()->GetAsTabContents();
94 }
95
~DevToolsWindow()96 DevToolsWindow::~DevToolsWindow() {
97 }
98
AsDevToolsWindow()99 DevToolsWindow* DevToolsWindow::AsDevToolsWindow() {
100 return this;
101 }
102
SendMessageToClient(const IPC::Message & message)103 void DevToolsWindow::SendMessageToClient(const IPC::Message& message) {
104 RenderViewHost* target_host =
105 tab_contents_->tab_contents()->render_view_host();
106 IPC::Message* m = new IPC::Message(message);
107 m->set_routing_id(target_host->routing_id());
108 target_host->Send(m);
109 }
110
InspectedTabClosing()111 void DevToolsWindow::InspectedTabClosing() {
112 if (docked_) {
113 // Update dev tools to reflect removed dev tools window.
114
115 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
116 if (inspected_window)
117 inspected_window->UpdateDevTools();
118 // In case of docked tab_contents we own it, so delete here.
119 delete tab_contents_;
120
121 delete this;
122 } else {
123 // First, initiate self-destruct to free all the registrars.
124 // Then close all tabs. Browser will take care of deleting tab_contents
125 // for us.
126 Browser* browser = browser_;
127 delete this;
128 browser->CloseAllTabs();
129 }
130 }
131
TabReplaced(TabContentsWrapper * new_tab)132 void DevToolsWindow::TabReplaced(TabContentsWrapper* new_tab) {
133 DCHECK_EQ(profile_, new_tab->profile());
134 inspected_tab_ = new_tab->tab_contents();
135 }
136
Show(DevToolsToggleAction action)137 void DevToolsWindow::Show(DevToolsToggleAction action) {
138 if (docked_) {
139 Browser* inspected_browser;
140 int inspected_tab_index;
141 // Tell inspected browser to update splitter and switch to inspected panel.
142 if (!IsInspectedBrowserPopup() &&
143 FindInspectedBrowserAndTabIndex(&inspected_browser,
144 &inspected_tab_index)) {
145 BrowserWindow* inspected_window = inspected_browser->window();
146 tab_contents_->tab_contents()->set_delegate(this);
147 inspected_window->UpdateDevTools();
148 tab_contents_->view()->SetInitialFocus();
149 inspected_window->Show();
150 TabStripModel* tabstrip_model = inspected_browser->tabstrip_model();
151 tabstrip_model->ActivateTabAt(inspected_tab_index, true);
152 ScheduleAction(action);
153 return;
154 } else {
155 // Sometimes we don't know where to dock. Stay undocked.
156 docked_ = false;
157 }
158 }
159
160 // Avoid consecutive window switching if the devtools window has been opened
161 // and the Inspect Element shortcut is pressed in the inspected tab.
162 bool should_show_window =
163 !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT;
164
165 if (!browser_)
166 CreateDevToolsBrowser();
167
168 if (should_show_window) {
169 browser_->window()->Show();
170 tab_contents_->view()->SetInitialFocus();
171 }
172
173 ScheduleAction(action);
174 }
175
Activate()176 void DevToolsWindow::Activate() {
177 if (!docked_) {
178 if (!browser_->window()->IsActive()) {
179 browser_->window()->Activate();
180 }
181 } else {
182 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
183 if (inspected_window)
184 tab_contents_->view()->Focus();
185 }
186 }
187
SetDocked(bool docked)188 void DevToolsWindow::SetDocked(bool docked) {
189 if (docked_ == docked)
190 return;
191 if (docked && (!GetInspectedBrowserWindow() || IsInspectedBrowserPopup())) {
192 // Cannot dock, avoid window flashing due to close-reopen cycle.
193 return;
194 }
195 docked_ = docked;
196
197 if (docked) {
198 // Detach window from the external devtools browser. It will lead to
199 // the browser object's close and delete. Remove observer first.
200 TabStripModel* tabstrip_model = browser_->tabstrip_model();
201 tabstrip_model->DetachTabContentsAt(
202 tabstrip_model->GetIndexOfTabContents(tab_contents_));
203 browser_ = NULL;
204 } else {
205 // Update inspected window to hide split and reset it.
206 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
207 if (inspected_window) {
208 inspected_window->UpdateDevTools();
209 inspected_window = NULL;
210 }
211 }
212 Show(DEVTOOLS_TOGGLE_ACTION_NONE);
213 }
214
GetRenderViewHost()215 RenderViewHost* DevToolsWindow::GetRenderViewHost() {
216 return tab_contents_->render_view_host();
217 }
218
CreateDevToolsBrowser()219 void DevToolsWindow::CreateDevToolsBrowser() {
220 // TODO(pfeldman): Make browser's getter for this key static.
221 std::string wp_key;
222 wp_key.append(prefs::kBrowserWindowPlacement);
223 wp_key.append("_");
224 wp_key.append(kDevToolsApp);
225
226 PrefService* prefs = profile_->GetPrefs();
227 if (!prefs->FindPreference(wp_key.c_str())) {
228 prefs->RegisterDictionaryPref(wp_key.c_str());
229 }
230
231 const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str());
232 if (!wp_pref || wp_pref->empty()) {
233 DictionaryPrefUpdate update(prefs, wp_key.c_str());
234 DictionaryValue* defaults = update.Get();
235 defaults->SetInteger("left", 100);
236 defaults->SetInteger("top", 100);
237 defaults->SetInteger("right", 740);
238 defaults->SetInteger("bottom", 740);
239 defaults->SetBoolean("maximized", false);
240 defaults->SetBoolean("always_on_top", false);
241 }
242
243 browser_ = Browser::CreateForDevTools(profile_);
244 browser_->tabstrip_model()->AddTabContents(
245 tab_contents_, -1, PageTransition::START_PAGE, TabStripModel::ADD_ACTIVE);
246 }
247
FindInspectedBrowserAndTabIndex(Browser ** browser,int * tab)248 bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser,
249 int* tab) {
250 const NavigationController& controller = inspected_tab_->controller();
251 for (BrowserList::const_iterator it = BrowserList::begin();
252 it != BrowserList::end(); ++it) {
253 int tab_index = (*it)->GetIndexOfController(&controller);
254 if (tab_index != TabStripModel::kNoTab) {
255 *browser = *it;
256 *tab = tab_index;
257 return true;
258 }
259 }
260 return false;
261 }
262
GetInspectedBrowserWindow()263 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
264 Browser* browser = NULL;
265 int tab;
266 return FindInspectedBrowserAndTabIndex(&browser, &tab) ?
267 browser->window() : NULL;
268 }
269
IsInspectedBrowserPopup()270 bool DevToolsWindow::IsInspectedBrowserPopup() {
271 Browser* browser = NULL;
272 int tab;
273 if (!FindInspectedBrowserAndTabIndex(&browser, &tab))
274 return false;
275
276 return (browser->type() & Browser::TYPE_POPUP) != 0;
277 }
278
UpdateFrontendAttachedState()279 void DevToolsWindow::UpdateFrontendAttachedState() {
280 tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame(
281 string16(),
282 docked_ ? ASCIIToUTF16("WebInspector.setAttachedWindow(true);")
283 : ASCIIToUTF16("WebInspector.setAttachedWindow(false);"));
284 }
285
286
AddDevToolsExtensionsToClient()287 void DevToolsWindow::AddDevToolsExtensionsToClient() {
288 if (inspected_tab_) {
289 FundamentalValue tabId(inspected_tab_->controller().session_id().id());
290 CallClientFunction(ASCIIToUTF16("WebInspector.setInspectedTabId"), tabId);
291 }
292 ListValue results;
293 const ExtensionService* extension_service =
294 tab_contents_->tab_contents()->profile()->
295 GetOriginalProfile()->GetExtensionService();
296 if (!extension_service)
297 return;
298
299 const ExtensionList* extensions = extension_service->extensions();
300
301 for (ExtensionList::const_iterator extension = extensions->begin();
302 extension != extensions->end(); ++extension) {
303 if ((*extension)->devtools_url().is_empty())
304 continue;
305 DictionaryValue* extension_info = new DictionaryValue();
306 extension_info->Set("startPage",
307 new StringValue((*extension)->devtools_url().spec()));
308 results.Append(extension_info);
309 }
310 CallClientFunction(ASCIIToUTF16("WebInspector.addExtensions"), results);
311 }
312
OpenURLFromTab(TabContents * source,const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)313 void DevToolsWindow::OpenURLFromTab(TabContents* source,
314 const GURL& url,
315 const GURL& referrer,
316 WindowOpenDisposition disposition,
317 PageTransition::Type transition) {
318 if (inspected_tab_)
319 inspected_tab_->OpenURL(url,
320 GURL(),
321 NEW_FOREGROUND_TAB,
322 PageTransition::LINK);
323 }
324
CallClientFunction(const string16 & function_name,const Value & arg)325 void DevToolsWindow::CallClientFunction(const string16& function_name,
326 const Value& arg) {
327 std::string json;
328 base::JSONWriter::Write(&arg, false, &json);
329 string16 javascript = function_name + char16('(') + UTF8ToUTF16(json) +
330 ASCIIToUTF16(");");
331 tab_contents_->render_view_host()->
332 ExecuteJavascriptInWebFrame(string16(), javascript);
333 }
334
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)335 void DevToolsWindow::Observe(NotificationType type,
336 const NotificationSource& source,
337 const NotificationDetails& details) {
338 if (type == NotificationType::LOAD_STOP && !is_loaded_) {
339 is_loaded_ = true;
340 UpdateTheme();
341 DoAction();
342 AddDevToolsExtensionsToClient();
343 } else if (type == NotificationType::TAB_CLOSING) {
344 if (Source<NavigationController>(source).ptr() ==
345 &tab_contents_->controller()) {
346 // This happens when browser closes all of its tabs as a result
347 // of window.Close event.
348 // Notify manager that this DevToolsClientHost no longer exists and
349 // initiate self-destuct here.
350 NotifyCloseListener();
351 delete this;
352 }
353 } else if (type == NotificationType::BROWSER_THEME_CHANGED) {
354 UpdateTheme();
355 }
356 }
357
ScheduleAction(DevToolsToggleAction action)358 void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) {
359 action_on_load_ = action;
360 if (is_loaded_)
361 DoAction();
362 }
363
DoAction()364 void DevToolsWindow::DoAction() {
365 UpdateFrontendAttachedState();
366 // TODO: these messages should be pushed through the WebKit API instead.
367 switch (action_on_load_) {
368 case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE:
369 tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame(
370 string16(), ASCIIToUTF16("WebInspector.showConsole();"));
371 break;
372 case DEVTOOLS_TOGGLE_ACTION_INSPECT:
373 tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame(
374 string16(), ASCIIToUTF16("WebInspector.toggleSearchingForNode();"));
375 case DEVTOOLS_TOGGLE_ACTION_NONE:
376 // Do nothing.
377 break;
378 default:
379 NOTREACHED();
380 }
381 action_on_load_ = DEVTOOLS_TOGGLE_ACTION_NONE;
382 }
383
SkColorToRGBAString(SkColor color)384 std::string SkColorToRGBAString(SkColor color) {
385 // We convert the alpha using DoubleToString because StringPrintf will use
386 // locale specific formatters (e.g., use , instead of . in German).
387 return StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color),
388 SkColorGetG(color), SkColorGetB(color),
389 base::DoubleToString(SkColorGetA(color) / 255.0).c_str());
390 }
391
GetDevToolsUrl()392 GURL DevToolsWindow::GetDevToolsUrl() {
393 ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_);
394 CHECK(tp);
395
396 SkColor color_toolbar =
397 tp->GetColor(ThemeService::COLOR_TOOLBAR);
398 SkColor color_tab_text =
399 tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT);
400
401 std::string url_string = StringPrintf(
402 "%sdevtools.html?docked=%s&toolbar_color=%s&text_color=%s",
403 chrome::kChromeUIDevToolsURL,
404 docked_ ? "true" : "false",
405 SkColorToRGBAString(color_toolbar).c_str(),
406 SkColorToRGBAString(color_tab_text).c_str());
407 return GURL(url_string);
408 }
409
UpdateTheme()410 void DevToolsWindow::UpdateTheme() {
411 ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_);
412 CHECK(tp);
413
414 SkColor color_toolbar =
415 tp->GetColor(ThemeService::COLOR_TOOLBAR);
416 SkColor color_tab_text =
417 tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT);
418 std::string command = StringPrintf(
419 "WebInspector.setToolbarColors(\"%s\", \"%s\")",
420 SkColorToRGBAString(color_toolbar).c_str(),
421 SkColorToRGBAString(color_tab_text).c_str());
422 tab_contents_->render_view_host()->
423 ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(command));
424 }
425
AddNewContents(TabContents * source,TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)426 void DevToolsWindow::AddNewContents(TabContents* source,
427 TabContents* new_contents,
428 WindowOpenDisposition disposition,
429 const gfx::Rect& initial_pos,
430 bool user_gesture) {
431 inspected_tab_->delegate()->AddNewContents(source,
432 new_contents,
433 disposition,
434 initial_pos,
435 user_gesture);
436 }
437
CanReloadContents(TabContents * source) const438 bool DevToolsWindow::CanReloadContents(TabContents* source) const {
439 return false;
440 }
441
PreHandleKeyboardEvent(const NativeWebKeyboardEvent & event,bool * is_keyboard_shortcut)442 bool DevToolsWindow::PreHandleKeyboardEvent(
443 const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
444 if (docked_) {
445 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
446 if (inspected_window)
447 return inspected_window->PreHandleKeyboardEvent(
448 event, is_keyboard_shortcut);
449 }
450 return false;
451 }
452
HandleKeyboardEvent(const NativeWebKeyboardEvent & event)453 void DevToolsWindow::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
454 if (docked_) {
455 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
456 if (inspected_window)
457 inspected_window->HandleKeyboardEvent(event);
458 }
459 }
460