Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(295)

Side by Side Diff: chrome/browser/extensions/api/tabs/tabs.cc

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

Powered by Google App Engine
This is Rietveld 408576698