OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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/views/about_network_dialog.h" | |
6 | |
7 #include "base/message_loop.h" | |
8 #include "base/thread.h" | |
9 #include "base/utf_string_conversions.h" | |
10 #include "chrome/browser/chrome_thread.h" | |
11 #include "net/url_request/url_request.h" | |
12 #include "net/url_request/url_request_job.h" | |
13 #include "net/url_request/url_request_job_tracker.h" | |
14 #include "views/grid_layout.h" | |
15 #include "views/controls/button/text_button.h" | |
16 #include "views/controls/textfield/textfield.h" | |
17 #include "views/standard_layout.h" | |
18 #include "views/window/window.h" | |
19 | |
20 namespace { | |
21 | |
22 // We don't localize this UI since this is a developer-only feature. | |
23 const wchar_t kStartTrackingLabel[] = L"Start tracking"; | |
24 const wchar_t kStopTrackingLabel[] = L"Stop tracking"; | |
25 const wchar_t kShowCurrentLabel[] = L"Show Current"; | |
26 const wchar_t kClearLabel[] = L"Clear"; | |
27 | |
28 // The singleton dialog box. This is non-NULL when a dialog is active so we | |
29 // know not to create a new one. | |
30 AboutNetworkDialog* active_dialog = NULL; | |
31 | |
32 // Returns a string representing the URL, handling the case where the spec | |
33 // is invalid. | |
34 std::wstring StringForURL(const GURL& url) { | |
35 if (url.is_valid()) | |
36 return UTF8ToWide(url.spec()); | |
37 return UTF8ToWide(url.possibly_invalid_spec()) + L" (invalid)"; | |
38 } | |
39 | |
40 std::wstring URLForJob(URLRequestJob* job) { | |
41 URLRequest* request = job->request(); | |
42 if (request) | |
43 return StringForURL(request->url()); | |
44 return std::wstring(L"(orphaned)"); | |
45 } | |
46 | |
47 // JobTracker ------------------------------------------------------------------ | |
48 | |
49 // A JobTracker is allocated to monitor network jobs running on the IO | |
50 // thread. This allows the NetworkStatusView to remain single-threaded. | |
51 class JobTracker : public URLRequestJobTracker::JobObserver, | |
52 public base::RefCountedThreadSafe<JobTracker> { | |
53 public: | |
54 explicit JobTracker(AboutNetworkDialog* view); | |
55 | |
56 // Called by the NetworkStatusView on the main application thread. | |
57 void StartTracking(); | |
58 void StopTracking(); | |
59 void ReportStatus(); | |
60 | |
61 // URLRequestJobTracker::JobObserver methods (called on the IO thread): | |
62 virtual void OnJobAdded(URLRequestJob* job); | |
63 virtual void OnJobRemoved(URLRequestJob* job); | |
64 virtual void OnJobDone(URLRequestJob* job, const URLRequestStatus& status); | |
65 virtual void OnJobRedirect(URLRequestJob* job, const GURL& location, | |
66 int status_code); | |
67 virtual void OnBytesRead(URLRequestJob* job, int byte_count); | |
68 | |
69 // The JobTracker may be deleted after NetworkStatusView is deleted. | |
70 void DetachView() { view_ = NULL; } | |
71 | |
72 private: | |
73 friend class base::RefCountedThreadSafe<JobTracker>; | |
74 | |
75 ~JobTracker(); | |
76 | |
77 void InvokeOnIOThread(void (JobTracker::*method)()); | |
78 | |
79 // Called on the IO thread | |
80 void OnStartTracking(); | |
81 void OnStopTracking(); | |
82 void OnReportStatus(); | |
83 void AppendText(const std::wstring& text); | |
84 | |
85 // Called on the main thread | |
86 void OnAppendText(const std::wstring& text); | |
87 | |
88 AboutNetworkDialog* view_; | |
89 MessageLoop* view_message_loop_; | |
90 }; | |
91 | |
92 // main thread: | |
93 JobTracker::JobTracker(AboutNetworkDialog* view) | |
94 : view_(view), | |
95 view_message_loop_(MessageLoop::current()) { | |
96 } | |
97 | |
98 JobTracker::~JobTracker() { | |
99 } | |
100 | |
101 // main thread: | |
102 void JobTracker::InvokeOnIOThread(void (JobTracker::*m)()) { | |
103 ChromeThread::PostTask( | |
104 ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, m)); | |
105 } | |
106 | |
107 // main thread: | |
108 void JobTracker::StartTracking() { | |
109 DCHECK(MessageLoop::current() == view_message_loop_); | |
110 DCHECK(view_); | |
111 InvokeOnIOThread(&JobTracker::OnStartTracking); | |
112 } | |
113 | |
114 // main thread: | |
115 void JobTracker::StopTracking() { | |
116 DCHECK(MessageLoop::current() == view_message_loop_); | |
117 // The tracker should not be deleted before it is removed from observer | |
118 // list. | |
119 AddRef(); | |
120 InvokeOnIOThread(&JobTracker::OnStopTracking); | |
121 } | |
122 | |
123 // main thread: | |
124 void JobTracker::ReportStatus() { | |
125 DCHECK(MessageLoop::current() == view_message_loop_); | |
126 InvokeOnIOThread(&JobTracker::OnReportStatus); | |
127 } | |
128 | |
129 // main thread: | |
130 void JobTracker::OnAppendText(const std::wstring& text) { | |
131 DCHECK(MessageLoop::current() == view_message_loop_); | |
132 if (view_ && view_->tracking()) | |
133 view_->AppendText(text); | |
134 } | |
135 | |
136 // IO thread: | |
137 void JobTracker::AppendText(const std::wstring& text) { | |
138 DCHECK(MessageLoop::current() != view_message_loop_); | |
139 view_message_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
140 this, &JobTracker::OnAppendText, text)); | |
141 } | |
142 | |
143 // IO thread: | |
144 void JobTracker::OnStartTracking() { | |
145 DCHECK(MessageLoop::current() != view_message_loop_); | |
146 g_url_request_job_tracker.AddObserver(this); | |
147 } | |
148 | |
149 // IO thread: | |
150 void JobTracker::OnStopTracking() { | |
151 DCHECK(MessageLoop::current() != view_message_loop_); | |
152 g_url_request_job_tracker.RemoveObserver(this); | |
153 // Balance the AddRef() in StopTracking() called in main thread. | |
154 Release(); | |
155 } | |
156 | |
157 // IO thread: | |
158 void JobTracker::OnReportStatus() { | |
159 DCHECK(MessageLoop::current() != view_message_loop_); | |
160 | |
161 std::wstring text(L"\r\n===== Active Job Summary =====\r\n"); | |
162 | |
163 URLRequestJobTracker::JobIterator begin_job = | |
164 g_url_request_job_tracker.begin(); | |
165 URLRequestJobTracker::JobIterator end_job = g_url_request_job_tracker.end(); | |
166 int orphaned_count = 0; | |
167 int regular_count = 0; | |
168 for (URLRequestJobTracker::JobIterator cur = begin_job; | |
169 cur != end_job; ++cur) { | |
170 URLRequestJob* job = (*cur); | |
171 URLRequest* request = job->request(); | |
172 if (!request) { | |
173 orphaned_count++; | |
174 continue; | |
175 } | |
176 | |
177 regular_count++; | |
178 | |
179 // active state | |
180 if (job->is_done()) | |
181 text.append(L" Done: "); | |
182 else | |
183 text.append(L" Active: "); | |
184 | |
185 // URL | |
186 text.append(StringForURL(request->url())); | |
187 text.append(L"\r\n"); | |
188 } | |
189 | |
190 if (regular_count == 0) | |
191 text.append(L" (No active jobs)\r\n"); | |
192 | |
193 if (orphaned_count) { | |
194 wchar_t buf[64]; | |
195 swprintf(buf, arraysize(buf), L" %d orphaned jobs\r\n", orphaned_count); | |
196 text.append(buf); | |
197 } | |
198 | |
199 text.append(L"=====\r\n\r\n"); | |
200 AppendText(text); | |
201 } | |
202 | |
203 // IO thread: | |
204 void JobTracker::OnJobAdded(URLRequestJob* job) { | |
205 DCHECK(MessageLoop::current() != view_message_loop_); | |
206 | |
207 std::wstring text(L"+ New job : "); | |
208 text.append(URLForJob(job)); | |
209 text.append(L"\r\n"); | |
210 AppendText(text); | |
211 } | |
212 | |
213 // IO thread: | |
214 void JobTracker::OnJobRemoved(URLRequestJob* job) { | |
215 DCHECK(MessageLoop::current() != view_message_loop_); | |
216 } | |
217 | |
218 // IO thread: | |
219 void JobTracker::OnJobDone(URLRequestJob* job, | |
220 const URLRequestStatus& status) { | |
221 DCHECK(MessageLoop::current() != view_message_loop_); | |
222 | |
223 std::wstring text; | |
224 if (status.is_success()) { | |
225 text.assign(L"- Complete: "); | |
226 } else if (status.status() == URLRequestStatus::CANCELED) { | |
227 text.assign(L"- Canceled: "); | |
228 } else if (status.status() == URLRequestStatus::HANDLED_EXTERNALLY) { | |
229 text.assign(L"- Handled externally: "); | |
230 } else { | |
231 wchar_t buf[32]; | |
232 swprintf(buf, arraysize(buf), L"Failed with %d: ", status.os_error()); | |
233 text.assign(buf); | |
234 } | |
235 | |
236 text.append(URLForJob(job)); | |
237 text.append(L"\r\n"); | |
238 AppendText(text); | |
239 } | |
240 | |
241 // IO thread: | |
242 void JobTracker::OnJobRedirect(URLRequestJob* job, | |
243 const GURL& location, | |
244 int status_code) { | |
245 DCHECK(MessageLoop::current() != view_message_loop_); | |
246 | |
247 std::wstring text(L"- Redirect: "); | |
248 text.append(URLForJob(job)); | |
249 text.append(L"\r\n "); | |
250 | |
251 wchar_t buf[16]; | |
252 swprintf(buf, arraysize(buf), L"(%d) to: ", status_code); | |
253 text.append(buf); | |
254 | |
255 text.append(StringForURL(location)); | |
256 text.append(L"\r\n"); | |
257 AppendText(text); | |
258 } | |
259 | |
260 void JobTracker::OnBytesRead(URLRequestJob* job, int byte_count) { | |
261 } | |
262 | |
263 // The singleton job tracker associated with the dialog. | |
264 JobTracker* tracker = NULL; | |
265 | |
266 } // namespace | |
267 | |
268 // AboutNetworkDialog ---------------------------------------------------------- | |
269 | |
270 AboutNetworkDialog::AboutNetworkDialog() : tracking_(false) { | |
271 SetupControls(); | |
272 tracker = new JobTracker(this); | |
273 tracker->AddRef(); | |
274 } | |
275 | |
276 AboutNetworkDialog::~AboutNetworkDialog() { | |
277 active_dialog = NULL; | |
278 tracker->StopTracking(); | |
279 tracker->Release(); | |
280 tracker = NULL; | |
281 } | |
282 | |
283 // static | |
284 void AboutNetworkDialog::RunDialog() { | |
285 if (!active_dialog) { | |
286 active_dialog = new AboutNetworkDialog; | |
287 views::Window::CreateChromeWindow(NULL, gfx::Rect(), active_dialog)->Show(); | |
288 } else { | |
289 // TOOD(brettw) it would be nice to focus the existing window. | |
290 } | |
291 } | |
292 | |
293 void AboutNetworkDialog::AppendText(const std::wstring& text) { | |
294 text_field_->AppendText(text); | |
295 } | |
296 | |
297 void AboutNetworkDialog::SetupControls() { | |
298 views::GridLayout* layout = CreatePanelGridLayout(this); | |
299 SetLayoutManager(layout); | |
300 | |
301 track_toggle_ = new views::TextButton(this, kStartTrackingLabel); | |
302 show_button_ = new views::TextButton(this, kShowCurrentLabel); | |
303 clear_button_ = new views::TextButton(this, kClearLabel); | |
304 | |
305 text_field_ = new views::Textfield(static_cast<views::Textfield::StyleFlags>( | |
306 views::Textfield::STYLE_MULTILINE)); | |
307 text_field_->SetReadOnly(true); | |
308 | |
309 // TODO(brettw): We may want to add this in the future. It can't be called | |
310 // from here, though, since the hwnd for the field hasn't been created yet. | |
311 // | |
312 // This raises the maximum number of chars from 32K to some large maximum, | |
313 // probably 2GB. 32K is not nearly enough for our use-case. | |
314 // SendMessageW(text_field_->GetNativeComponent(), EM_SETLIMITTEXT, 0, 0); | |
315 | |
316 static const int first_column_set = 1; | |
317 views::ColumnSet* column_set = layout->AddColumnSet(first_column_set); | |
318 column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, | |
319 33.33f, views::GridLayout::FIXED, 0, 0); | |
320 column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, | |
321 33.33f, views::GridLayout::FIXED, 0, 0); | |
322 column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, | |
323 33.33f, views::GridLayout::FIXED, 0, 0); | |
324 | |
325 static const int text_column_set = 2; | |
326 column_set = layout->AddColumnSet(text_column_set); | |
327 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, | |
328 100.0f, | |
329 views::GridLayout::FIXED, 0, 0); | |
330 | |
331 layout->StartRow(0, first_column_set); | |
332 layout->AddView(track_toggle_); | |
333 layout->AddView(show_button_); | |
334 layout->AddView(clear_button_); | |
335 layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); | |
336 layout->StartRow(1.0f, text_column_set); | |
337 layout->AddView(text_field_); | |
338 } | |
339 | |
340 gfx::Size AboutNetworkDialog::GetPreferredSize() { | |
341 return gfx::Size(800, 400); | |
342 } | |
343 | |
344 views::View* AboutNetworkDialog::GetContentsView() { | |
345 return this; | |
346 } | |
347 | |
348 int AboutNetworkDialog::GetDialogButtons() const { | |
349 // Don't want OK or Cancel. | |
350 return 0; | |
351 } | |
352 | |
353 std::wstring AboutNetworkDialog::GetWindowTitle() const { | |
354 return L"about:network"; | |
355 } | |
356 | |
357 bool AboutNetworkDialog::CanResize() const { | |
358 return true; | |
359 } | |
360 | |
361 void AboutNetworkDialog::ButtonPressed( | |
362 views::Button* button, const views::Event& event) { | |
363 if (button == track_toggle_) { | |
364 if (tracking_) { | |
365 track_toggle_->SetText(kStartTrackingLabel); | |
366 tracking_ = false; | |
367 tracker->StopTracking(); | |
368 } else { | |
369 track_toggle_->SetText(kStopTrackingLabel); | |
370 tracking_ = true; | |
371 tracker->StartTracking(); | |
372 } | |
373 track_toggle_->SchedulePaint(); | |
374 } else if (button == show_button_) { | |
375 tracker->ReportStatus(); | |
376 } else if (button == clear_button_) { | |
377 text_field_->SetText(std::wstring()); | |
378 } | |
379 } | |
OLD | NEW |