|
OLD | NEW |
---|---|
(Empty) | |
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 "chrome/browser/extensions/extension_offscreen_tabs_module.h" | |
6 | |
7 #include <algorithm> | |
8 #include <hash_map> | |
9 #include <vector> | |
10 | |
11 #include "base/base64.h" | |
12 #include "base/memory/ref_counted_memory.h" | |
13 #include "base/stl_util.h" | |
14 #include "base/string_number_conversions.h" | |
15 #include "base/string_util.h" | |
16 #include "base/stringprintf.h" | |
17 #include "base/utf_string_conversions.h" | |
18 #include "chrome/browser/extensions/extension_function_dispatcher.h" | |
19 #include "chrome/browser/extensions/extension_host.h" | |
20 #include "chrome/browser/extensions/extension_offscreen_tabs_module_constants.h" | |
21 #include "chrome/browser/extensions/extension_service.h" | |
22 #include "chrome/browser/extensions/extension_tabs_module.h" | |
23 #include "chrome/browser/net/url_fixer_upper.h" | |
24 #include "chrome/browser/profiles/profile.h" | |
25 #include "chrome/browser/sessions/restore_tab_helper.h" | |
26 #include "chrome/browser/tabs/tab_strip_model.h" | |
27 #include "chrome/browser/translate/translate_tab_helper.h" | |
28 #include "chrome/browser/ui/browser.h" | |
29 #include "chrome/browser/ui/browser_list.h" | |
30 #include "chrome/browser/ui/browser_navigator.h" | |
31 #include "chrome/browser/ui/browser_window.h" | |
32 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
33 #include "chrome/browser/ui/window_sizer.h" | |
34 #include "chrome/common/chrome_notification_types.h" | |
35 #include "chrome/common/chrome_switches.h" | |
36 #include "chrome/common/extensions/extension.h" | |
37 #include "chrome/common/extensions/extension_error_utils.h" | |
38 #include "chrome/common/extensions/extension_messages.h" | |
39 #include "chrome/common/pref_names.h" | |
40 #include "chrome/common/url_constants.h" | |
41 #include "content/browser/renderer_host/backing_store.h" | |
42 #include "content/browser/renderer_host/render_view_host.h" | |
43 #include "content/browser/renderer_host/render_view_host_delegate.h" | |
44 #include "content/browser/tab_contents/navigation_entry.h" | |
45 #include "content/browser/tab_contents/tab_contents.h" | |
46 #include "content/browser/tab_contents/tab_contents_view.h" | |
47 #include "content/common/notification_service.h" | |
48 #include "skia/ext/image_operations.h" | |
49 #include "skia/ext/platform_canvas.h" | |
50 #include "third_party/skia/include/core/SkBitmap.h" | |
51 #include "ui/gfx/codec/jpeg_codec.h" | |
52 #include "ui/gfx/codec/png_codec.h" | |
53 | |
54 using WebKit::WebInputEvent; | |
55 | |
56 namespace keys = extension_offscreen_tabs_module_constants; | |
57 | |
58 const int ToDataUrlOffscreenTabFunction::kDefaultQuality = 90; | |
59 | |
60 typedef base::hash_map<int, std::vector<TabContents*> > OffscreenTabsMap; | |
61 OffscreenTabsMap* offscreen_tabs_map = new OffscreenTabsMap(); | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
It's not OK to have a nontrivial initializer for t
alexbost
2011/08/24 20:06:21
Done.
| |
62 | |
63 OffscreenTabsEventManager* event_manager; | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
Must go in the anonymous namespace as well. Also m
alexbost
2011/08/24 20:06:21
Done.
| |
64 | |
65 namespace { | |
66 | |
67 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
68 // Takes |url_string| and returns a GURL which is either valid and absolute | |
69 // or invalid. If |url_string| is not directly interpretable as a valid (it is | |
70 // likely a relative URL) an attempt is made to resolve it. |extension| is | |
71 // provided so it can be resolved relative to its extension base | |
72 // (chrome-extension://<id>/). Using the source frame url would be more correct, | |
73 // but because the api shipped with urls resolved relative to their extension | |
74 // base, we decided it wasn't worth breaking existing extensions to fix. | |
75 GURL ResolvePossiblyRelativeURL(const std::string& url_string, | |
76 const Extension* extension) { | |
77 GURL url = GURL(url_string); | |
78 if (!url.is_valid()) | |
79 url = extension->GetResourceURL(url_string); | |
80 | |
81 return url; | |
82 } | |
83 | |
84 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
85 bool IsCrashURL(const GURL& url) { | |
86 // Check a fixed-up URL, to normalize the scheme and parse hosts correctly. | |
87 GURL fixed_url = | |
88 URLFixerUpper::FixupURL(url.possibly_invalid_spec(), std::string()); | |
89 return (fixed_url.SchemeIs(chrome::kChromeUIScheme) && | |
90 (fixed_url.host() == chrome::kChromeUIBrowserCrashHost || | |
91 fixed_url.host() == chrome::kChromeUICrashHost)); | |
92 } | |
93 | |
94 bool GetOffscreenTabById(const int tab_id, | |
95 TabContentsWrapper** offscreen_tab, | |
96 const TabContents* current_tab_contents, | |
97 std::string* error_message) { | |
98 std::vector<TabContents*> offscreen_tab_contents_vector = | |
99 (*offscreen_tabs_map)[ExtensionTabUtil::GetTabId(current_tab_contents)]; | |
100 | |
101 for (std::vector<TabContents*>::iterator | |
102 it_offscreen_tab_contents = offscreen_tab_contents_vector.begin(); | |
103 it_offscreen_tab_contents != offscreen_tab_contents_vector.end(); | |
104 ++it_offscreen_tab_contents) | |
105 if (ExtensionTabUtil::GetTabId(*it_offscreen_tab_contents) == tab_id) { | |
106 *offscreen_tab = TabContentsWrapper::GetCurrentWrapperForContents( | |
107 *it_offscreen_tab_contents); | |
108 | |
109 return true; | |
110 } | |
111 | |
112 *error_message = ExtensionErrorUtils::FormatErrorMessage( | |
113 keys::kOffscreenTabNotFoundError, base::IntToString(tab_id)); | |
114 | |
115 return false; | |
116 } | |
117 | |
118 bool GetTabById(const int tab_id, | |
119 Browser* browser, | |
120 TabContents** tab_contents, | |
121 std::string* error_message) { | |
122 TabStripModel* tab_strip = browser->tabstrip_model(); | |
123 for (int i = 0; i <tab_strip->count(); ++i) { | |
124 *tab_contents = tab_strip->GetTabContentsAt(i)->tab_contents(); | |
125 | |
126 if (ExtensionTabUtil::GetTabId(*tab_contents) == tab_id) | |
127 return true; | |
128 } | |
129 | |
130 *error_message = ExtensionErrorUtils::FormatErrorMessage( | |
131 keys::kTabNotFoundError, base::IntToString(tab_id)); | |
132 | |
133 return false; | |
134 } | |
135 | |
136 std::string GetTabUrl(const TabContents* tab_contents) { | |
137 return tab_contents->GetURL().spec(); | |
138 } | |
139 | |
140 int GetTabWidth(const TabContents* tab_contents) { | |
141 const TabContentsWrapper* tab = | |
142 TabContentsWrapper::GetCurrentWrapperForContents(tab_contents); | |
143 | |
144 return tab ? tab->view()->GetContainerSize().width() : -1; | |
145 } | |
146 | |
147 int GetTabHeight(const TabContents* tab_contents) { | |
148 const TabContentsWrapper* tab = | |
149 TabContentsWrapper::GetCurrentWrapperForContents(tab_contents); | |
150 | |
151 return tab ? tab->view()->GetContainerSize().height() : -1; | |
152 } | |
153 | |
154 void CloseOffscreenTab(TabContents* tab_contents) { | |
155 // TODO(alexbost): Is this the best way to close an offscreen tab? | |
156 delete TabContentsWrapper::GetCurrentWrapperForContents(tab_contents); | |
157 } | |
158 | |
159 DictionaryValue* CreateOffscreenTabValue(const TabContentsWrapper* tab) { | |
160 DictionaryValue* result = new DictionaryValue(); | |
161 result->SetInteger(keys::kIdKey, | |
162 ExtensionTabUtil::GetTabId(tab->tab_contents())); | |
163 result->SetString(keys::kUrlKey, GetTabUrl(tab->tab_contents())); | |
164 result->SetInteger(keys::kWidthKey, GetTabWidth(tab->tab_contents())); | |
165 result->SetInteger(keys::kHeightKey, GetTabHeight(tab->tab_contents())); | |
166 | |
167 return result; | |
168 } | |
169 | |
170 } // namespace | |
171 | |
172 // Event Manager --------------------------------------------------------------- | |
173 | |
174 void OffscreenTabsEventManager::RegisterForTabNotifications( | |
175 const TabContents* tab_contents) { | |
176 registrar_.Add(this, | |
177 content::NOTIFICATION_NAV_ENTRY_COMMITTED, | |
178 Source<NavigationController>(&tab_contents->controller())); | |
179 | |
180 registrar_.Add(this, | |
181 content::NOTIFICATION_TAB_CONTENTS_DESTROYED, | |
182 Source<TabContents>(tab_contents)); | |
183 } | |
184 | |
185 void OffscreenTabsEventManager::UnregisterForTabNotifications( | |
186 const TabContents* tab_contents) { | |
187 registrar_.Remove(this, | |
188 content::NOTIFICATION_NAV_ENTRY_COMMITTED, | |
189 Source<NavigationController>(&tab_contents->controller())); | |
190 | |
191 registrar_.Remove(this, | |
192 content::NOTIFICATION_TAB_CONTENTS_DESTROYED, | |
193 Source<TabContents>(tab_contents)); | |
194 } | |
195 | |
196 void OffscreenTabsEventManager::Observe(const int type, | |
197 const NotificationSource& source, | |
198 const NotificationDetails& details) { | |
199 TabContents* tab_contents; | |
200 | |
201 if (type == content::NOTIFICATION_TAB_CONTENTS_DESTROYED) | |
202 tab_contents = Source<TabContents>(source).ptr(); | |
203 else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) | |
204 tab_contents = Source<NavigationController>(source).ptr()->tab_contents(); | |
205 else | |
206 return; | |
207 | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
DCHECK(tab_contents)?
alexbost
2011/08/24 20:06:21
Done.
| |
208 // Close all offscreen tabs spawned by this tab | |
209 std::vector<TabContents*> offscreen_tab_contents_vector = | |
210 (*offscreen_tabs_map)[ExtensionTabUtil::GetTabId(tab_contents)]; | |
211 for (std::vector<TabContents*>::iterator | |
212 it_offscreen_tab_contents = offscreen_tab_contents_vector.begin(); | |
213 it_offscreen_tab_contents != offscreen_tab_contents_vector.end(); | |
214 ++it_offscreen_tab_contents) | |
215 CloseOffscreenTab(*it_offscreen_tab_contents); | |
216 | |
217 offscreen_tabs_map->erase(ExtensionTabUtil::GetTabId(tab_contents)); | |
218 | |
219 event_manager->UnregisterForTabNotifications(tab_contents); | |
220 } | |
221 | |
222 // create ---------------------------------------------------------------------- | |
223 | |
224 bool CreateOffscreenTabFunction::RunImpl() { | |
225 Browser* browser = GetCurrentBrowser(); | |
226 if (!browser) { | |
227 error_ = keys::kNoCurrentWindowError; | |
228 return false; | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
Do you need to SendResponse(false) here, and elsew
alexbost
2011/08/24 20:06:21
Looking at extension_tabs_module.cc, they do not c
| |
229 } | |
230 | |
231 DictionaryValue* create_props; | |
232 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &create_props)); | |
233 | |
234 // Url | |
235 std::string url_string; | |
236 GURL url; | |
237 EXTENSION_FUNCTION_VALIDATE( | |
238 create_props->GetString(keys::kUrlKey, &url_string)); | |
239 url = ResolvePossiblyRelativeURL(url_string, GetExtension()); | |
240 if (!url.is_valid()) { | |
241 error_ = ExtensionErrorUtils::FormatErrorMessage( | |
242 keys::kInvalidUrlError, url_string); | |
243 return false; | |
244 } | |
245 if (IsCrashURL(url)) { | |
246 error_ = keys::kNoCrashBrowserError; | |
247 return false; | |
248 } | |
249 | |
250 // Width and height | |
251 gfx::Rect window_bounds; | |
252 bool maximized; | |
253 if (!create_props->HasKey(keys::kWidthKey) || | |
254 !create_props->HasKey(keys::kHeightKey)) | |
255 WindowSizer::GetBrowserWindowBounds(std::string(), gfx::Rect(), | |
256 browser, &window_bounds, | |
257 &maximized); | |
258 | |
259 int width; | |
260 if (create_props->HasKey(keys::kWidthKey)) | |
261 EXTENSION_FUNCTION_VALIDATE( | |
262 create_props->GetInteger(keys::kWidthKey, &width)); | |
263 else | |
264 width = window_bounds.width(); | |
265 | |
266 int height; | |
267 if (create_props->HasKey(keys::kHeightKey)) | |
268 EXTENSION_FUNCTION_VALIDATE( | |
269 create_props->GetInteger(keys::kHeightKey, &height)); | |
270 else | |
271 height = window_bounds.height(); | |
272 | |
273 // New offscreen tab | |
274 TabContents* offscreen_tab_contents = | |
275 new TabContents(profile_, NULL, MSG_ROUTING_NONE, NULL, NULL); | |
276 TabContentsWrapper* offscreen_tab = | |
277 new TabContentsWrapper(offscreen_tab_contents); | |
278 | |
279 // Setting the size starts the renderer | |
280 offscreen_tab_contents->view()->SizeContents(*new gfx::Size(width, height)); | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
This is a memory leak. Just use: ->SizeContents(gf
alexbost
2011/08/24 20:06:21
Done.
| |
281 | |
282 // Navigate | |
283 offscreen_tab_contents->controller().LoadURL( | |
284 url, GURL(), PageTransition::LINK); | |
285 | |
286 // Map the offscreen tab to the tab that spawned it | |
287 TabContents* tab_contents = | |
288 dispatcher()->delegate()->GetAssociatedTabContents(); | |
289 | |
290 (*offscreen_tabs_map)[ExtensionTabUtil::GetTabId(tab_contents)]. | |
291 push_back(offscreen_tab_contents); | |
292 | |
293 // Register for tab notifications | |
294 // if this is the first offscreen tab spawned by the tab | |
295 if ((*offscreen_tabs_map)[ExtensionTabUtil::GetTabId(tab_contents)]. | |
296 size() == 1) { | |
297 if (!event_manager) | |
298 event_manager = new OffscreenTabsEventManager(); | |
299 | |
300 event_manager->RegisterForTabNotifications(tab_contents); | |
301 } | |
302 | |
303 // Callback | |
304 if (has_callback()) { | |
305 result_.reset(CreateOffscreenTabValue(offscreen_tab)); | |
306 SendResponse(true); | |
307 } | |
308 | |
309 return true; | |
310 } | |
311 | |
312 // get ------------------------------------------------------------------------- | |
313 | |
314 bool GetOffscreenTabFunction::RunImpl() { | |
315 int tab_id; | |
316 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
317 TabContentsWrapper* tab = NULL; | |
318 TabContents* current_tab_contents = | |
319 dispatcher()->delegate()->GetAssociatedTabContents(); | |
320 if (!GetOffscreenTabById(tab_id, &tab, current_tab_contents, &error_)) | |
321 return false; | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
Is a SendReponse(false) needed here? If so, also e
alexbost
2011/08/24 20:06:21
See above.
| |
322 | |
323 result_.reset(CreateOffscreenTabValue(tab)); | |
324 SendResponse(true); | |
325 | |
326 return true; | |
327 } | |
328 | |
329 // getAll ---------------------------------------------------------------------- | |
330 | |
331 bool GetAllOffscreenTabFunction::RunImpl() { | |
332 TabContents* current_tab_contents = | |
333 dispatcher()->delegate()->GetAssociatedTabContents(); | |
334 | |
335 std::vector<TabContents*> offscreen_tab_contents_vector = | |
336 (*offscreen_tabs_map)[ExtensionTabUtil::GetTabId(current_tab_contents)]; | |
337 | |
338 ListValue* tab_list = new ListValue(); | |
339 | |
340 for (std::vector<TabContents*>::iterator | |
341 it_offscreen_tab_contents = offscreen_tab_contents_vector.begin(); | |
342 it_offscreen_tab_contents != offscreen_tab_contents_vector.end(); | |
343 ++it_offscreen_tab_contents) | |
344 tab_list->Append(CreateOffscreenTabValue(TabContentsWrapper:: | |
345 GetCurrentWrapperForContents(*it_offscreen_tab_contents))); | |
346 | |
347 result_.reset(tab_list); | |
348 SendResponse(true); | |
349 | |
350 return true; | |
351 } | |
352 | |
353 // remove ---------------------------------------------------------------------- | |
354 | |
355 bool RemoveOffscreenTabFunction::RunImpl() { | |
356 int tab_id; | |
357 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
358 | |
359 for (OffscreenTabsMap::iterator it = offscreen_tabs_map->begin(); | |
360 it != offscreen_tabs_map->end(); ++it) | |
361 for (std::vector<TabContents*>::iterator it_offscreen_tab_contents = | |
362 it->second.begin(); | |
363 it_offscreen_tab_contents != it->second.end(); | |
364 ++it_offscreen_tab_contents) | |
365 if (ExtensionTabUtil::GetTabId(*it_offscreen_tab_contents) == tab_id) { | |
366 TabContents* tab_contents; | |
367 if (!GetTabById( | |
368 it->first, GetCurrentBrowser(), &tab_contents, &error_)) | |
369 return false; | |
370 | |
371 CloseOffscreenTab(*it_offscreen_tab_contents); | |
372 | |
373 it->second.erase(it_offscreen_tab_contents); | |
374 | |
375 // If this was the last offscreen tab for the tab, | |
376 // unregister the tab for notifications and remove it from the map | |
377 if (it->second.empty()) { | |
378 event_manager->UnregisterForTabNotifications(tab_contents); | |
379 offscreen_tabs_map->erase(it); | |
380 } | |
381 | |
382 return true; | |
383 } | |
384 | |
385 error_ = ExtensionErrorUtils::FormatErrorMessage( | |
386 keys::kOffscreenTabNotFoundError, base::IntToString(tab_id)); | |
387 return false; | |
388 } | |
389 | |
390 // sendKeyboardEvent ----------------------------------------------------------- | |
391 | |
392 bool SendKeyboardEventOffscreenTabFunction::RunImpl() { | |
393 int tab_id; | |
394 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
395 TabContentsWrapper* tab = NULL; | |
396 TabContents* current_tab_contents = | |
397 dispatcher()->delegate()->GetAssociatedTabContents(); | |
398 if (!GetOffscreenTabById(tab_id, &tab, current_tab_contents, &error_)) | |
399 return false; | |
Ken Russell (switch to Gerrit)
2011/08/23 22:46:53
Here and below, is any SendResponse(false) needed?
alexbost
2011/08/24 20:06:21
See above.
| |
400 | |
401 // JavaScript KeyboardEvent | |
402 DictionaryValue* dict; | |
403 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &dict)); | |
404 | |
405 NativeWebKeyboardEvent keyboard_event; | |
406 | |
407 // Type | |
408 std::string type; | |
409 if (dict->HasKey(keys::kKeyboardEventTypeKey)) { | |
410 EXTENSION_FUNCTION_VALIDATE( | |
411 dict->GetString(keys::kKeyboardEventTypeKey, &type)); | |
412 } else { | |
413 error_ = keys::kInvalidKeyboardEventObjectError; | |
414 return false; | |
415 } | |
416 | |
417 if (type.compare(keys::kKeyboardEventTypeValueKeypress) == 0) { | |
418 keyboard_event.type = WebInputEvent::Char; | |
419 } else if (type.compare(keys::kKeyboardEventTypeValueKeydown) == 0) { | |
420 keyboard_event.type = WebInputEvent::KeyDown; | |
421 } else if (type.compare(keys::kKeyboardEventTypeValueKeyup) == 0) { | |
422 keyboard_event.type = WebInputEvent::KeyUp; | |
423 } else { | |
424 error_ = keys::kInvalidKeyboardEventObjectError; | |
425 return false; | |
426 } | |
427 | |
428 // Key code | |
429 int key_code; | |
430 if (dict->HasKey(keys::kKeyboardEventKeyCodeKey)) { | |
431 EXTENSION_FUNCTION_VALIDATE( | |
432 dict->GetInteger(keys::kKeyboardEventKeyCodeKey, &key_code)); | |
433 } else { | |
434 error_ = keys::kInvalidKeyboardEventObjectError; | |
435 return false; | |
436 } | |
437 | |
438 keyboard_event.nativeKeyCode = key_code; | |
439 keyboard_event.windowsKeyCode = key_code; | |
440 | |
441 // Key identifier | |
442 keyboard_event.setKeyIdentifierFromWindowsKeyCode(); | |
443 | |
444 // Keypress = type character | |
445 if (type.compare(keys::kKeyboardEventTypeValueKeypress) == 0) { | |
446 int char_code; | |
447 if (dict->HasKey(keys::kKeyboardEventCharCodeKey)) { | |
448 EXTENSION_FUNCTION_VALIDATE( | |
449 dict->GetInteger(keys::kKeyboardEventCharCodeKey, &char_code)); | |
450 keyboard_event.text[0] = char_code; | |
451 keyboard_event.unmodifiedText[0] = char_code; | |
452 } else { | |
453 error_ = keys::kInvalidKeyboardEventObjectError; | |
454 return false; | |
455 } | |
456 } | |
457 | |
458 // Modifiers | |
459 bool alt_key; | |
460 if (dict->HasKey(keys::kKeyboardEventAltKeyKey)) | |
461 EXTENSION_FUNCTION_VALIDATE( | |
462 dict->GetBoolean(keys::kKeyboardEventAltKeyKey, &alt_key)); | |
463 if (alt_key) | |
464 keyboard_event.modifiers |= WebInputEvent::AltKey; | |
465 | |
466 bool ctrl_key; | |
467 if (dict->HasKey(keys::kKeyboardEventCtrlKeyKey)) | |
468 EXTENSION_FUNCTION_VALIDATE( | |
469 dict->GetBoolean(keys::kKeyboardEventCtrlKeyKey, &ctrl_key)); | |
470 if (ctrl_key) | |
471 keyboard_event.modifiers |= WebInputEvent::ControlKey; | |
472 | |
473 bool meta_key = false; | |
474 if (dict->HasKey(keys::kMouseEventMetaKeyKey)) | |
475 EXTENSION_FUNCTION_VALIDATE( | |
476 dict->GetBoolean(keys::kMouseEventMetaKeyKey, &meta_key)); | |
477 if (meta_key) | |
478 keyboard_event.modifiers |= WebInputEvent::MetaKey; | |
479 | |
480 bool shift_key; | |
481 if (dict->HasKey(keys::kKeyboardEventShiftKeyKey)) | |
482 EXTENSION_FUNCTION_VALIDATE( | |
483 dict->GetBoolean(keys::kKeyboardEventShiftKeyKey, &shift_key)); | |
484 if (shift_key) | |
485 keyboard_event.modifiers |= WebInputEvent::ShiftKey; | |
486 | |
487 // Forward event | |
488 tab->tab_contents()->render_view_host()-> | |
489 ForwardKeyboardEvent(keyboard_event); | |
490 | |
491 // Callback | |
492 if (has_callback()) { | |
493 result_.reset(CreateOffscreenTabValue(tab)); | |
494 SendResponse(true); | |
495 } | |
496 | |
497 return true; | |
498 } | |
499 | |
500 // sendMouseEvent -------------------------------------------------------------- | |
501 | |
502 bool SendMouseEventOffscreenTabFunction::RunImpl() { | |
503 int tab_id; | |
504 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
505 TabContentsWrapper* tab = NULL; | |
506 TabContents* current_tab_contents = | |
507 dispatcher()->delegate()->GetAssociatedTabContents(); | |
508 if (!GetOffscreenTabById(tab_id, &tab, current_tab_contents, &error_)) | |
509 return false; | |
510 | |
511 // JavaScript MouseEvent | |
512 DictionaryValue* dict; | |
513 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &dict)); | |
514 | |
515 std::string type; | |
516 if (dict->HasKey(keys::kMouseEventTypeKey)) { | |
517 EXTENSION_FUNCTION_VALIDATE( | |
518 dict->GetString(keys::kMouseEventTypeKey, &type)); | |
519 } else { | |
520 error_ = keys::kInvalidMouseEventObjectError; | |
521 return false; | |
522 } | |
523 | |
524 if (type.compare(keys::kMouseEventTypeValueMousewheel) == 0) { | |
525 WebKit::WebMouseWheelEvent wheel_event; | |
526 | |
527 // Type | |
528 wheel_event.type = WebInputEvent::MouseWheel; | |
529 | |
530 // deltaX, deltaY | |
531 if (dict->HasKey(keys::kMouseEventWheelDeltaXKey) && | |
532 dict->HasKey(keys::kMouseEventWheelDeltaYKey)) { | |
533 int delta_x, delta_y; | |
534 EXTENSION_FUNCTION_VALIDATE( | |
535 dict->GetInteger(keys::kMouseEventWheelDeltaXKey, &delta_x)); | |
536 EXTENSION_FUNCTION_VALIDATE( | |
537 dict->GetInteger(keys::kMouseEventWheelDeltaYKey, &delta_y)); | |
538 wheel_event.deltaX = delta_x; | |
539 wheel_event.deltaY = delta_y; | |
540 } else { | |
541 error_ = keys::kInvalidMouseEventObjectError; | |
542 return false; | |
543 } | |
544 | |
545 // Forward the event | |
546 tab->tab_contents()->render_view_host()->ForwardWheelEvent(wheel_event); | |
547 } else { | |
548 WebKit::WebMouseEvent mouse_event; | |
549 | |
550 // Type | |
551 if (type.compare(keys::kMouseEventTypeValueMousedown) == 0 || | |
552 type.compare(keys::kMouseEventTypeValueClick) == 0) { | |
553 mouse_event.type = WebKit::WebInputEvent::MouseDown; | |
554 } else if (type.compare(keys::kMouseEventTypeValueMouseup) == 0) { | |
555 mouse_event.type = WebKit::WebInputEvent::MouseUp; | |
556 } else if (type.compare(keys::kMouseEventTypeValueMousemove) == 0) { | |
557 mouse_event.type = WebKit::WebInputEvent::MouseMove; | |
558 } else { | |
559 error_ = keys::kInvalidMouseEventObjectError; | |
560 return false; | |
561 } | |
562 | |
563 // Button | |
564 int button; | |
565 if (dict->HasKey(keys::kMouseEventButtonKey)) { | |
566 EXTENSION_FUNCTION_VALIDATE( | |
567 dict->GetInteger(keys::kMouseEventButtonKey, &button)); | |
568 } else { | |
569 error_ = keys::kInvalidMouseEventObjectError; | |
570 return false; | |
571 } | |
572 | |
573 if (button == keys::kMouseEventButtonValueLeft) { | |
574 mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; | |
575 } else if (button == keys::kMouseEventButtonValueMiddle) { | |
576 mouse_event.button = WebKit::WebMouseEvent::ButtonMiddle; | |
577 } else if (button == keys::kMouseEventButtonValueRight) { | |
578 mouse_event.button = WebKit::WebMouseEvent::ButtonRight; | |
579 } else { | |
580 error_ = keys::kInvalidMouseEventObjectError; | |
581 return false; | |
582 } | |
583 | |
584 // x, y | |
585 if (HasOptionalArgument(2) && HasOptionalArgument(3)) { | |
586 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(2, &mouse_event.x)); | |
587 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(3, &mouse_event.y)); | |
588 } else { | |
589 error_ = keys::kNoMouseCoordinatesError; | |
590 return false; | |
591 } | |
592 | |
593 // Modifiers | |
594 bool alt_key = false; | |
595 if (dict->HasKey(keys::kMouseEventAltKeyKey)) | |
596 EXTENSION_FUNCTION_VALIDATE( | |
597 dict->GetBoolean(keys::kMouseEventAltKeyKey, &alt_key)); | |
598 if (alt_key) | |
599 mouse_event.modifiers |= WebInputEvent::AltKey; | |
600 | |
601 bool ctrl_key = false; | |
602 if (dict->HasKey(keys::kMouseEventCtrlKeyKey)) | |
603 EXTENSION_FUNCTION_VALIDATE( | |
604 dict->GetBoolean(keys::kMouseEventCtrlKeyKey, &ctrl_key)); | |
605 if (ctrl_key) | |
606 mouse_event.modifiers |= WebInputEvent::ControlKey; | |
607 | |
608 bool meta_key = false; | |
609 if (dict->HasKey(keys::kMouseEventMetaKeyKey)) | |
610 EXTENSION_FUNCTION_VALIDATE( | |
611 dict->GetBoolean(keys::kMouseEventMetaKeyKey, &meta_key)); | |
612 if (meta_key) | |
613 mouse_event.modifiers |= WebInputEvent::MetaKey; | |
614 | |
615 bool shift_key = false; | |
616 if (dict->HasKey(keys::kMouseEventShiftKeyKey)) | |
617 EXTENSION_FUNCTION_VALIDATE( | |
618 dict->GetBoolean(keys::kMouseEventShiftKeyKey, &shift_key)); | |
619 if (shift_key) | |
620 mouse_event.modifiers |= WebInputEvent::ShiftKey; | |
621 | |
622 // Click count | |
623 mouse_event.clickCount = 1; | |
624 | |
625 // Forward the event | |
626 tab->tab_contents()->render_view_host()->ForwardMouseEvent(mouse_event); | |
627 | |
628 // If the event is a click, | |
629 // fire a mouseup event in addition to the mousedown | |
630 if (type.compare(keys::kMouseEventTypeValueClick) == 0) { | |
631 mouse_event.type = WebKit::WebInputEvent::MouseUp; | |
632 tab->tab_contents()->render_view_host()->ForwardMouseEvent(mouse_event); | |
633 } | |
634 } | |
635 | |
636 // Callback | |
637 if (has_callback()) { | |
638 result_.reset(CreateOffscreenTabValue(tab)); | |
639 SendResponse(true); | |
640 } | |
641 | |
642 return true; | |
643 } | |
644 | |
645 // toDataUrl ------------------------------------------------------------------- | |
646 | |
647 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
648 // TODO(alexbost): We want to optimize this function in order to get more image | |
649 // updates on the browser side. One improvement would be to implement another | |
650 // hash map to get offscreen tabs in O(1). | |
651 bool ToDataUrlOffscreenTabFunction::RunImpl() { | |
652 int tab_id; | |
653 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
654 TabContentsWrapper* tab = NULL; | |
655 TabContents* current_tab_contents = | |
656 dispatcher()->delegate()->GetAssociatedTabContents(); | |
657 if (!GetOffscreenTabById(tab_id, &tab, current_tab_contents, &error_)) | |
658 return false; | |
659 | |
660 image_format_ = FORMAT_JPEG; // Default format is JPEG. | |
661 image_quality_ = kDefaultQuality; // Default quality setting. | |
662 | |
663 if (HasOptionalArgument(1)) { | |
664 DictionaryValue* options; | |
665 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options)); | |
666 | |
667 if (options->HasKey(keys::kFormatKey)) { | |
668 std::string format; | |
669 EXTENSION_FUNCTION_VALIDATE( | |
670 options->GetString(keys::kFormatKey, &format)); | |
671 | |
672 if (format == keys::kFormatValueJpeg) { | |
673 image_format_ = FORMAT_JPEG; | |
674 } else if (format == keys::kFormatValuePng) { | |
675 image_format_ = FORMAT_PNG; | |
676 } else { | |
677 // Schema validation should make this unreachable. | |
678 EXTENSION_FUNCTION_VALIDATE(0); | |
679 } | |
680 } | |
681 | |
682 if (options->HasKey(keys::kQualityKey)) { | |
683 EXTENSION_FUNCTION_VALIDATE( | |
684 options->GetInteger(keys::kQualityKey, &image_quality_)); | |
685 } | |
686 } | |
687 | |
688 // captureVisibleTab() can return an image containing sensitive information | |
689 // that the browser would otherwise protect. Ensure the extension has | |
690 // permission to do this. | |
691 if (!GetExtension()-> | |
692 CanCaptureVisiblePage(tab->tab_contents()->GetURL(), &error_)) | |
693 return false; | |
694 | |
695 // The backing store approach does not work on Mac | |
696 // TODO(alexbost): Test on Windows | |
697 #if !defined(OS_MACOSX) | |
698 RenderViewHost* render_view_host = tab->tab_contents()->render_view_host(); | |
699 | |
700 // If a backing store is cached for the tab we want to capture, | |
701 // and it can be copied into a bitmap, then use it to generate the image. | |
702 BackingStore* backing_store = render_view_host->GetBackingStore(false); | |
703 if (backing_store && CaptureSnapshotFromBackingStore(backing_store)) | |
704 return true; | |
705 #endif | |
706 | |
707 // Ask the renderer for a snapshot of the tab. | |
708 tab->CaptureSnapshot(); | |
709 registrar_.Add(this, | |
710 chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN, | |
711 Source<TabContentsWrapper>(tab)); | |
712 | |
713 AddRef(); // Balanced in ToDataUrlOffscreenTabFunction::Observe(). | |
714 | |
715 return true; | |
716 } | |
717 | |
718 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
719 // Build the image of a tab's contents out of a backing store. | |
720 // This may fail if we can not copy a backing store into a bitmap. | |
721 // For example, some uncommon X11 visual modes are not supported by | |
722 // CopyFromBackingStore(). | |
723 bool ToDataUrlOffscreenTabFunction::CaptureSnapshotFromBackingStore( | |
724 BackingStore* backing_store) { | |
725 | |
726 skia::PlatformCanvas temp_canvas; | |
727 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), | |
728 &temp_canvas)) { | |
729 return false; | |
730 } | |
731 VLOG(1) << "captureVisibleTab() got image from backing store."; | |
732 | |
733 SendResultFromBitmap( | |
734 skia::GetTopDevice(temp_canvas)->accessBitmap(false)); | |
735 return true; | |
736 } | |
737 | |
738 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
739 void ToDataUrlOffscreenTabFunction::Observe(const int type, | |
740 const NotificationSource& source, | |
741 const NotificationDetails& details) { | |
742 DCHECK(type == chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN); | |
743 | |
744 const SkBitmap *screen_capture = Details<const SkBitmap>(details).ptr(); | |
745 const bool error = screen_capture->empty(); | |
746 | |
747 if (error) { | |
748 error_ = keys::kInternalVisibleTabCaptureError; | |
749 SendResponse(false); | |
750 } else { | |
751 VLOG(1) << "Got image from renderer."; | |
752 SendResultFromBitmap(*screen_capture); | |
753 } | |
754 | |
755 Release(); // Balanced in ToDataUrlOffscreenTabFunction::RunImpl(). | |
756 } | |
757 | |
758 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
759 // Turn a bitmap of the screen into an image, set that image as the result, | |
760 // and call SendResponse(). | |
761 void ToDataUrlOffscreenTabFunction::SendResultFromBitmap( | |
762 const SkBitmap& screen_capture) { | |
763 std::vector<unsigned char> data; | |
764 SkAutoLockPixels screen_capture_lock(screen_capture); | |
765 bool encoded = false; | |
766 std::string mime_type; | |
767 switch (image_format_) { | |
768 case FORMAT_JPEG: | |
769 encoded = gfx::JPEGCodec::Encode( | |
770 reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)), | |
771 gfx::JPEGCodec::FORMAT_SkBitmap, | |
772 screen_capture.width(), | |
773 screen_capture.height(), | |
774 static_cast<int>(screen_capture.rowBytes()), | |
775 image_quality_, | |
776 &data); | |
777 mime_type = keys::kMimeTypeJpeg; | |
778 break; | |
779 case FORMAT_PNG: | |
780 encoded = gfx::PNGCodec::EncodeBGRASkBitmap( | |
781 screen_capture, | |
782 true, // Discard transparency. | |
783 &data); | |
784 mime_type = keys::kMimeTypePng; | |
785 break; | |
786 default: | |
787 NOTREACHED() << "Invalid image format."; | |
788 } | |
789 | |
790 if (!encoded) { | |
791 error_ = keys::kInternalVisibleTabCaptureError; | |
792 SendResponse(false); | |
793 return; | |
794 } | |
795 | |
796 std::string base64_result; | |
797 base::StringPiece stream_as_string( | |
798 reinterpret_cast<const char*>(vector_as_array(&data)), data.size()); | |
799 | |
800 base::Base64Encode(stream_as_string, &base64_result); | |
801 base64_result.insert(0, base::StringPrintf("data:%s;base64,", | |
802 mime_type.c_str())); | |
803 result_.reset(new StringValue(base64_result)); | |
804 SendResponse(true); | |
805 } | |
806 | |
807 // update ---------------------------------------------------------------------- | |
808 | |
809 UpdateOffscreenTabFunction::UpdateOffscreenTabFunction() { | |
810 } | |
811 | |
812 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
813 bool UpdateOffscreenTabFunction::RunImpl() { | |
814 int tab_id; | |
815 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
816 TabContentsWrapper* tab = NULL; | |
817 TabContents* current_tab_contents = | |
818 dispatcher()->delegate()->GetAssociatedTabContents(); | |
819 if (!GetOffscreenTabById(tab_id, &tab, current_tab_contents, &error_)) | |
820 return false; | |
821 | |
822 DictionaryValue* update_props; | |
823 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); | |
824 | |
825 // Url | |
826 if (update_props->HasKey(keys::kUrlKey)) { | |
827 std::string url_string; | |
828 GURL url; | |
829 EXTENSION_FUNCTION_VALIDATE( | |
830 update_props->GetString(keys::kUrlKey, &url_string)); | |
831 url = ResolvePossiblyRelativeURL(url_string, GetExtension()); | |
832 if (!url.is_valid()) { | |
833 error_ = ExtensionErrorUtils::FormatErrorMessage( | |
834 keys::kInvalidUrlError, url_string); | |
835 return false; | |
836 } | |
837 if (IsCrashURL(url)) { | |
838 error_ = keys::kNoCrashBrowserError; | |
839 return false; | |
840 } | |
841 | |
842 // JavaScript URLs can do the same kinds of things as cross-origin XHR, so | |
843 // we need to check host permissions before allowing them. | |
844 if (url.SchemeIs(chrome::kJavaScriptScheme)) { | |
845 if (!GetExtension()->CanExecuteScriptOnPage( | |
846 tab->tab_contents()->GetURL(), NULL, &error_)) { | |
847 return false; | |
848 } | |
849 | |
850 ExtensionMsg_ExecuteCode_Params params; | |
851 params.request_id = request_id(); | |
852 params.extension_id = extension_id(); | |
853 params.is_javascript = true; | |
854 params.code = url.path(); | |
855 params.all_frames = false; | |
856 params.in_main_world = true; | |
857 | |
858 RenderViewHost* render_view_host = | |
859 tab->tab_contents()->render_view_host(); | |
860 render_view_host->Send( | |
861 new ExtensionMsg_ExecuteCode(render_view_host->routing_id(), | |
862 params)); | |
863 | |
864 Observe(tab->tab_contents()); | |
865 AddRef(); // balanced in Observe() | |
866 | |
867 return true; | |
868 } | |
869 | |
870 tab->tab_contents()->controller(). | |
871 LoadURL(url, GURL(), PageTransition::LINK); | |
872 | |
873 // The URL of a tab contents never actually changes to a JavaScript URL, so | |
874 // this check only makes sense in other cases. | |
875 if (!url.SchemeIs(chrome::kJavaScriptScheme)) | |
876 DCHECK_EQ(url.spec(), tab->tab_contents()->GetURL().spec()); | |
877 } | |
878 | |
879 // Width and height | |
880 if (update_props->HasKey(keys::kWidthKey) || | |
881 update_props->HasKey(keys::kHeightKey)) { | |
882 int width; | |
883 if (update_props->HasKey(keys::kWidthKey)) | |
884 EXTENSION_FUNCTION_VALIDATE( | |
885 update_props->GetInteger(keys::kWidthKey, &width)); | |
886 else | |
887 GetTabWidth(tab->tab_contents()); | |
888 | |
889 int height; | |
890 if (update_props->HasKey(keys::kHeightKey)) | |
891 EXTENSION_FUNCTION_VALIDATE( | |
892 update_props->GetInteger(keys::kHeightKey, &height)); | |
893 else | |
894 GetTabHeight(tab->tab_contents()); | |
895 | |
896 tab->tab_contents()->view()->SizeContents(*new gfx::Size(width, height)); | |
897 } | |
898 | |
899 // Callback | |
900 if (has_callback()) { | |
901 result_.reset(CreateOffscreenTabValue(tab)); | |
902 SendResponse(true); | |
903 } | |
904 | |
905 return true; | |
906 } | |
907 | |
908 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
909 bool UpdateOffscreenTabFunction::OnMessageReceived( | |
910 const IPC::Message& message) { | |
911 if (message.type() != ExtensionHostMsg_ExecuteCodeFinished::ID) | |
912 return false; | |
913 | |
914 int message_request_id; | |
915 void* iter = NULL; | |
916 if (!message.ReadInt(&iter, &message_request_id)) { | |
917 NOTREACHED() << "malformed extension message"; | |
918 return true; | |
919 } | |
920 | |
921 if (message_request_id != request_id()) | |
922 return false; | |
923 | |
924 IPC_BEGIN_MESSAGE_MAP(UpdateOffscreenTabFunction, message) | |
925 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExecuteCodeFinished, | |
926 OnExecuteCodeFinished) | |
927 IPC_END_MESSAGE_MAP() | |
928 return true; | |
929 } | |
930 | |
931 // TODO(alexbost): Needs refactoring. Similar method in extension_tabs_module. | |
932 void UpdateOffscreenTabFunction::OnExecuteCodeFinished(int request_id, | |
933 bool success, | |
934 const std::string& error) { | |
935 if (!error.empty()) { | |
936 CHECK(!success); | |
937 error_ = error; | |
938 } | |
939 | |
940 SendResponse(success); | |
941 | |
942 Observe(NULL); | |
943 Release(); // balanced in Execute() | |
944 } | |
945 | |
946 | |
OLD | NEW |