OLD | NEW |
| (Empty) |
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/extensions/api/tabs/tabs_api.h" | |
6 | |
7 #include <algorithm> | |
8 #include <limits> | |
9 #include <vector> | |
10 | |
11 #include "base/base64.h" | |
12 #include "base/bind.h" | |
13 #include "base/command_line.h" | |
14 #include "base/logging.h" | |
15 #include "base/memory/ref_counted_memory.h" | |
16 #include "base/message_loop.h" | |
17 #include "base/stl_util.h" | |
18 #include "base/string16.h" | |
19 #include "base/string_number_conversions.h" | |
20 #include "base/string_util.h" | |
21 #include "base/stringprintf.h" | |
22 #include "base/utf_string_conversions.h" | |
23 #include "chrome/browser/extensions/api/tabs/tabs_constants.h" | |
24 #include "chrome/browser/extensions/extension_function_dispatcher.h" | |
25 #include "chrome/browser/extensions/extension_function_util.h" | |
26 #include "chrome/browser/extensions/extension_host.h" | |
27 #include "chrome/browser/extensions/extension_service.h" | |
28 #include "chrome/browser/extensions/extension_tab_util.h" | |
29 #include "chrome/browser/extensions/file_reader.h" | |
30 #include "chrome/browser/extensions/script_executor.h" | |
31 #include "chrome/browser/extensions/tab_helper.h" | |
32 #include "chrome/browser/extensions/window_controller.h" | |
33 #include "chrome/browser/extensions/window_controller_list.h" | |
34 #include "chrome/browser/prefs/incognito_mode_prefs.h" | |
35 #include "chrome/browser/prefs/pref_service.h" | |
36 #include "chrome/browser/profiles/profile.h" | |
37 #include "chrome/browser/translate/translate_tab_helper.h" | |
38 #include "chrome/browser/ui/browser.h" | |
39 #include "chrome/browser/ui/browser_commands.h" | |
40 #include "chrome/browser/ui/browser_finder.h" | |
41 #include "chrome/browser/ui/browser_list.h" | |
42 #include "chrome/browser/ui/browser_navigator.h" | |
43 #include "chrome/browser/ui/browser_tabstrip.h" | |
44 #include "chrome/browser/ui/browser_window.h" | |
45 #include "chrome/browser/ui/extensions/shell_window.h" | |
46 #include "chrome/browser/ui/host_desktop.h" | |
47 #include "chrome/browser/ui/panels/panel_manager.h" | |
48 #include "chrome/browser/ui/snapshot_tab_helper.h" | |
49 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
50 #include "chrome/browser/ui/window_sizer/window_sizer.h" | |
51 #include "chrome/browser/web_applications/web_app.h" | |
52 #include "chrome/common/chrome_notification_types.h" | |
53 #include "chrome/common/chrome_switches.h" | |
54 #include "chrome/common/extensions/api/tabs.h" | |
55 #include "chrome/common/extensions/api/windows.h" | |
56 #include "chrome/common/extensions/extension.h" | |
57 #include "chrome/common/extensions/extension_constants.h" | |
58 #include "chrome/common/extensions/extension_file_util.h" | |
59 #include "chrome/common/extensions/extension_l10n_util.h" | |
60 #include "chrome/common/extensions/extension_manifest_constants.h" | |
61 #include "chrome/common/extensions/extension_messages.h" | |
62 #include "chrome/common/extensions/message_bundle.h" | |
63 #include "chrome/common/extensions/user_script.h" | |
64 #include "chrome/common/pref_names.h" | |
65 #include "chrome/common/url_constants.h" | |
66 #include "content/public/browser/navigation_controller.h" | |
67 #include "content/public/browser/navigation_entry.h" | |
68 #include "content/public/browser/notification_details.h" | |
69 #include "content/public/browser/notification_source.h" | |
70 #include "content/public/browser/render_view_host.h" | |
71 #include "content/public/browser/render_widget_host_view.h" | |
72 #include "content/public/browser/web_contents.h" | |
73 #include "content/public/browser/web_contents_view.h" | |
74 #include "content/public/common/url_constants.h" | |
75 #include "extensions/common/constants.h" | |
76 #include "extensions/common/error_utils.h" | |
77 #include "skia/ext/image_operations.h" | |
78 #include "skia/ext/platform_canvas.h" | |
79 #include "third_party/skia/include/core/SkBitmap.h" | |
80 #include "ui/base/models/list_selection_model.h" | |
81 #include "ui/base/ui_base_types.h" | |
82 #include "ui/gfx/codec/jpeg_codec.h" | |
83 #include "ui/gfx/codec/png_codec.h" | |
84 | |
85 #if defined(OS_WIN) | |
86 #include "win8/util/win8_util.h" | |
87 #endif // OS_WIN | |
88 | |
89 namespace Get = extensions::api::windows::Get; | |
90 namespace GetAll = extensions::api::windows::GetAll; | |
91 namespace GetCurrent = extensions::api::windows::GetCurrent; | |
92 namespace GetLastFocused = extensions::api::windows::GetLastFocused; | |
93 namespace errors = extension_manifest_errors; | |
94 namespace keys = extensions::tabs_constants; | |
95 | |
96 using content::BrowserThread; | |
97 using content::NavigationController; | |
98 using content::NavigationEntry; | |
99 using content::OpenURLParams; | |
100 using content::Referrer; | |
101 using content::RenderViewHost; | |
102 using content::WebContents; | |
103 using extensions::ErrorUtils; | |
104 using extensions::ScriptExecutor; | |
105 using extensions::UserScript; | |
106 using extensions::WindowController; | |
107 using extensions::WindowControllerList; | |
108 using extensions::api::tabs::InjectDetails; | |
109 | |
110 const int TabsCaptureVisibleTabFunction::kDefaultQuality = 90; | |
111 | |
112 namespace { | |
113 | |
114 // |error_message| can optionally be passed in a will be set with an appropriate | |
115 // message if the window cannot be found by id. | |
116 Browser* GetBrowserInProfileWithId(Profile* profile, | |
117 const int window_id, | |
118 bool include_incognito, | |
119 std::string* error_message) { | |
120 Profile* incognito_profile = | |
121 include_incognito && profile->HasOffTheRecordProfile() ? | |
122 profile->GetOffTheRecordProfile() : NULL; | |
123 for (BrowserList::const_iterator browser = BrowserList::begin(); | |
124 browser != BrowserList::end(); ++browser) { | |
125 if (((*browser)->profile() == profile || | |
126 (*browser)->profile() == incognito_profile) && | |
127 ExtensionTabUtil::GetWindowId(*browser) == window_id && | |
128 ((*browser)->window())) | |
129 return *browser; | |
130 } | |
131 | |
132 if (error_message) | |
133 *error_message = ErrorUtils::FormatErrorMessage( | |
134 keys::kWindowNotFoundError, base::IntToString(window_id)); | |
135 | |
136 return NULL; | |
137 } | |
138 | |
139 bool GetBrowserFromWindowID( | |
140 UIThreadExtensionFunction* function, int window_id, Browser** browser) { | |
141 if (window_id == extension_misc::kCurrentWindowId) { | |
142 *browser = function->GetCurrentBrowser(); | |
143 if (!(*browser) || !(*browser)->window()) { | |
144 function->SetError(keys::kNoCurrentWindowError); | |
145 return false; | |
146 } | |
147 } else { | |
148 std::string error; | |
149 *browser = GetBrowserInProfileWithId( | |
150 function->profile(), window_id, function->include_incognito(), &error); | |
151 if (!*browser) { | |
152 function->SetError(error); | |
153 return false; | |
154 } | |
155 } | |
156 return true; | |
157 } | |
158 | |
159 bool GetWindowFromWindowID(UIThreadExtensionFunction* function, | |
160 int window_id, | |
161 WindowController** controller) { | |
162 if (window_id == extension_misc::kCurrentWindowId) { | |
163 WindowController* extension_window_controller = | |
164 function->dispatcher()->delegate()->GetExtensionWindowController(); | |
165 // If there is a window controller associated with this extension, use that. | |
166 if (extension_window_controller) { | |
167 *controller = extension_window_controller; | |
168 } else { | |
169 // Otherwise get the focused or most recently added window. | |
170 *controller = WindowControllerList::GetInstance()-> | |
171 CurrentWindowForFunction(function); | |
172 } | |
173 if (!(*controller)) { | |
174 function->SetError(keys::kNoCurrentWindowError); | |
175 return false; | |
176 } | |
177 } else { | |
178 *controller = WindowControllerList::GetInstance()-> | |
179 FindWindowForFunctionById(function, window_id); | |
180 if (!(*controller)) { | |
181 function->SetError(ErrorUtils::FormatErrorMessage( | |
182 keys::kWindowNotFoundError, base::IntToString(window_id))); | |
183 return false; | |
184 } | |
185 } | |
186 return true; | |
187 } | |
188 | |
189 // |error_message| can optionally be passed in and will be set with an | |
190 // appropriate message if the tab cannot be found by id. | |
191 bool GetTabById(int tab_id, | |
192 Profile* profile, | |
193 bool include_incognito, | |
194 Browser** browser, | |
195 TabStripModel** tab_strip, | |
196 content::WebContents** contents, | |
197 int* tab_index, | |
198 std::string* error_message) { | |
199 if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito, | |
200 browser, tab_strip, contents, tab_index)) | |
201 return true; | |
202 | |
203 if (error_message) | |
204 *error_message = ErrorUtils::FormatErrorMessage( | |
205 keys::kTabNotFoundError, base::IntToString(tab_id)); | |
206 | |
207 return false; | |
208 } | |
209 | |
210 // A three state enum to distinguish between when a boolean query argument is | |
211 // set or not. | |
212 enum QueryArg { | |
213 NOT_SET = -1, | |
214 MATCH_FALSE, | |
215 MATCH_TRUE | |
216 }; | |
217 | |
218 bool MatchesQueryArg(QueryArg arg, bool value) { | |
219 if (arg == NOT_SET) | |
220 return true; | |
221 | |
222 return (arg == MATCH_TRUE && value) || (arg == MATCH_FALSE && !value); | |
223 } | |
224 | |
225 QueryArg ParseBoolQueryArg(base::DictionaryValue* query, const char* key) { | |
226 if (query->HasKey(key)) { | |
227 bool value = false; | |
228 CHECK(query->GetBoolean(key, &value)); | |
229 return value ? MATCH_TRUE : MATCH_FALSE; | |
230 } | |
231 return NOT_SET; | |
232 } | |
233 | |
234 Browser* CreateBrowserWindow(const Browser::CreateParams& params, | |
235 Profile* profile, | |
236 const std::string& extension_id) { | |
237 bool use_existing_browser_window = false; | |
238 | |
239 #if defined(OS_WIN) | |
240 // In windows 8 metro mode we don't allow windows to be created. | |
241 if (win8::IsSingleWindowMetroMode()) | |
242 use_existing_browser_window = true; | |
243 #endif // OS_WIN | |
244 | |
245 Browser* new_window = NULL; | |
246 if (use_existing_browser_window) | |
247 // The false parameter passed below is to ensure that we find a browser | |
248 // object matching the profile passed in, instead of the original profile | |
249 new_window = chrome::FindTabbedBrowser(profile, false, | |
250 params.host_desktop_type); | |
251 | |
252 if (!new_window) | |
253 new_window = new Browser(params); | |
254 return new_window; | |
255 } | |
256 | |
257 } // namespace | |
258 | |
259 // Windows --------------------------------------------------------------------- | |
260 | |
261 bool WindowsGetFunction::RunImpl() { | |
262 scoped_ptr<Get::Params> params(Get::Params::Create(*args_)); | |
263 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
264 | |
265 bool populate_tabs = false; | |
266 if (params->get_info.get() && params->get_info->populate.get()) | |
267 populate_tabs = *params->get_info->populate; | |
268 | |
269 WindowController* controller; | |
270 if (!GetWindowFromWindowID(this, params->window_id, &controller)) | |
271 return false; | |
272 | |
273 if (populate_tabs) | |
274 SetResult(controller->CreateWindowValueWithTabs(GetExtension())); | |
275 else | |
276 SetResult(controller->CreateWindowValue()); | |
277 return true; | |
278 } | |
279 | |
280 bool WindowsGetCurrentFunction::RunImpl() { | |
281 scoped_ptr<GetCurrent::Params> params(GetCurrent::Params::Create(*args_)); | |
282 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
283 | |
284 bool populate_tabs = false; | |
285 if (params->get_info.get() && params->get_info->populate.get()) | |
286 populate_tabs = *params->get_info->populate; | |
287 | |
288 WindowController* controller; | |
289 if (!GetWindowFromWindowID(this, | |
290 extension_misc::kCurrentWindowId, | |
291 &controller)) { | |
292 return false; | |
293 } | |
294 if (populate_tabs) | |
295 SetResult(controller->CreateWindowValueWithTabs(GetExtension())); | |
296 else | |
297 SetResult(controller->CreateWindowValue()); | |
298 return true; | |
299 } | |
300 | |
301 bool WindowsGetLastFocusedFunction::RunImpl() { | |
302 scoped_ptr<GetLastFocused::Params> params( | |
303 GetLastFocused::Params::Create(*args_)); | |
304 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
305 | |
306 bool populate_tabs = false; | |
307 if (params->get_info.get() && params->get_info->populate.get()) | |
308 populate_tabs = *params->get_info->populate; | |
309 | |
310 // Note: currently this returns the last active browser. If we decide to | |
311 // include other window types (e.g. panels), we will need to add logic to | |
312 // WindowControllerList that mirrors the active behavior of BrowserList. | |
313 Browser* browser = chrome::FindAnyBrowser( | |
314 profile(), include_incognito(), chrome::GetActiveDesktop()); | |
315 if (!browser || !browser->window()) { | |
316 error_ = keys::kNoLastFocusedWindowError; | |
317 return false; | |
318 } | |
319 WindowController* controller = | |
320 browser->extension_window_controller(); | |
321 if (populate_tabs) | |
322 SetResult(controller->CreateWindowValueWithTabs(GetExtension())); | |
323 else | |
324 SetResult(controller->CreateWindowValue()); | |
325 return true; | |
326 } | |
327 | |
328 bool WindowsGetAllFunction::RunImpl() { | |
329 scoped_ptr<GetAll::Params> params(GetAll::Params::Create(*args_)); | |
330 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
331 | |
332 bool populate_tabs = false; | |
333 if (params->get_info.get() && params->get_info->populate.get()) | |
334 populate_tabs = *params->get_info->populate; | |
335 | |
336 ListValue* window_list = new ListValue(); | |
337 const WindowControllerList::ControllerList& windows = | |
338 WindowControllerList::GetInstance()->windows(); | |
339 for (WindowControllerList::ControllerList::const_iterator iter = | |
340 windows.begin(); | |
341 iter != windows.end(); ++iter) { | |
342 if (!this->CanOperateOnWindow(*iter)) | |
343 continue; | |
344 if (populate_tabs) | |
345 window_list->Append((*iter)->CreateWindowValueWithTabs(GetExtension())); | |
346 else | |
347 window_list->Append((*iter)->CreateWindowValue()); | |
348 } | |
349 SetResult(window_list); | |
350 return true; | |
351 } | |
352 | |
353 bool WindowsCreateFunction::ShouldOpenIncognitoWindow( | |
354 const base::DictionaryValue* args, | |
355 std::vector<GURL>* urls, | |
356 bool* is_error) { | |
357 *is_error = false; | |
358 const IncognitoModePrefs::Availability incognito_availability = | |
359 IncognitoModePrefs::GetAvailability(profile_->GetPrefs()); | |
360 bool incognito = false; | |
361 if (args && args->HasKey(keys::kIncognitoKey)) { | |
362 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey, | |
363 &incognito)); | |
364 if (incognito && incognito_availability == IncognitoModePrefs::DISABLED) { | |
365 error_ = keys::kIncognitoModeIsDisabled; | |
366 *is_error = true; | |
367 return false; | |
368 } | |
369 if (!incognito && incognito_availability == IncognitoModePrefs::FORCED) { | |
370 error_ = keys::kIncognitoModeIsForced; | |
371 *is_error = true; | |
372 return false; | |
373 } | |
374 } else if (incognito_availability == IncognitoModePrefs::FORCED) { | |
375 // If incognito argument is not specified explicitly, we default to | |
376 // incognito when forced so by policy. | |
377 incognito = true; | |
378 } | |
379 | |
380 // Remove all URLs that are not allowed in an incognito session. Note that a | |
381 // ChromeOS guest session is not considered incognito in this case. | |
382 if (incognito && !profile_->IsGuestSession()) { | |
383 std::string first_url_erased; | |
384 for (size_t i = 0; i < urls->size();) { | |
385 if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) { | |
386 i++; | |
387 } else { | |
388 if (first_url_erased.empty()) | |
389 first_url_erased = (*urls)[i].spec(); | |
390 urls->erase(urls->begin() + i); | |
391 } | |
392 } | |
393 if (urls->empty() && !first_url_erased.empty()) { | |
394 error_ = ErrorUtils::FormatErrorMessage( | |
395 keys::kURLsNotAllowedInIncognitoError, first_url_erased); | |
396 *is_error = true; | |
397 return false; | |
398 } | |
399 } | |
400 return incognito; | |
401 } | |
402 | |
403 bool WindowsCreateFunction::RunImpl() { | |
404 DictionaryValue* args = NULL; | |
405 std::vector<GURL> urls; | |
406 WebContents* contents = NULL; | |
407 | |
408 if (HasOptionalArgument(0)) | |
409 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); | |
410 | |
411 // Look for optional url. | |
412 if (args) { | |
413 if (args->HasKey(keys::kUrlKey)) { | |
414 Value* url_value; | |
415 std::vector<std::string> url_strings; | |
416 args->Get(keys::kUrlKey, &url_value); | |
417 | |
418 // First, get all the URLs the client wants to open. | |
419 if (url_value->IsType(Value::TYPE_STRING)) { | |
420 std::string url_string; | |
421 url_value->GetAsString(&url_string); | |
422 url_strings.push_back(url_string); | |
423 } else if (url_value->IsType(Value::TYPE_LIST)) { | |
424 const ListValue* url_list = static_cast<const ListValue*>(url_value); | |
425 for (size_t i = 0; i < url_list->GetSize(); ++i) { | |
426 std::string url_string; | |
427 EXTENSION_FUNCTION_VALIDATE(url_list->GetString(i, &url_string)); | |
428 url_strings.push_back(url_string); | |
429 } | |
430 } | |
431 | |
432 // Second, resolve, validate and convert them to GURLs. | |
433 for (std::vector<std::string>::iterator i = url_strings.begin(); | |
434 i != url_strings.end(); ++i) { | |
435 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( | |
436 *i, GetExtension()); | |
437 if (!url.is_valid()) { | |
438 error_ = ErrorUtils::FormatErrorMessage( | |
439 keys::kInvalidUrlError, *i); | |
440 return false; | |
441 } | |
442 // Don't let the extension crash the browser or renderers. | |
443 if (ExtensionTabUtil::IsCrashURL(url)) { | |
444 error_ = keys::kNoCrashBrowserError; | |
445 return false; | |
446 } | |
447 urls.push_back(url); | |
448 } | |
449 } | |
450 } | |
451 | |
452 // Look for optional tab id. | |
453 if (args) { | |
454 int tab_id = -1; | |
455 if (args->HasKey(keys::kTabIdKey)) { | |
456 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id)); | |
457 | |
458 // Find the tab and detach it from the original window. | |
459 TabStripModel* source_tab_strip = NULL; | |
460 int tab_index = -1; | |
461 if (!GetTabById(tab_id, profile(), include_incognito(), | |
462 NULL, &source_tab_strip, | |
463 NULL, &tab_index, &error_)) | |
464 return false; | |
465 contents = source_tab_strip->DetachWebContentsAt(tab_index); | |
466 if (!contents) { | |
467 error_ = ErrorUtils::FormatErrorMessage( | |
468 keys::kTabNotFoundError, base::IntToString(tab_id)); | |
469 return false; | |
470 } | |
471 } | |
472 } | |
473 | |
474 Profile* window_profile = profile(); | |
475 Browser::Type window_type = Browser::TYPE_TABBED; | |
476 | |
477 // panel_create_mode only applies if window is TYPE_PANEL. | |
478 PanelManager::CreateMode panel_create_mode = PanelManager::CREATE_AS_DOCKED; | |
479 | |
480 gfx::Rect window_bounds; | |
481 bool focused = true; | |
482 bool saw_focus_key = false; | |
483 std::string extension_id; | |
484 | |
485 // Decide whether we are opening a normal window or an incognito window. | |
486 bool is_error = true; | |
487 bool open_incognito_window = ShouldOpenIncognitoWindow(args, &urls, | |
488 &is_error); | |
489 if (is_error) { | |
490 // error_ member variable is set inside of ShouldOpenIncognitoWindow. | |
491 return false; | |
492 } | |
493 if (open_incognito_window) { | |
494 window_profile = window_profile->GetOffTheRecordProfile(); | |
495 } | |
496 | |
497 if (args) { | |
498 // Figure out window type before figuring out bounds so that default | |
499 // bounds can be set according to the window type. | |
500 std::string type_str; | |
501 if (args->HasKey(keys::kWindowTypeKey)) { | |
502 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kWindowTypeKey, | |
503 &type_str)); | |
504 if (type_str == keys::kWindowTypeValuePopup) { | |
505 window_type = Browser::TYPE_POPUP; | |
506 extension_id = GetExtension()->id(); | |
507 } else if (type_str == keys::kWindowTypeValuePanel || | |
508 type_str == keys::kWindowTypeValueDetachedPanel) { | |
509 extension_id = GetExtension()->id(); | |
510 bool use_panels = false; | |
511 #if !defined(OS_ANDROID) | |
512 use_panels = PanelManager::ShouldUsePanels(extension_id); | |
513 #endif | |
514 if (use_panels) { | |
515 window_type = Browser::TYPE_PANEL; | |
516 #if !defined(OS_CHROMEOS) | |
517 // Non-ChromeOS has both docked and detached panel types. | |
518 if (type_str == keys::kWindowTypeValueDetachedPanel) | |
519 panel_create_mode = PanelManager::CREATE_AS_DETACHED; | |
520 #endif | |
521 } else { | |
522 window_type = Browser::TYPE_POPUP; | |
523 } | |
524 } else if (type_str != keys::kWindowTypeValueNormal) { | |
525 error_ = keys::kInvalidWindowTypeError; | |
526 return false; | |
527 } | |
528 } | |
529 | |
530 // Initialize default window bounds according to window type. | |
531 // In ChromiumOS the default popup bounds is 0x0 which indicates default | |
532 // window sizes in PanelBrowserView. In other OSs use the same default | |
533 // bounds as windows. | |
534 #if !defined(OS_CHROMEOS) | |
535 if (Browser::TYPE_TABBED == window_type || | |
536 Browser::TYPE_POPUP == window_type) { | |
537 #else | |
538 if (Browser::TYPE_TABBED == window_type) { | |
539 #endif | |
540 // Try to position the new browser relative to its originating | |
541 // browser window. The call offsets the bounds by kWindowTilePixels | |
542 // (defined in WindowSizer to be 10). | |
543 // | |
544 // NOTE(rafaelw): It's ok if GetCurrentBrowser() returns NULL here. | |
545 // GetBrowserWindowBounds will default to saved "default" values for | |
546 // the app. | |
547 ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT; | |
548 WindowSizer::GetBrowserWindowBoundsAndShowState(std::string(), | |
549 gfx::Rect(), | |
550 GetCurrentBrowser(), | |
551 &window_bounds, | |
552 &show_state); | |
553 } | |
554 | |
555 if (Browser::TYPE_PANEL == window_type && | |
556 PanelManager::CREATE_AS_DETACHED == panel_create_mode) { | |
557 window_bounds.set_origin( | |
558 PanelManager::GetInstance()->GetDefaultDetachedPanelOrigin()); | |
559 } | |
560 | |
561 // Any part of the bounds can optionally be set by the caller. | |
562 int bounds_val = -1; | |
563 if (args->HasKey(keys::kLeftKey)) { | |
564 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey, | |
565 &bounds_val)); | |
566 window_bounds.set_x(bounds_val); | |
567 } | |
568 | |
569 if (args->HasKey(keys::kTopKey)) { | |
570 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTopKey, | |
571 &bounds_val)); | |
572 window_bounds.set_y(bounds_val); | |
573 } | |
574 | |
575 if (args->HasKey(keys::kWidthKey)) { | |
576 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey, | |
577 &bounds_val)); | |
578 window_bounds.set_width(bounds_val); | |
579 } | |
580 | |
581 if (args->HasKey(keys::kHeightKey)) { | |
582 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kHeightKey, | |
583 &bounds_val)); | |
584 window_bounds.set_height(bounds_val); | |
585 } | |
586 | |
587 if (args->HasKey(keys::kFocusedKey)) { | |
588 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kFocusedKey, | |
589 &focused)); | |
590 saw_focus_key = true; | |
591 } | |
592 } | |
593 | |
594 #if !defined(OS_CHROMEOS) | |
595 if (window_type == Browser::TYPE_PANEL) { | |
596 std::string title = | |
597 web_app::GenerateApplicationNameFromExtensionId(extension_id); | |
598 // Note: Panels ignore all but the first url provided. | |
599 Panel* panel = PanelManager::GetInstance()->CreatePanel( | |
600 title, window_profile, urls[0], window_bounds, panel_create_mode); | |
601 | |
602 // Unlike other window types, Panels do not take focus by default. | |
603 if (!saw_focus_key || !focused) | |
604 panel->ShowInactive(); | |
605 else | |
606 panel->Show(); | |
607 | |
608 SetResult( | |
609 panel->extension_window_controller()->CreateWindowValueWithTabs( | |
610 GetExtension())); | |
611 return true; | |
612 } | |
613 #endif | |
614 | |
615 // Create a new BrowserWindow. | |
616 chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop(); | |
617 Browser::CreateParams create_params(window_type, window_profile, | |
618 host_desktop_type); | |
619 if (extension_id.empty()) { | |
620 create_params.initial_bounds = window_bounds; | |
621 } else { | |
622 create_params = Browser::CreateParams::CreateForApp( | |
623 window_type, | |
624 web_app::GenerateApplicationNameFromExtensionId(extension_id), | |
625 window_bounds, | |
626 window_profile); | |
627 } | |
628 create_params.initial_show_state = ui::SHOW_STATE_NORMAL; | |
629 create_params.host_desktop_type = chrome::GetActiveDesktop(); | |
630 | |
631 Browser* new_window = CreateBrowserWindow(create_params, window_profile, | |
632 extension_id); | |
633 | |
634 for (std::vector<GURL>::iterator i = urls.begin(); i != urls.end(); ++i) { | |
635 WebContents* tab = chrome::AddSelectedTabWithURL( | |
636 new_window, *i, content::PAGE_TRANSITION_LINK); | |
637 if (window_type == Browser::TYPE_PANEL) { | |
638 extensions::TabHelper::FromWebContents(tab)-> | |
639 SetExtensionAppIconById(extension_id); | |
640 } | |
641 } | |
642 if (contents) { | |
643 TabStripModel* target_tab_strip = new_window->tab_strip_model(); | |
644 target_tab_strip->InsertWebContentsAt(urls.size(), contents, | |
645 TabStripModel::ADD_NONE); | |
646 } else if (urls.empty()) { | |
647 chrome::NewTab(new_window); | |
648 } | |
649 chrome::SelectNumberedTab(new_window, 0); | |
650 | |
651 // Unlike other window types, Panels do not take focus by default. | |
652 if (!saw_focus_key && window_type == Browser::TYPE_PANEL) | |
653 focused = false; | |
654 | |
655 if (focused) | |
656 new_window->window()->Show(); | |
657 else | |
658 new_window->window()->ShowInactive(); | |
659 | |
660 if (new_window->profile()->IsOffTheRecord() && !include_incognito()) { | |
661 // Don't expose incognito windows if the extension isn't allowed. | |
662 SetResult(Value::CreateNullValue()); | |
663 } else { | |
664 SetResult( | |
665 new_window->extension_window_controller()->CreateWindowValueWithTabs( | |
666 GetExtension())); | |
667 } | |
668 | |
669 return true; | |
670 } | |
671 | |
672 bool WindowsUpdateFunction::RunImpl() { | |
673 int window_id = extension_misc::kUnknownWindowId; | |
674 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); | |
675 DictionaryValue* update_props; | |
676 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); | |
677 | |
678 WindowController* controller; | |
679 if (!GetWindowFromWindowID(this, window_id, &controller)) | |
680 return false; | |
681 | |
682 #if defined(OS_WIN) | |
683 // Silently ignore changes on the window for metro mode. | |
684 if (win8::IsSingleWindowMetroMode()) { | |
685 SetResult(controller->CreateWindowValue()); | |
686 return true; | |
687 } | |
688 #endif | |
689 | |
690 ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT; // No change. | |
691 std::string state_str; | |
692 if (update_props->HasKey(keys::kShowStateKey)) { | |
693 EXTENSION_FUNCTION_VALIDATE(update_props->GetString(keys::kShowStateKey, | |
694 &state_str)); | |
695 if (state_str == keys::kShowStateValueNormal) { | |
696 show_state = ui::SHOW_STATE_NORMAL; | |
697 } else if (state_str == keys::kShowStateValueMinimized) { | |
698 show_state = ui::SHOW_STATE_MINIMIZED; | |
699 } else if (state_str == keys::kShowStateValueMaximized) { | |
700 show_state = ui::SHOW_STATE_MAXIMIZED; | |
701 } else if (state_str == keys::kShowStateValueFullscreen) { | |
702 show_state = ui::SHOW_STATE_FULLSCREEN; | |
703 } else { | |
704 error_ = keys::kInvalidWindowStateError; | |
705 return false; | |
706 } | |
707 } | |
708 | |
709 if (show_state != ui::SHOW_STATE_FULLSCREEN && | |
710 show_state != ui::SHOW_STATE_DEFAULT) | |
711 controller->SetFullscreenMode(false, GetExtension()->url()); | |
712 | |
713 switch (show_state) { | |
714 case ui::SHOW_STATE_MINIMIZED: | |
715 controller->window()->Minimize(); | |
716 break; | |
717 case ui::SHOW_STATE_MAXIMIZED: | |
718 controller->window()->Maximize(); | |
719 break; | |
720 case ui::SHOW_STATE_FULLSCREEN: | |
721 if (controller->window()->IsMinimized() || | |
722 controller->window()->IsMaximized()) | |
723 controller->window()->Restore(); | |
724 controller->SetFullscreenMode(true, GetExtension()->url()); | |
725 break; | |
726 case ui::SHOW_STATE_NORMAL: | |
727 controller->window()->Restore(); | |
728 break; | |
729 default: | |
730 break; | |
731 } | |
732 | |
733 gfx::Rect bounds; | |
734 if (controller->window()->IsMinimized()) | |
735 bounds = controller->window()->GetRestoredBounds(); | |
736 else | |
737 bounds = controller->window()->GetBounds(); | |
738 bool set_bounds = false; | |
739 | |
740 // Any part of the bounds can optionally be set by the caller. | |
741 int bounds_val; | |
742 if (update_props->HasKey(keys::kLeftKey)) { | |
743 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
744 keys::kLeftKey, | |
745 &bounds_val)); | |
746 bounds.set_x(bounds_val); | |
747 set_bounds = true; | |
748 } | |
749 | |
750 if (update_props->HasKey(keys::kTopKey)) { | |
751 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
752 keys::kTopKey, | |
753 &bounds_val)); | |
754 bounds.set_y(bounds_val); | |
755 set_bounds = true; | |
756 } | |
757 | |
758 if (update_props->HasKey(keys::kWidthKey)) { | |
759 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
760 keys::kWidthKey, | |
761 &bounds_val)); | |
762 bounds.set_width(bounds_val); | |
763 set_bounds = true; | |
764 } | |
765 | |
766 if (update_props->HasKey(keys::kHeightKey)) { | |
767 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
768 keys::kHeightKey, | |
769 &bounds_val)); | |
770 bounds.set_height(bounds_val); | |
771 set_bounds = true; | |
772 } | |
773 | |
774 if (set_bounds) { | |
775 if (show_state == ui::SHOW_STATE_MINIMIZED || | |
776 show_state == ui::SHOW_STATE_MAXIMIZED || | |
777 show_state == ui::SHOW_STATE_FULLSCREEN) { | |
778 error_ = keys::kInvalidWindowStateError; | |
779 return false; | |
780 } | |
781 controller->window()->SetBounds(bounds); | |
782 } | |
783 | |
784 bool active_val = false; | |
785 if (update_props->HasKey(keys::kFocusedKey)) { | |
786 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
787 keys::kFocusedKey, &active_val)); | |
788 if (active_val) { | |
789 if (show_state == ui::SHOW_STATE_MINIMIZED) { | |
790 error_ = keys::kInvalidWindowStateError; | |
791 return false; | |
792 } | |
793 controller->window()->Activate(); | |
794 } else { | |
795 if (show_state == ui::SHOW_STATE_MAXIMIZED || | |
796 show_state == ui::SHOW_STATE_FULLSCREEN) { | |
797 error_ = keys::kInvalidWindowStateError; | |
798 return false; | |
799 } | |
800 controller->window()->Deactivate(); | |
801 } | |
802 } | |
803 | |
804 bool draw_attention = false; | |
805 if (update_props->HasKey(keys::kDrawAttentionKey)) { | |
806 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
807 keys::kDrawAttentionKey, &draw_attention)); | |
808 controller->window()->FlashFrame(draw_attention); | |
809 } | |
810 | |
811 SetResult(controller->CreateWindowValue()); | |
812 | |
813 return true; | |
814 } | |
815 | |
816 bool WindowsRemoveFunction::RunImpl() { | |
817 int window_id = -1; | |
818 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); | |
819 | |
820 WindowController* controller; | |
821 if (!GetWindowFromWindowID(this, window_id, &controller)) | |
822 return false; | |
823 | |
824 #if defined(OS_WIN) | |
825 // In Windows 8 metro mode, an existing Browser instance is reused for | |
826 // hosting the extension tab. We should not be closing it as we don't own it. | |
827 if (win8::IsSingleWindowMetroMode()) | |
828 return false; | |
829 #endif | |
830 | |
831 WindowController::Reason reason; | |
832 if (!controller->CanClose(&reason)) { | |
833 if (reason == WindowController::REASON_NOT_EDITABLE) | |
834 error_ = keys::kTabStripNotEditableError; | |
835 return false; | |
836 } | |
837 controller->window()->Close(); | |
838 return true; | |
839 } | |
840 | |
841 // Tabs ------------------------------------------------------------------------ | |
842 | |
843 bool TabsGetSelectedFunction::RunImpl() { | |
844 // windowId defaults to "current" window. | |
845 int window_id = extension_misc::kCurrentWindowId; | |
846 | |
847 if (HasOptionalArgument(0)) | |
848 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); | |
849 | |
850 Browser* browser = NULL; | |
851 if (!GetBrowserFromWindowID(this, window_id, &browser)) | |
852 return false; | |
853 | |
854 TabStripModel* tab_strip = browser->tab_strip_model(); | |
855 WebContents* contents = tab_strip->GetActiveWebContents(); | |
856 if (!contents) { | |
857 error_ = keys::kNoSelectedTabError; | |
858 return false; | |
859 } | |
860 SetResult(ExtensionTabUtil::CreateTabValue(contents, | |
861 tab_strip, | |
862 tab_strip->active_index(), | |
863 GetExtension())); | |
864 return true; | |
865 } | |
866 | |
867 bool TabsGetAllInWindowFunction::RunImpl() { | |
868 // windowId defaults to "current" window. | |
869 int window_id = extension_misc::kCurrentWindowId; | |
870 if (HasOptionalArgument(0)) | |
871 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); | |
872 | |
873 Browser* browser = NULL; | |
874 if (!GetBrowserFromWindowID(this, window_id, &browser)) | |
875 return false; | |
876 | |
877 SetResult(ExtensionTabUtil::CreateTabList(browser, GetExtension())); | |
878 | |
879 return true; | |
880 } | |
881 | |
882 bool TabsQueryFunction::RunImpl() { | |
883 DictionaryValue* query = NULL; | |
884 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query)); | |
885 | |
886 QueryArg active = ParseBoolQueryArg(query, keys::kActiveKey); | |
887 QueryArg pinned = ParseBoolQueryArg(query, keys::kPinnedKey); | |
888 QueryArg selected = ParseBoolQueryArg(query, keys::kHighlightedKey); | |
889 QueryArg current_window = ParseBoolQueryArg(query, keys::kCurrentWindowKey); | |
890 QueryArg focused_window = | |
891 ParseBoolQueryArg(query, keys::kLastFocusedWindowKey); | |
892 | |
893 QueryArg loading = NOT_SET; | |
894 if (query->HasKey(keys::kStatusKey)) { | |
895 std::string status; | |
896 EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kStatusKey, &status)); | |
897 loading = (status == keys::kStatusValueLoading) ? MATCH_TRUE : MATCH_FALSE; | |
898 } | |
899 | |
900 // It is o.k. to use URLPattern::SCHEME_ALL here because this function does | |
901 // not grant access to the content of the tabs, only to seeing their URLs and | |
902 // meta data. | |
903 URLPattern url_pattern(URLPattern::SCHEME_ALL, "<all_urls>"); | |
904 if (query->HasKey(keys::kUrlKey)) { | |
905 std::string value; | |
906 EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kUrlKey, &value)); | |
907 url_pattern = URLPattern(URLPattern::SCHEME_ALL, value); | |
908 } | |
909 | |
910 std::string title; | |
911 if (query->HasKey(keys::kTitleKey)) | |
912 EXTENSION_FUNCTION_VALIDATE( | |
913 query->GetString(keys::kTitleKey, &title)); | |
914 | |
915 int window_id = extension_misc::kUnknownWindowId; | |
916 if (query->HasKey(keys::kWindowIdKey)) | |
917 EXTENSION_FUNCTION_VALIDATE( | |
918 query->GetInteger(keys::kWindowIdKey, &window_id)); | |
919 | |
920 int index = -1; | |
921 if (query->HasKey(keys::kIndexKey)) | |
922 EXTENSION_FUNCTION_VALIDATE( | |
923 query->GetInteger(keys::kIndexKey, &index)); | |
924 | |
925 std::string window_type; | |
926 if (query->HasKey(keys::kWindowTypeLongKey)) | |
927 EXTENSION_FUNCTION_VALIDATE( | |
928 query->GetString(keys::kWindowTypeLongKey, &window_type)); | |
929 | |
930 ListValue* result = new ListValue(); | |
931 for (BrowserList::const_iterator browser = BrowserList::begin(); | |
932 browser != BrowserList::end(); ++browser) { | |
933 if (!profile()->IsSameProfile((*browser)->profile())) | |
934 continue; | |
935 | |
936 if (!(*browser)->window()) | |
937 continue; | |
938 | |
939 if (!include_incognito() && profile() != (*browser)->profile()) | |
940 continue; | |
941 | |
942 if (window_id >= 0 && window_id != ExtensionTabUtil::GetWindowId(*browser)) | |
943 continue; | |
944 | |
945 if (window_id == extension_misc::kCurrentWindowId && | |
946 *browser != GetCurrentBrowser()) | |
947 continue; | |
948 | |
949 if (!MatchesQueryArg(current_window, *browser == GetCurrentBrowser())) | |
950 continue; | |
951 | |
952 if (!MatchesQueryArg(focused_window, (*browser)->window()->IsActive())) | |
953 continue; | |
954 | |
955 if (!window_type.empty() && | |
956 window_type != | |
957 (*browser)->extension_window_controller()->GetWindowTypeText()) | |
958 continue; | |
959 | |
960 TabStripModel* tab_strip = (*browser)->tab_strip_model(); | |
961 for (int i = 0; i < tab_strip->count(); ++i) { | |
962 const WebContents* web_contents = tab_strip->GetWebContentsAt(i); | |
963 | |
964 if (index > -1 && i != index) | |
965 continue; | |
966 | |
967 if (!MatchesQueryArg(selected, tab_strip->IsTabSelected(i))) | |
968 continue; | |
969 | |
970 if (!MatchesQueryArg(active, i == tab_strip->active_index())) | |
971 continue; | |
972 | |
973 if (!MatchesQueryArg(pinned, tab_strip->IsTabPinned(i))) | |
974 continue; | |
975 | |
976 if (!title.empty() && !MatchPattern(web_contents->GetTitle(), | |
977 UTF8ToUTF16(title))) | |
978 continue; | |
979 | |
980 if (!url_pattern.MatchesURL(web_contents->GetURL())) | |
981 continue; | |
982 | |
983 if (!MatchesQueryArg(loading, web_contents->IsLoading())) | |
984 continue; | |
985 | |
986 result->Append(ExtensionTabUtil::CreateTabValue( | |
987 web_contents, tab_strip, i, GetExtension())); | |
988 } | |
989 } | |
990 | |
991 SetResult(result); | |
992 return true; | |
993 } | |
994 | |
995 bool TabsCreateFunction::RunImpl() { | |
996 DictionaryValue* args = NULL; | |
997 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); | |
998 | |
999 // windowId defaults to "current" window. | |
1000 int window_id = extension_misc::kCurrentWindowId; | |
1001 if (args->HasKey(keys::kWindowIdKey)) | |
1002 EXTENSION_FUNCTION_VALIDATE(args->GetInteger( | |
1003 keys::kWindowIdKey, &window_id)); | |
1004 | |
1005 Browser* browser = NULL; | |
1006 if (!GetBrowserFromWindowID(this, window_id, &browser)) | |
1007 return false; | |
1008 | |
1009 // Ensure the selected browser is tabbed. | |
1010 if (!browser->is_type_tabbed() && browser->IsAttemptingToCloseBrowser()) | |
1011 browser = chrome::FindTabbedBrowser(profile(), include_incognito(), | |
1012 browser->host_desktop_type()); | |
1013 | |
1014 if (!browser || !browser->window()) | |
1015 return false; | |
1016 | |
1017 // TODO(jstritar): Add a constant, chrome.tabs.TAB_ID_ACTIVE, that | |
1018 // represents the active tab. | |
1019 WebContents* opener = NULL; | |
1020 if (args->HasKey(keys::kOpenerTabIdKey)) { | |
1021 int opener_id = -1; | |
1022 EXTENSION_FUNCTION_VALIDATE(args->GetInteger( | |
1023 keys::kOpenerTabIdKey, &opener_id)); | |
1024 | |
1025 if (!ExtensionTabUtil::GetTabById( | |
1026 opener_id, profile(), include_incognito(), | |
1027 NULL, NULL, &opener, NULL)) | |
1028 return false; | |
1029 } | |
1030 | |
1031 // TODO(rafaelw): handle setting remaining tab properties: | |
1032 // -title | |
1033 // -favIconUrl | |
1034 | |
1035 std::string url_string; | |
1036 GURL url; | |
1037 if (args->HasKey(keys::kUrlKey)) { | |
1038 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, | |
1039 &url_string)); | |
1040 url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, | |
1041 GetExtension()); | |
1042 if (!url.is_valid()) { | |
1043 error_ = ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, | |
1044 url_string); | |
1045 return false; | |
1046 } | |
1047 } | |
1048 | |
1049 // Don't let extensions crash the browser or renderers. | |
1050 if (ExtensionTabUtil::IsCrashURL(url)) { | |
1051 error_ = keys::kNoCrashBrowserError; | |
1052 return false; | |
1053 } | |
1054 | |
1055 // Default to foreground for the new tab. The presence of 'selected' property | |
1056 // will override this default. This property is deprecated ('active' should | |
1057 // be used instead). | |
1058 bool active = true; | |
1059 if (args->HasKey(keys::kSelectedKey)) | |
1060 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kSelectedKey, &active)); | |
1061 | |
1062 // The 'active' property has replaced the 'selected' property. | |
1063 if (args->HasKey(keys::kActiveKey)) | |
1064 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kActiveKey, &active)); | |
1065 | |
1066 // Default to not pinning the tab. Setting the 'pinned' property to true | |
1067 // will override this default. | |
1068 bool pinned = false; | |
1069 if (args->HasKey(keys::kPinnedKey)) | |
1070 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPinnedKey, &pinned)); | |
1071 | |
1072 // We can't load extension URLs into incognito windows unless the extension | |
1073 // uses split mode. Special case to fall back to a tabbed window. | |
1074 if (url.SchemeIs(extensions::kExtensionScheme) && | |
1075 !GetExtension()->incognito_split_mode() && | |
1076 browser->profile()->IsOffTheRecord()) { | |
1077 Profile* profile = browser->profile()->GetOriginalProfile(); | |
1078 chrome::HostDesktopType desktop_type = browser->host_desktop_type(); | |
1079 | |
1080 browser = chrome::FindTabbedBrowser(profile, false, desktop_type); | |
1081 if (!browser) { | |
1082 browser = new Browser(Browser::CreateParams(Browser::TYPE_TABBED, | |
1083 profile, desktop_type)); | |
1084 browser->window()->Show(); | |
1085 } | |
1086 } | |
1087 | |
1088 // If index is specified, honor the value, but keep it bound to | |
1089 // -1 <= index <= tab_strip->count() where -1 invokes the default behavior. | |
1090 int index = -1; | |
1091 if (args->HasKey(keys::kIndexKey)) | |
1092 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kIndexKey, &index)); | |
1093 | |
1094 TabStripModel* tab_strip = browser->tab_strip_model(); | |
1095 | |
1096 index = std::min(std::max(index, -1), tab_strip->count()); | |
1097 | |
1098 int add_types = active ? TabStripModel::ADD_ACTIVE : | |
1099 TabStripModel::ADD_NONE; | |
1100 add_types |= TabStripModel::ADD_FORCE_INDEX; | |
1101 if (pinned) | |
1102 add_types |= TabStripModel::ADD_PINNED; | |
1103 chrome::NavigateParams params(browser, url, content::PAGE_TRANSITION_LINK); | |
1104 params.disposition = active ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; | |
1105 params.tabstrip_index = index; | |
1106 params.tabstrip_add_types = add_types; | |
1107 chrome::Navigate(¶ms); | |
1108 | |
1109 // The tab may have been created in a different window, so make sure we look | |
1110 // at the right tab strip. | |
1111 tab_strip = params.browser->tab_strip_model(); | |
1112 int new_index = tab_strip->GetIndexOfWebContents(params.target_contents); | |
1113 if (opener) | |
1114 tab_strip->SetOpenerOfWebContentsAt(new_index, opener); | |
1115 | |
1116 if (active) | |
1117 params.target_contents->GetView()->SetInitialFocus(); | |
1118 | |
1119 // Return data about the newly created tab. | |
1120 if (has_callback()) { | |
1121 SetResult(ExtensionTabUtil::CreateTabValue( | |
1122 params.target_contents, | |
1123 tab_strip, new_index, GetExtension())); | |
1124 } | |
1125 | |
1126 return true; | |
1127 } | |
1128 | |
1129 bool TabsDuplicateFunction::RunImpl() { | |
1130 int tab_id = -1; | |
1131 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
1132 | |
1133 Browser* browser = NULL; | |
1134 TabStripModel* tab_strip = NULL; | |
1135 int tab_index = -1; | |
1136 if (!GetTabById(tab_id, profile(), include_incognito(), | |
1137 &browser, &tab_strip, NULL, &tab_index, &error_)) { | |
1138 return false; | |
1139 } | |
1140 | |
1141 WebContents* new_contents = chrome::DuplicateTabAt(browser, tab_index); | |
1142 if (!has_callback()) | |
1143 return true; | |
1144 | |
1145 int new_index = tab_strip->GetIndexOfWebContents(new_contents); | |
1146 | |
1147 // Return data about the newly created tab. | |
1148 SetResult(ExtensionTabUtil::CreateTabValue( | |
1149 new_contents, | |
1150 tab_strip, new_index, GetExtension())); | |
1151 | |
1152 return true; | |
1153 } | |
1154 | |
1155 bool TabsGetFunction::RunImpl() { | |
1156 int tab_id = -1; | |
1157 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
1158 | |
1159 TabStripModel* tab_strip = NULL; | |
1160 WebContents* contents = NULL; | |
1161 int tab_index = -1; | |
1162 if (!GetTabById(tab_id, profile(), include_incognito(), | |
1163 NULL, &tab_strip, &contents, &tab_index, &error_)) | |
1164 return false; | |
1165 | |
1166 SetResult(ExtensionTabUtil::CreateTabValue(contents, | |
1167 tab_strip, | |
1168 tab_index, | |
1169 GetExtension())); | |
1170 return true; | |
1171 } | |
1172 | |
1173 bool TabsGetCurrentFunction::RunImpl() { | |
1174 DCHECK(dispatcher()); | |
1175 | |
1176 WebContents* contents = dispatcher()->delegate()->GetAssociatedWebContents(); | |
1177 if (contents) | |
1178 SetResult(ExtensionTabUtil::CreateTabValue(contents, GetExtension())); | |
1179 | |
1180 return true; | |
1181 } | |
1182 | |
1183 bool TabsHighlightFunction::RunImpl() { | |
1184 DictionaryValue* info = NULL; | |
1185 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &info)); | |
1186 | |
1187 // Get the window id from the params; default to current window if omitted. | |
1188 int window_id = extension_misc::kCurrentWindowId; | |
1189 if (info->HasKey(keys::kWindowIdKey)) | |
1190 EXTENSION_FUNCTION_VALIDATE( | |
1191 info->GetInteger(keys::kWindowIdKey, &window_id)); | |
1192 | |
1193 Browser* browser = NULL; | |
1194 if (!GetBrowserFromWindowID(this, window_id, &browser)) | |
1195 return false; | |
1196 | |
1197 TabStripModel* tabstrip = browser->tab_strip_model(); | |
1198 ui::ListSelectionModel selection; | |
1199 int active_index = -1; | |
1200 | |
1201 Value* tab_value = NULL; | |
1202 EXTENSION_FUNCTION_VALIDATE(info->Get(keys::kTabsKey, &tab_value)); | |
1203 | |
1204 std::vector<int> tab_indices; | |
1205 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers( | |
1206 tab_value, &tab_indices)); | |
1207 | |
1208 // Create a new selection model as we read the list of tab indices. | |
1209 for (size_t i = 0; i < tab_indices.size(); ++i) { | |
1210 int index = tab_indices[i]; | |
1211 | |
1212 // Make sure the index is in range. | |
1213 if (!tabstrip->ContainsIndex(index)) { | |
1214 error_ = ErrorUtils::FormatErrorMessage( | |
1215 keys::kTabIndexNotFoundError, base::IntToString(index)); | |
1216 return false; | |
1217 } | |
1218 | |
1219 // By default, we make the first tab in the list active. | |
1220 if (active_index == -1) | |
1221 active_index = index; | |
1222 | |
1223 selection.AddIndexToSelection(index); | |
1224 } | |
1225 | |
1226 // Make sure they actually specified tabs to select. | |
1227 if (selection.empty()) { | |
1228 error_ = keys::kNoHighlightedTabError; | |
1229 return false; | |
1230 } | |
1231 | |
1232 selection.set_active(active_index); | |
1233 browser->tab_strip_model()->SetSelectionFromModel(selection); | |
1234 SetResult( | |
1235 browser->extension_window_controller()->CreateWindowValueWithTabs( | |
1236 GetExtension())); | |
1237 return true; | |
1238 } | |
1239 | |
1240 TabsUpdateFunction::TabsUpdateFunction() : web_contents_(NULL) { | |
1241 } | |
1242 | |
1243 bool TabsUpdateFunction::RunImpl() { | |
1244 DictionaryValue* update_props = NULL; | |
1245 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); | |
1246 | |
1247 Value* tab_value = NULL; | |
1248 if (HasOptionalArgument(0)) { | |
1249 EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); | |
1250 } | |
1251 | |
1252 int tab_id = -1; | |
1253 WebContents* contents = NULL; | |
1254 if (tab_value == NULL || tab_value->IsType(Value::TYPE_NULL)) { | |
1255 Browser* browser = GetCurrentBrowser(); | |
1256 if (!browser) { | |
1257 error_ = keys::kNoCurrentWindowError; | |
1258 return false; | |
1259 } | |
1260 contents = browser->tab_strip_model()->GetActiveWebContents(); | |
1261 if (!contents) { | |
1262 error_ = keys::kNoSelectedTabError; | |
1263 return false; | |
1264 } | |
1265 tab_id = SessionID::IdForTab(contents); | |
1266 } else { | |
1267 EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id)); | |
1268 } | |
1269 | |
1270 int tab_index = -1; | |
1271 TabStripModel* tab_strip = NULL; | |
1272 if (!GetTabById(tab_id, profile(), include_incognito(), | |
1273 NULL, &tab_strip, &contents, &tab_index, &error_)) { | |
1274 return false; | |
1275 } | |
1276 | |
1277 web_contents_ = contents; | |
1278 | |
1279 // TODO(rafaelw): handle setting remaining tab properties: | |
1280 // -title | |
1281 // -favIconUrl | |
1282 | |
1283 // Navigate the tab to a new location if the url is different. | |
1284 bool is_async = false; | |
1285 if (!UpdateURLIfPresent(update_props, tab_id, &is_async)) | |
1286 return false; | |
1287 | |
1288 bool active = false; | |
1289 // TODO(rafaelw): Setting |active| from js doesn't make much sense. | |
1290 // Move tab selection management up to window. | |
1291 if (update_props->HasKey(keys::kSelectedKey)) | |
1292 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
1293 keys::kSelectedKey, &active)); | |
1294 | |
1295 // The 'active' property has replaced 'selected'. | |
1296 if (update_props->HasKey(keys::kActiveKey)) | |
1297 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
1298 keys::kActiveKey, &active)); | |
1299 | |
1300 if (active) { | |
1301 if (tab_strip->active_index() != tab_index) { | |
1302 tab_strip->ActivateTabAt(tab_index, false); | |
1303 DCHECK_EQ(contents, tab_strip->GetActiveWebContents()); | |
1304 } | |
1305 web_contents_->Focus(); | |
1306 } | |
1307 | |
1308 if (update_props->HasKey(keys::kHighlightedKey)) { | |
1309 bool highlighted = false; | |
1310 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
1311 keys::kHighlightedKey, &highlighted)); | |
1312 if (highlighted != tab_strip->IsTabSelected(tab_index)) | |
1313 tab_strip->ToggleSelectionAt(tab_index); | |
1314 } | |
1315 | |
1316 if (update_props->HasKey(keys::kPinnedKey)) { | |
1317 bool pinned = false; | |
1318 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( | |
1319 keys::kPinnedKey, &pinned)); | |
1320 tab_strip->SetTabPinned(tab_index, pinned); | |
1321 | |
1322 // Update the tab index because it may move when being pinned. | |
1323 tab_index = tab_strip->GetIndexOfWebContents(contents); | |
1324 } | |
1325 | |
1326 if (update_props->HasKey(keys::kOpenerTabIdKey)) { | |
1327 int opener_id = -1; | |
1328 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
1329 keys::kOpenerTabIdKey, &opener_id)); | |
1330 | |
1331 WebContents* opener_contents = NULL; | |
1332 if (!ExtensionTabUtil::GetTabById( | |
1333 opener_id, profile(), include_incognito(), | |
1334 NULL, NULL, &opener_contents, NULL)) | |
1335 return false; | |
1336 | |
1337 tab_strip->SetOpenerOfWebContentsAt(tab_index, opener_contents); | |
1338 } | |
1339 | |
1340 if (!is_async) { | |
1341 PopulateResult(); | |
1342 SendResponse(true); | |
1343 } | |
1344 return true; | |
1345 } | |
1346 | |
1347 bool TabsUpdateFunction::UpdateURLIfPresent(DictionaryValue* update_props, | |
1348 int tab_id, | |
1349 bool* is_async) { | |
1350 if (!update_props->HasKey(keys::kUrlKey)) | |
1351 return true; | |
1352 | |
1353 std::string url_string; | |
1354 EXTENSION_FUNCTION_VALIDATE(update_props->GetString( | |
1355 keys::kUrlKey, &url_string)); | |
1356 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( | |
1357 url_string, GetExtension()); | |
1358 | |
1359 if (!url.is_valid()) { | |
1360 error_ = ErrorUtils::FormatErrorMessage( | |
1361 keys::kInvalidUrlError, url_string); | |
1362 return false; | |
1363 } | |
1364 | |
1365 // Don't let the extension crash the browser or renderers. | |
1366 if (ExtensionTabUtil::IsCrashURL(url)) { | |
1367 error_ = keys::kNoCrashBrowserError; | |
1368 return false; | |
1369 } | |
1370 | |
1371 // JavaScript URLs can do the same kinds of things as cross-origin XHR, so | |
1372 // we need to check host permissions before allowing them. | |
1373 if (url.SchemeIs(chrome::kJavaScriptScheme)) { | |
1374 if (!GetExtension()->CanExecuteScriptOnPage( | |
1375 web_contents_->GetURL(), | |
1376 web_contents_->GetURL(), | |
1377 tab_id, | |
1378 NULL, | |
1379 &error_)) { | |
1380 return false; | |
1381 } | |
1382 | |
1383 extensions::TabHelper::FromWebContents(web_contents_)-> | |
1384 script_executor()->ExecuteScript( | |
1385 extension_id(), | |
1386 ScriptExecutor::JAVASCRIPT, | |
1387 url.path(), | |
1388 ScriptExecutor::TOP_FRAME, | |
1389 extensions::UserScript::DOCUMENT_IDLE, | |
1390 ScriptExecutor::MAIN_WORLD, | |
1391 base::Bind(&TabsUpdateFunction::OnExecuteCodeFinished, this)); | |
1392 | |
1393 *is_async = true; | |
1394 return true; | |
1395 } | |
1396 | |
1397 web_contents_->GetController().LoadURL( | |
1398 url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); | |
1399 | |
1400 // The URL of a tab contents never actually changes to a JavaScript URL, so | |
1401 // this check only makes sense in other cases. | |
1402 if (!url.SchemeIs(chrome::kJavaScriptScheme)) | |
1403 DCHECK_EQ(url.spec(), web_contents_->GetURL().spec()); | |
1404 | |
1405 return true; | |
1406 } | |
1407 | |
1408 void TabsUpdateFunction::PopulateResult() { | |
1409 if (!has_callback()) | |
1410 return; | |
1411 | |
1412 SetResult(ExtensionTabUtil::CreateTabValue(web_contents_, GetExtension())); | |
1413 } | |
1414 | |
1415 void TabsUpdateFunction::OnExecuteCodeFinished(const std::string& error, | |
1416 int32 on_page_id, | |
1417 const GURL& url, | |
1418 const ListValue& script_result) { | |
1419 if (error.empty()) | |
1420 PopulateResult(); | |
1421 else | |
1422 error_ = error; | |
1423 SendResponse(error.empty()); | |
1424 } | |
1425 | |
1426 bool TabsMoveFunction::RunImpl() { | |
1427 Value* tab_value = NULL; | |
1428 EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); | |
1429 | |
1430 std::vector<int> tab_ids; | |
1431 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers( | |
1432 tab_value, &tab_ids)); | |
1433 | |
1434 DictionaryValue* update_props = NULL; | |
1435 int new_index; | |
1436 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); | |
1437 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(keys::kIndexKey, | |
1438 &new_index)); | |
1439 | |
1440 ListValue tab_values; | |
1441 for (size_t i = 0; i < tab_ids.size(); ++i) { | |
1442 Browser* source_browser = NULL; | |
1443 TabStripModel* source_tab_strip = NULL; | |
1444 WebContents* contents = NULL; | |
1445 int tab_index = -1; | |
1446 if (!GetTabById(tab_ids[i], profile(), include_incognito(), | |
1447 &source_browser, &source_tab_strip, &contents, | |
1448 &tab_index, &error_)) | |
1449 return false; | |
1450 | |
1451 // Don't let the extension move the tab if the user is dragging tabs. | |
1452 if (!source_browser->window()->IsTabStripEditable()) { | |
1453 error_ = keys::kTabStripNotEditableError; | |
1454 return false; | |
1455 } | |
1456 | |
1457 // Insert the tabs one after another. | |
1458 new_index += i; | |
1459 | |
1460 if (update_props->HasKey(keys::kWindowIdKey)) { | |
1461 Browser* target_browser = NULL; | |
1462 int window_id = extension_misc::kUnknownWindowId; | |
1463 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( | |
1464 keys::kWindowIdKey, &window_id)); | |
1465 | |
1466 if (!GetBrowserFromWindowID(this, window_id, &target_browser)) | |
1467 return false; | |
1468 | |
1469 if (!target_browser->window()->IsTabStripEditable()) { | |
1470 error_ = keys::kTabStripNotEditableError; | |
1471 return false; | |
1472 } | |
1473 | |
1474 if (!target_browser->is_type_tabbed()) { | |
1475 error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; | |
1476 return false; | |
1477 } | |
1478 | |
1479 if (target_browser->profile() != source_browser->profile()) { | |
1480 error_ = keys::kCanOnlyMoveTabsWithinSameProfileError; | |
1481 return false; | |
1482 } | |
1483 | |
1484 // If windowId is different from the current window, move between windows. | |
1485 if (ExtensionTabUtil::GetWindowId(target_browser) != | |
1486 ExtensionTabUtil::GetWindowId(source_browser)) { | |
1487 TabStripModel* target_tab_strip = target_browser->tab_strip_model(); | |
1488 WebContents* web_contents = | |
1489 source_tab_strip->DetachWebContentsAt(tab_index); | |
1490 if (!web_contents) { | |
1491 error_ = ErrorUtils::FormatErrorMessage( | |
1492 keys::kTabNotFoundError, base::IntToString(tab_ids[i])); | |
1493 return false; | |
1494 } | |
1495 | |
1496 // Clamp move location to the last position. | |
1497 // This is ">" because it can append to a new index position. | |
1498 // -1 means set the move location to the last position. | |
1499 if (new_index > target_tab_strip->count() || new_index < 0) | |
1500 new_index = target_tab_strip->count(); | |
1501 | |
1502 target_tab_strip->InsertWebContentsAt( | |
1503 new_index, web_contents, TabStripModel::ADD_NONE); | |
1504 | |
1505 if (has_callback()) { | |
1506 tab_values.Append(ExtensionTabUtil::CreateTabValue( | |
1507 web_contents, | |
1508 target_tab_strip, | |
1509 new_index, | |
1510 GetExtension())); | |
1511 } | |
1512 | |
1513 continue; | |
1514 } | |
1515 } | |
1516 | |
1517 // Perform a simple within-window move. | |
1518 // Clamp move location to the last position. | |
1519 // This is ">=" because the move must be to an existing location. | |
1520 // -1 means set the move location to the last position. | |
1521 if (new_index >= source_tab_strip->count() || new_index < 0) | |
1522 new_index = source_tab_strip->count() - 1; | |
1523 | |
1524 if (new_index != tab_index) | |
1525 source_tab_strip->MoveWebContentsAt(tab_index, new_index, false); | |
1526 | |
1527 if (has_callback()) { | |
1528 tab_values.Append(ExtensionTabUtil::CreateTabValue( | |
1529 contents, source_tab_strip, new_index, GetExtension())); | |
1530 } | |
1531 } | |
1532 | |
1533 if (!has_callback()) | |
1534 return true; | |
1535 | |
1536 // Only return the results as an array if there are multiple tabs. | |
1537 if (tab_ids.size() > 1) { | |
1538 SetResult(tab_values.DeepCopy()); | |
1539 } else if (tab_ids.size() == 1) { | |
1540 Value* value = NULL; | |
1541 CHECK(tab_values.Get(0, &value)); | |
1542 SetResult(value->DeepCopy()); | |
1543 } | |
1544 return true; | |
1545 } | |
1546 | |
1547 bool TabsReloadFunction::RunImpl() { | |
1548 bool bypass_cache = false; | |
1549 if (HasOptionalArgument(1)) { | |
1550 DictionaryValue* reload_props = NULL; | |
1551 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &reload_props)); | |
1552 | |
1553 if (reload_props->HasKey(keys::kBypassCache)) { | |
1554 EXTENSION_FUNCTION_VALIDATE(reload_props->GetBoolean( | |
1555 keys::kBypassCache, | |
1556 &bypass_cache)); | |
1557 } | |
1558 } | |
1559 | |
1560 content::WebContents* web_contents = NULL; | |
1561 | |
1562 // If |tab_id| is specified, look for it. Otherwise default to selected tab | |
1563 // in the current window. | |
1564 Value* tab_value = NULL; | |
1565 if (HasOptionalArgument(0)) | |
1566 EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); | |
1567 | |
1568 if (tab_value == NULL || tab_value->IsType(Value::TYPE_NULL)) { | |
1569 Browser* browser = GetCurrentBrowser(); | |
1570 if (!browser) { | |
1571 error_ = keys::kNoCurrentWindowError; | |
1572 return false; | |
1573 } | |
1574 | |
1575 if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, NULL)) | |
1576 return false; | |
1577 } else { | |
1578 int tab_id = -1; | |
1579 EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id)); | |
1580 | |
1581 Browser* browser = NULL; | |
1582 if (!GetTabById(tab_id, profile(), include_incognito(), | |
1583 &browser, NULL, &web_contents, NULL, &error_)) | |
1584 return false; | |
1585 } | |
1586 | |
1587 if (web_contents->ShowingInterstitialPage()) { | |
1588 // This does as same as Browser::ReloadInternal. | |
1589 NavigationEntry* entry = web_contents->GetController().GetActiveEntry(); | |
1590 OpenURLParams params(entry->GetURL(), Referrer(), CURRENT_TAB, | |
1591 content::PAGE_TRANSITION_RELOAD, false); | |
1592 GetCurrentBrowser()->OpenURL(params); | |
1593 } else if (bypass_cache) { | |
1594 web_contents->GetController().ReloadIgnoringCache(true); | |
1595 } else { | |
1596 web_contents->GetController().Reload(true); | |
1597 } | |
1598 | |
1599 return true; | |
1600 } | |
1601 | |
1602 bool TabsRemoveFunction::RunImpl() { | |
1603 Value* tab_value = NULL; | |
1604 EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); | |
1605 | |
1606 std::vector<int> tab_ids; | |
1607 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers( | |
1608 tab_value, &tab_ids)); | |
1609 | |
1610 for (size_t i = 0; i < tab_ids.size(); ++i) { | |
1611 Browser* browser = NULL; | |
1612 WebContents* contents = NULL; | |
1613 if (!GetTabById(tab_ids[i], profile(), include_incognito(), | |
1614 &browser, NULL, &contents, NULL, &error_)) | |
1615 return false; | |
1616 | |
1617 // Don't let the extension remove a tab if the user is dragging tabs around. | |
1618 if (!browser->window()->IsTabStripEditable()) { | |
1619 error_ = keys::kTabStripNotEditableError; | |
1620 return false; | |
1621 } | |
1622 | |
1623 // There's a chance that the tab is being dragged, or we're in some other | |
1624 // nested event loop. This code path ensures that the tab is safely closed | |
1625 // under such circumstances, whereas |TabStripModel::CloseWebContentsAt()| | |
1626 // does not. | |
1627 contents->Close(); | |
1628 } | |
1629 return true; | |
1630 } | |
1631 | |
1632 bool TabsCaptureVisibleTabFunction::GetTabToCapture( | |
1633 WebContents** web_contents) { | |
1634 Browser* browser = NULL; | |
1635 // windowId defaults to "current" window. | |
1636 int window_id = extension_misc::kCurrentWindowId; | |
1637 | |
1638 if (HasOptionalArgument(0)) | |
1639 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); | |
1640 | |
1641 if (!GetBrowserFromWindowID(this, window_id, &browser)) | |
1642 return false; | |
1643 | |
1644 *web_contents = chrome::GetActiveWebContents(browser); | |
1645 if (*web_contents == NULL) { | |
1646 error_ = keys::kInternalVisibleTabCaptureError; | |
1647 return false; | |
1648 } | |
1649 | |
1650 return true; | |
1651 }; | |
1652 | |
1653 bool TabsCaptureVisibleTabFunction::RunImpl() { | |
1654 PrefServiceBase* service = profile()->GetPrefs(); | |
1655 if (service->GetBoolean(prefs::kDisableScreenshots)) { | |
1656 error_ = keys::kScreenshotsDisabled; | |
1657 return false; | |
1658 } | |
1659 | |
1660 WebContents* web_contents = NULL; | |
1661 if (!GetTabToCapture(&web_contents)) | |
1662 return false; | |
1663 | |
1664 image_format_ = FORMAT_JPEG; // Default format is JPEG. | |
1665 image_quality_ = kDefaultQuality; // Default quality setting. | |
1666 | |
1667 if (HasOptionalArgument(1)) { | |
1668 DictionaryValue* options = NULL; | |
1669 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options)); | |
1670 | |
1671 if (options->HasKey(keys::kFormatKey)) { | |
1672 std::string format; | |
1673 EXTENSION_FUNCTION_VALIDATE( | |
1674 options->GetString(keys::kFormatKey, &format)); | |
1675 | |
1676 if (format == keys::kFormatValueJpeg) { | |
1677 image_format_ = FORMAT_JPEG; | |
1678 } else if (format == keys::kFormatValuePng) { | |
1679 image_format_ = FORMAT_PNG; | |
1680 } else { | |
1681 // Schema validation should make this unreachable. | |
1682 EXTENSION_FUNCTION_VALIDATE(0); | |
1683 } | |
1684 } | |
1685 | |
1686 if (options->HasKey(keys::kQualityKey)) { | |
1687 EXTENSION_FUNCTION_VALIDATE( | |
1688 options->GetInteger(keys::kQualityKey, &image_quality_)); | |
1689 } | |
1690 } | |
1691 | |
1692 // captureVisibleTab() can return an image containing sensitive information | |
1693 // that the browser would otherwise protect. Ensure the extension has | |
1694 // permission to do this. | |
1695 if (!GetExtension()->CanCaptureVisiblePage( | |
1696 web_contents->GetURL(), | |
1697 SessionID::IdForTab(web_contents), | |
1698 &error_)) { | |
1699 return false; | |
1700 } | |
1701 | |
1702 RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); | |
1703 content::RenderWidgetHostView* view = render_view_host->GetView(); | |
1704 if (!view) { | |
1705 error_ = keys::kInternalVisibleTabCaptureError; | |
1706 return false; | |
1707 } | |
1708 skia::PlatformBitmap* temp_bitmap = new skia::PlatformBitmap; | |
1709 render_view_host->CopyFromBackingStore( | |
1710 gfx::Rect(), | |
1711 view->GetViewBounds().size(), | |
1712 base::Bind(&TabsCaptureVisibleTabFunction::CopyFromBackingStoreComplete, | |
1713 this, | |
1714 base::Owned(temp_bitmap)), | |
1715 temp_bitmap); | |
1716 return true; | |
1717 } | |
1718 | |
1719 void TabsCaptureVisibleTabFunction::CopyFromBackingStoreComplete( | |
1720 skia::PlatformBitmap* bitmap, | |
1721 bool succeeded) { | |
1722 if (succeeded) { | |
1723 VLOG(1) << "captureVisibleTab() got image from backing store."; | |
1724 SendResultFromBitmap(bitmap->GetBitmap()); | |
1725 return; | |
1726 } | |
1727 | |
1728 WebContents* web_contents = NULL; | |
1729 if (!GetTabToCapture(&web_contents)) { | |
1730 error_ = keys::kInternalVisibleTabCaptureError; | |
1731 SendResponse(false); | |
1732 return; | |
1733 } | |
1734 | |
1735 // Ask the renderer for a snapshot of the tab. | |
1736 registrar_.Add(this, | |
1737 chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN, | |
1738 content::Source<WebContents>(web_contents)); | |
1739 AddRef(); // Balanced in TabsCaptureVisibleTabFunction::Observe(). | |
1740 SnapshotTabHelper::FromWebContents(web_contents)->CaptureSnapshot(); | |
1741 } | |
1742 | |
1743 // If a backing store was not available in | |
1744 // TabsCaptureVisibleTabFunction::RunImpl, than the renderer was asked for a | |
1745 // snapshot. Listen for a notification that the snapshot is available. | |
1746 void TabsCaptureVisibleTabFunction::Observe( | |
1747 int type, | |
1748 const content::NotificationSource& source, | |
1749 const content::NotificationDetails& details) { | |
1750 DCHECK(type == chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN); | |
1751 | |
1752 const SkBitmap *screen_capture = | |
1753 content::Details<const SkBitmap>(details).ptr(); | |
1754 const bool error = screen_capture->empty(); | |
1755 | |
1756 if (error) { | |
1757 error_ = keys::kInternalVisibleTabCaptureError; | |
1758 SendResponse(false); | |
1759 } else { | |
1760 VLOG(1) << "captureVisibleTab() got image from renderer."; | |
1761 SendResultFromBitmap(*screen_capture); | |
1762 } | |
1763 | |
1764 Release(); // Balanced in TabsCaptureVisibleTabFunction::RunImpl(). | |
1765 } | |
1766 | |
1767 // Turn a bitmap of the screen into an image, set that image as the result, | |
1768 // and call SendResponse(). | |
1769 void TabsCaptureVisibleTabFunction::SendResultFromBitmap( | |
1770 const SkBitmap& screen_capture) { | |
1771 std::vector<unsigned char> data; | |
1772 SkAutoLockPixels screen_capture_lock(screen_capture); | |
1773 bool encoded = false; | |
1774 std::string mime_type; | |
1775 switch (image_format_) { | |
1776 case FORMAT_JPEG: | |
1777 encoded = gfx::JPEGCodec::Encode( | |
1778 reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)), | |
1779 gfx::JPEGCodec::FORMAT_SkBitmap, | |
1780 screen_capture.width(), | |
1781 screen_capture.height(), | |
1782 static_cast<int>(screen_capture.rowBytes()), | |
1783 image_quality_, | |
1784 &data); | |
1785 mime_type = keys::kMimeTypeJpeg; | |
1786 break; | |
1787 case FORMAT_PNG: | |
1788 encoded = gfx::PNGCodec::EncodeBGRASkBitmap( | |
1789 screen_capture, | |
1790 true, // Discard transparency. | |
1791 &data); | |
1792 mime_type = keys::kMimeTypePng; | |
1793 break; | |
1794 default: | |
1795 NOTREACHED() << "Invalid image format."; | |
1796 } | |
1797 | |
1798 if (!encoded) { | |
1799 error_ = keys::kInternalVisibleTabCaptureError; | |
1800 SendResponse(false); | |
1801 return; | |
1802 } | |
1803 | |
1804 std::string base64_result; | |
1805 base::StringPiece stream_as_string( | |
1806 reinterpret_cast<const char*>(vector_as_array(&data)), data.size()); | |
1807 | |
1808 base::Base64Encode(stream_as_string, &base64_result); | |
1809 base64_result.insert(0, base::StringPrintf("data:%s;base64,", | |
1810 mime_type.c_str())); | |
1811 SetResult(new StringValue(base64_result)); | |
1812 SendResponse(true); | |
1813 } | |
1814 | |
1815 void TabsCaptureVisibleTabFunction::RegisterUserPrefs( | |
1816 PrefServiceSyncable* service) { | |
1817 service->RegisterBooleanPref(prefs::kDisableScreenshots, false, | |
1818 PrefServiceSyncable::UNSYNCABLE_PREF); | |
1819 } | |
1820 | |
1821 bool TabsDetectLanguageFunction::RunImpl() { | |
1822 int tab_id = 0; | |
1823 Browser* browser = NULL; | |
1824 WebContents* contents = NULL; | |
1825 | |
1826 // If |tab_id| is specified, look for it. Otherwise default to selected tab | |
1827 // in the current window. | |
1828 if (HasOptionalArgument(0)) { | |
1829 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); | |
1830 if (!GetTabById(tab_id, profile(), include_incognito(), | |
1831 &browser, NULL, &contents, NULL, &error_)) { | |
1832 return false; | |
1833 } | |
1834 if (!browser || !contents) | |
1835 return false; | |
1836 } else { | |
1837 browser = GetCurrentBrowser(); | |
1838 if (!browser) | |
1839 return false; | |
1840 contents = browser->tab_strip_model()->GetActiveWebContents(); | |
1841 if (!contents) | |
1842 return false; | |
1843 } | |
1844 | |
1845 if (contents->GetController().NeedsReload()) { | |
1846 // If the tab hasn't been loaded, don't wait for the tab to load. | |
1847 error_ = keys::kCannotDetermineLanguageOfUnloadedTab; | |
1848 return false; | |
1849 } | |
1850 | |
1851 AddRef(); // Balanced in GotLanguage(). | |
1852 | |
1853 TranslateTabHelper* translate_tab_helper = | |
1854 TranslateTabHelper::FromWebContents(contents); | |
1855 if (!translate_tab_helper->language_state().original_language().empty()) { | |
1856 // Delay the callback invocation until after the current JS call has | |
1857 // returned. | |
1858 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( | |
1859 &TabsDetectLanguageFunction::GotLanguage, this, | |
1860 translate_tab_helper->language_state().original_language())); | |
1861 return true; | |
1862 } | |
1863 // The tab contents does not know its language yet. Let's wait until it | |
1864 // receives it, or until the tab is closed/navigates to some other page. | |
1865 registrar_.Add(this, chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED, | |
1866 content::Source<WebContents>(contents)); | |
1867 registrar_.Add( | |
1868 this, chrome::NOTIFICATION_TAB_CLOSING, | |
1869 content::Source<NavigationController>(&(contents->GetController()))); | |
1870 registrar_.Add( | |
1871 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, | |
1872 content::Source<NavigationController>(&(contents->GetController()))); | |
1873 return true; | |
1874 } | |
1875 | |
1876 void TabsDetectLanguageFunction::Observe( | |
1877 int type, | |
1878 const content::NotificationSource& source, | |
1879 const content::NotificationDetails& details) { | |
1880 std::string language; | |
1881 if (type == chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED) | |
1882 language = *content::Details<std::string>(details).ptr(); | |
1883 | |
1884 registrar_.RemoveAll(); | |
1885 | |
1886 // Call GotLanguage in all cases as we want to guarantee the callback is | |
1887 // called for every API call the extension made. | |
1888 GotLanguage(language); | |
1889 } | |
1890 | |
1891 void TabsDetectLanguageFunction::GotLanguage(const std::string& language) { | |
1892 SetResult(Value::CreateStringValue(language.c_str())); | |
1893 SendResponse(true); | |
1894 | |
1895 Release(); // Balanced in Run() | |
1896 } | |
1897 | |
1898 ExecuteCodeInTabFunction::ExecuteCodeInTabFunction() | |
1899 : execute_tab_id_(-1) { | |
1900 } | |
1901 | |
1902 ExecuteCodeInTabFunction::~ExecuteCodeInTabFunction() {} | |
1903 | |
1904 bool ExecuteCodeInTabFunction::HasPermission() { | |
1905 if (Init() && | |
1906 extension_->HasAPIPermissionForTab(execute_tab_id_, | |
1907 extensions::APIPermission::kTab)) { | |
1908 return true; | |
1909 } | |
1910 return ExtensionFunction::HasPermission(); | |
1911 } | |
1912 | |
1913 bool ExecuteCodeInTabFunction::RunImpl() { | |
1914 EXTENSION_FUNCTION_VALIDATE(Init()); | |
1915 | |
1916 if (!details_->code.get() && !details_->file.get()) { | |
1917 error_ = keys::kNoCodeOrFileToExecuteError; | |
1918 return false; | |
1919 } | |
1920 if (details_->code.get() && details_->file.get()) { | |
1921 error_ = keys::kMoreThanOneValuesError; | |
1922 return false; | |
1923 } | |
1924 | |
1925 content::WebContents* contents = NULL; | |
1926 | |
1927 // If |tab_id| is specified, look for the tab. Otherwise default to selected | |
1928 // tab in the current window. | |
1929 CHECK_GE(execute_tab_id_, 0); | |
1930 if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), | |
1931 include_incognito(), | |
1932 NULL, NULL, &contents, NULL)) { | |
1933 return false; | |
1934 } | |
1935 | |
1936 // NOTE: This can give the wrong answer due to race conditions, but it is OK, | |
1937 // we check again in the renderer. | |
1938 CHECK(contents); | |
1939 if (!GetExtension()->CanExecuteScriptOnPage(contents->GetURL(), | |
1940 contents->GetURL(), | |
1941 execute_tab_id_, | |
1942 NULL, | |
1943 &error_)) { | |
1944 return false; | |
1945 } | |
1946 | |
1947 if (details_->code.get()) | |
1948 return Execute(*details_->code); | |
1949 | |
1950 CHECK(details_->file.get()); | |
1951 resource_ = GetExtension()->GetResource(*details_->file); | |
1952 | |
1953 if (resource_.extension_root().empty() || resource_.relative_path().empty()) { | |
1954 error_ = keys::kNoCodeOrFileToExecuteError; | |
1955 return false; | |
1956 } | |
1957 | |
1958 scoped_refptr<FileReader> file_reader(new FileReader( | |
1959 resource_, base::Bind(&ExecuteCodeInTabFunction::DidLoadFile, this))); | |
1960 file_reader->Start(); | |
1961 | |
1962 return true; | |
1963 } | |
1964 | |
1965 void ExecuteCodeInTabFunction::OnExecuteCodeFinished(const std::string& error, | |
1966 int32 on_page_id, | |
1967 const GURL& on_url, | |
1968 const ListValue& result) { | |
1969 if (!error.empty()) | |
1970 SetError(error); | |
1971 | |
1972 SendResponse(error.empty()); | |
1973 } | |
1974 | |
1975 void TabsExecuteScriptFunction::OnExecuteCodeFinished(const std::string& error, | |
1976 int32 on_page_id, | |
1977 const GURL& on_url, | |
1978 const ListValue& result) { | |
1979 if (error.empty()) | |
1980 SetResult(result.DeepCopy()); | |
1981 ExecuteCodeInTabFunction::OnExecuteCodeFinished(error, on_page_id, on_url, | |
1982 result); | |
1983 } | |
1984 | |
1985 bool ExecuteCodeInTabFunction::Init() { | |
1986 if (details_.get()) | |
1987 return true; | |
1988 | |
1989 // |tab_id| is optional so it's ok if it's not there. | |
1990 int tab_id = -1; | |
1991 args_->GetInteger(0, &tab_id); | |
1992 | |
1993 // |details| are not optional. | |
1994 DictionaryValue* details_value = NULL; | |
1995 if (!args_->GetDictionary(1, &details_value)) | |
1996 return false; | |
1997 scoped_ptr<InjectDetails> details(new InjectDetails()); | |
1998 if (!InjectDetails::Populate(*details_value, details.get())) | |
1999 return false; | |
2000 | |
2001 // If the tab ID is -1 then it needs to be converted to the currently active | |
2002 // tab's ID. | |
2003 if (tab_id == -1) { | |
2004 Browser* browser = GetCurrentBrowser(); | |
2005 if (!browser) | |
2006 return false; | |
2007 content::WebContents* web_contents = NULL; | |
2008 if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, &tab_id)) | |
2009 return false; | |
2010 } | |
2011 | |
2012 execute_tab_id_ = tab_id; | |
2013 details_ = details.Pass(); | |
2014 return true; | |
2015 } | |
2016 | |
2017 void ExecuteCodeInTabFunction::DidLoadFile(bool success, | |
2018 const std::string& data) { | |
2019 std::string function_name = name(); | |
2020 const extensions::Extension* extension = GetExtension(); | |
2021 | |
2022 // Check if the file is CSS and needs localization. | |
2023 if (success && | |
2024 function_name == TabsInsertCSSFunction::function_name() && | |
2025 extension != NULL && | |
2026 data.find( | |
2027 extensions::MessageBundle::kMessageBegin) != std::string::npos) { | |
2028 BrowserThread::PostTask( | |
2029 BrowserThread::FILE, FROM_HERE, | |
2030 base::Bind(&ExecuteCodeInTabFunction::LocalizeCSS, this, | |
2031 data, | |
2032 extension->id(), | |
2033 extension->path(), | |
2034 extension->default_locale())); | |
2035 } else { | |
2036 DidLoadAndLocalizeFile(success, data); | |
2037 } | |
2038 } | |
2039 | |
2040 void ExecuteCodeInTabFunction::LocalizeCSS( | |
2041 const std::string& data, | |
2042 const std::string& extension_id, | |
2043 const FilePath& extension_path, | |
2044 const std::string& extension_default_locale) { | |
2045 scoped_ptr<SubstitutionMap> localization_messages( | |
2046 extension_file_util::LoadMessageBundleSubstitutionMap( | |
2047 extension_path, extension_id, extension_default_locale)); | |
2048 | |
2049 // We need to do message replacement on the data, so it has to be mutable. | |
2050 std::string css_data = data; | |
2051 std::string error; | |
2052 extensions::MessageBundle::ReplaceMessagesWithExternalDictionary( | |
2053 *localization_messages, &css_data, &error); | |
2054 | |
2055 // Call back DidLoadAndLocalizeFile on the UI thread. The success parameter | |
2056 // is always true, because if loading had failed, we wouldn't have had | |
2057 // anything to localize. | |
2058 BrowserThread::PostTask( | |
2059 BrowserThread::UI, FROM_HERE, | |
2060 base::Bind(&ExecuteCodeInTabFunction::DidLoadAndLocalizeFile, this, | |
2061 true, css_data)); | |
2062 } | |
2063 | |
2064 void ExecuteCodeInTabFunction::DidLoadAndLocalizeFile(bool success, | |
2065 const std::string& data) { | |
2066 if (success) { | |
2067 if (!Execute(data)) | |
2068 SendResponse(false); | |
2069 } else { | |
2070 #if defined(OS_POSIX) | |
2071 // TODO(viettrungluu): bug: there's no particular reason the path should be | |
2072 // UTF-8, in which case this may fail. | |
2073 error_ = ErrorUtils::FormatErrorMessage(keys::kLoadFileError, | |
2074 resource_.relative_path().value()); | |
2075 #elif defined(OS_WIN) | |
2076 error_ = ErrorUtils::FormatErrorMessage(keys::kLoadFileError, | |
2077 WideToUTF8(resource_.relative_path().value())); | |
2078 #endif // OS_WIN | |
2079 SendResponse(false); | |
2080 } | |
2081 } | |
2082 | |
2083 bool ExecuteCodeInTabFunction::Execute(const std::string& code_string) { | |
2084 content::WebContents* contents = NULL; | |
2085 Browser* browser = NULL; | |
2086 | |
2087 bool success = ExtensionTabUtil::GetTabById( | |
2088 execute_tab_id_, profile(), include_incognito(), &browser, NULL, | |
2089 &contents, NULL) && contents && browser; | |
2090 | |
2091 if (!success) | |
2092 return false; | |
2093 | |
2094 const extensions::Extension* extension = GetExtension(); | |
2095 if (!extension) | |
2096 return false; | |
2097 | |
2098 ScriptExecutor::ScriptType script_type = ScriptExecutor::JAVASCRIPT; | |
2099 std::string function_name = name(); | |
2100 if (function_name == TabsInsertCSSFunction::function_name()) { | |
2101 script_type = ScriptExecutor::CSS; | |
2102 } else if (function_name != TabsExecuteScriptFunction::function_name()) { | |
2103 NOTREACHED(); | |
2104 } | |
2105 | |
2106 ScriptExecutor::FrameScope frame_scope = | |
2107 details_->all_frames.get() && *details_->all_frames ? | |
2108 ScriptExecutor::ALL_FRAMES : | |
2109 ScriptExecutor::TOP_FRAME; | |
2110 | |
2111 UserScript::RunLocation run_at = UserScript::UNDEFINED; | |
2112 switch (details_->run_at) { | |
2113 case InjectDetails::RUN_AT_NONE: | |
2114 case InjectDetails::RUN_AT_DOCUMENT_IDLE: | |
2115 run_at = UserScript::DOCUMENT_IDLE; | |
2116 break; | |
2117 case InjectDetails::RUN_AT_DOCUMENT_START: | |
2118 run_at = UserScript::DOCUMENT_START; | |
2119 break; | |
2120 case InjectDetails::RUN_AT_DOCUMENT_END: | |
2121 run_at = UserScript::DOCUMENT_END; | |
2122 break; | |
2123 } | |
2124 CHECK_NE(UserScript::UNDEFINED, run_at); | |
2125 | |
2126 extensions::TabHelper::FromWebContents(contents)-> | |
2127 script_executor()->ExecuteScript( | |
2128 extension->id(), | |
2129 script_type, | |
2130 code_string, | |
2131 frame_scope, | |
2132 run_at, | |
2133 ScriptExecutor::ISOLATED_WORLD, | |
2134 base::Bind(&ExecuteCodeInTabFunction::OnExecuteCodeFinished, this)); | |
2135 return true; | |
2136 } | |
OLD | NEW |