1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/extension_tabs_module.h"
6
7 #include <algorithm>
8 #include <vector>
9
10 #include "base/base64.h"
11 #include "base/string_number_conversions.h"
12 #include "base/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/extensions/extension_function_dispatcher.h"
16 #include "chrome/browser/extensions/extension_host.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/extensions/extension_tabs_module_constants.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/tabs/tab_strip_model.h"
21 #include "chrome/browser/translate/translate_tab_helper.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/ui/browser_navigator.h"
25 #include "chrome/browser/ui/browser_window.h"
26 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
27 #include "chrome/browser/ui/window_sizer.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/extensions/extension.h"
30 #include "chrome/common/extensions/extension_error_utils.h"
31 #include "chrome/common/extensions/extension_messages.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/url_constants.h"
34 #include "content/browser/renderer_host/backing_store.h"
35 #include "content/browser/renderer_host/render_view_host.h"
36 #include "content/browser/renderer_host/render_view_host_delegate.h"
37 #include "content/browser/tab_contents/navigation_entry.h"
38 #include "content/browser/tab_contents/tab_contents.h"
39 #include "content/browser/tab_contents/tab_contents_view.h"
40 #include "content/common/notification_service.h"
41 #include "skia/ext/image_operations.h"
42 #include "skia/ext/platform_canvas.h"
43 #include "third_party/skia/include/core/SkBitmap.h"
44 #include "ui/gfx/codec/jpeg_codec.h"
45 #include "ui/gfx/codec/png_codec.h"
46
47 namespace keys = extension_tabs_module_constants;
48 namespace errors = extension_manifest_errors;
49
50 const int CaptureVisibleTabFunction::kDefaultQuality = 90;
51
52 // Forward declare static helper functions defined below.
53
54 // |error_message| can optionally be passed in a will be set with an appropriate
55 // message if the window cannot be found by id.
56 static Browser* GetBrowserInProfileWithId(Profile* profile,
57 const int window_id,
58 bool include_incognito,
59 std::string* error_message);
60
61 // |error_message| can optionally be passed in and will be set with an
62 // appropriate message if the tab cannot be found by id.
63 static bool GetTabById(int tab_id, Profile* profile,
64 bool include_incognito,
65 Browser** browser,
66 TabStripModel** tab_strip,
67 TabContentsWrapper** contents,
68 int* tab_index, std::string* error_message);
69
70 // Takes |url_string| and returns a GURL which is either valid and absolute
71 // or invalid. If |url_string| is not directly interpretable as a valid (it is
72 // likely a relative URL) an attempt is made to resolve it. |extension| is
73 // provided so it can be resolved relative to its extension base
74 // (chrome-extension://<id>/). Using the source frame url would be more correct,
75 // but because the api shipped with urls resolved relative to their extension
76 // base, we decided it wasn't worth breaking existing extensions to fix.
77 static GURL ResolvePossiblyRelativeURL(const std::string& url_string,
78 const Extension* extension);
79
80 // Return the type name for a browser window type.
81 static std::string GetWindowTypeText(Browser::Type type);
82
GetWindowId(const Browser * browser)83 int ExtensionTabUtil::GetWindowId(const Browser* browser) {
84 return browser->session_id().id();
85 }
86
GetTabId(const TabContents * tab_contents)87 int ExtensionTabUtil::GetTabId(const TabContents* tab_contents) {
88 return tab_contents->controller().session_id().id();
89 }
90
GetTabStatusText(bool is_loading)91 std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
92 return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
93 }
94
GetWindowIdOfTab(const TabContents * tab_contents)95 int ExtensionTabUtil::GetWindowIdOfTab(const TabContents* tab_contents) {
96 return tab_contents->controller().window_id().id();
97 }
98
CreateTabValue(const TabContents * contents)99 DictionaryValue* ExtensionTabUtil::CreateTabValue(
100 const TabContents* contents) {
101 // Find the tab strip and index of this guy.
102 TabStripModel* tab_strip = NULL;
103 int tab_index;
104 if (ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index))
105 return ExtensionTabUtil::CreateTabValue(contents, tab_strip, tab_index);
106
107 // Couldn't find it. This can happen if the tab is being dragged.
108 return ExtensionTabUtil::CreateTabValue(contents, NULL, -1);
109 }
110
CreateTabList(const Browser * browser)111 ListValue* ExtensionTabUtil::CreateTabList(const Browser* browser) {
112 ListValue* tab_list = new ListValue();
113 TabStripModel* tab_strip = browser->tabstrip_model();
114 for (int i = 0; i < tab_strip->count(); ++i) {
115 tab_list->Append(ExtensionTabUtil::CreateTabValue(
116 tab_strip->GetTabContentsAt(i)->tab_contents(), tab_strip, i));
117 }
118
119 return tab_list;
120 }
121
CreateTabValue(const TabContents * contents,TabStripModel * tab_strip,int tab_index)122 DictionaryValue* ExtensionTabUtil::CreateTabValue(
123 const TabContents* contents, TabStripModel* tab_strip, int tab_index) {
124 DictionaryValue* result = new DictionaryValue();
125 result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetTabId(contents));
126 result->SetInteger(keys::kIndexKey, tab_index);
127 result->SetInteger(keys::kWindowIdKey,
128 ExtensionTabUtil::GetWindowIdOfTab(contents));
129 result->SetString(keys::kUrlKey, contents->GetURL().spec());
130 result->SetString(keys::kStatusKey, GetTabStatusText(contents->is_loading()));
131 result->SetBoolean(keys::kSelectedKey,
132 tab_strip && tab_index == tab_strip->active_index());
133 result->SetBoolean(keys::kPinnedKey,
134 tab_strip && tab_strip->IsTabPinned(tab_index));
135 result->SetString(keys::kTitleKey, contents->GetTitle());
136 result->SetBoolean(keys::kIncognitoKey,
137 contents->profile()->IsOffTheRecord());
138
139 if (!contents->is_loading()) {
140 NavigationEntry* entry = contents->controller().GetActiveEntry();
141 if (entry) {
142 if (entry->favicon().is_valid())
143 result->SetString(keys::kFaviconUrlKey, entry->favicon().url().spec());
144 }
145 }
146
147 return result;
148 }
149
150 // if |populate| is true, each window gets a list property |tabs| which contains
151 // fully populated tab objects.
CreateWindowValue(const Browser * browser,bool populate_tabs)152 DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser,
153 bool populate_tabs) {
154 DCHECK(browser);
155 DCHECK(browser->window());
156 DictionaryValue* result = new DictionaryValue();
157 result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetWindowId(browser));
158 result->SetBoolean(keys::kIncognitoKey,
159 browser->profile()->IsOffTheRecord());
160 result->SetBoolean(keys::kFocusedKey, browser->window()->IsActive());
161 gfx::Rect bounds;
162 if (browser->window()->IsMaximized() || browser->window()->IsFullscreen())
163 bounds = browser->window()->GetBounds();
164 else
165 bounds = browser->window()->GetRestoredBounds();
166
167 result->SetInteger(keys::kLeftKey, bounds.x());
168 result->SetInteger(keys::kTopKey, bounds.y());
169 result->SetInteger(keys::kWidthKey, bounds.width());
170 result->SetInteger(keys::kHeightKey, bounds.height());
171 result->SetString(keys::kWindowTypeKey, GetWindowTypeText(browser->type()));
172
173 if (populate_tabs) {
174 result->Set(keys::kTabsKey, ExtensionTabUtil::CreateTabList(browser));
175 }
176
177 return result;
178 }
179
GetTabStripModel(const TabContents * tab_contents,TabStripModel ** tab_strip_model,int * tab_index)180 bool ExtensionTabUtil::GetTabStripModel(const TabContents* tab_contents,
181 TabStripModel** tab_strip_model,
182 int* tab_index) {
183 DCHECK(tab_contents);
184 DCHECK(tab_strip_model);
185 DCHECK(tab_index);
186
187 for (BrowserList::const_iterator it = BrowserList::begin();
188 it != BrowserList::end(); ++it) {
189 TabStripModel* tab_strip = (*it)->tabstrip_model();
190 int index = tab_strip->GetWrapperIndex(tab_contents);
191 if (index != -1) {
192 *tab_strip_model = tab_strip;
193 *tab_index = index;
194 return true;
195 }
196 }
197
198 return false;
199 }
200
GetDefaultTab(Browser * browser,TabContentsWrapper ** contents,int * tab_id)201 bool ExtensionTabUtil::GetDefaultTab(Browser* browser,
202 TabContentsWrapper** contents,
203 int* tab_id) {
204 DCHECK(browser);
205 DCHECK(contents);
206 DCHECK(tab_id);
207
208 *contents = browser->GetSelectedTabContentsWrapper();
209 if (*contents) {
210 if (tab_id)
211 *tab_id = ExtensionTabUtil::GetTabId((*contents)->tab_contents());
212 return true;
213 }
214
215 return false;
216 }
217
GetTabById(int tab_id,Profile * profile,bool include_incognito,Browser ** browser,TabStripModel ** tab_strip,TabContentsWrapper ** contents,int * tab_index)218 bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile,
219 bool include_incognito,
220 Browser** browser,
221 TabStripModel** tab_strip,
222 TabContentsWrapper** contents,
223 int* tab_index) {
224 Profile* incognito_profile =
225 include_incognito && profile->HasOffTheRecordProfile() ?
226 profile->GetOffTheRecordProfile() : NULL;
227 for (BrowserList::const_iterator iter = BrowserList::begin();
228 iter != BrowserList::end(); ++iter) {
229 Browser* target_browser = *iter;
230 if (target_browser->profile() == profile ||
231 target_browser->profile() == incognito_profile) {
232 TabStripModel* target_tab_strip = target_browser->tabstrip_model();
233 for (int i = 0; i < target_tab_strip->count(); ++i) {
234 TabContentsWrapper* target_contents =
235 target_tab_strip->GetTabContentsAt(i);
236 if (target_contents->controller().session_id().id() == tab_id) {
237 if (browser)
238 *browser = target_browser;
239 if (tab_strip)
240 *tab_strip = target_tab_strip;
241 if (contents)
242 *contents = target_contents;
243 if (tab_index)
244 *tab_index = i;
245 return true;
246 }
247 }
248 }
249 }
250 return false;
251 }
252
253 // Windows ---------------------------------------------------------------------
254
RunImpl()255 bool GetWindowFunction::RunImpl() {
256 int window_id;
257 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
258
259 Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
260 include_incognito(), &error_);
261 if (!browser || !browser->window()) {
262 error_ = ExtensionErrorUtils::FormatErrorMessage(
263 keys::kWindowNotFoundError, base::IntToString(window_id));
264 return false;
265 }
266
267 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
268 return true;
269 }
270
RunImpl()271 bool GetCurrentWindowFunction::RunImpl() {
272 Browser* browser = GetCurrentBrowser();
273 if (!browser || !browser->window()) {
274 error_ = keys::kNoCurrentWindowError;
275 return false;
276 }
277 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
278 return true;
279 }
280
RunImpl()281 bool GetLastFocusedWindowFunction::RunImpl() {
282 Browser* browser = BrowserList::FindBrowserWithType(
283 profile(), Browser::TYPE_ANY, include_incognito());
284 if (!browser || !browser->window()) {
285 error_ = keys::kNoLastFocusedWindowError;
286 return false;
287 }
288 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
289 return true;
290 }
291
RunImpl()292 bool GetAllWindowsFunction::RunImpl() {
293 bool populate_tabs = false;
294 if (HasOptionalArgument(0)) {
295 DictionaryValue* args;
296 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
297
298 if (args->HasKey(keys::kPopulateKey)) {
299 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPopulateKey,
300 &populate_tabs));
301 }
302 }
303
304 result_.reset(new ListValue());
305 Profile* incognito_profile =
306 include_incognito() && profile()->HasOffTheRecordProfile() ?
307 profile()->GetOffTheRecordProfile() : NULL;
308 for (BrowserList::const_iterator browser = BrowserList::begin();
309 browser != BrowserList::end(); ++browser) {
310 // Only examine browsers in the current profile that have windows.
311 if (((*browser)->profile() == profile() ||
312 (*browser)->profile() == incognito_profile) &&
313 (*browser)->window()) {
314 static_cast<ListValue*>(result_.get())->
315 Append(ExtensionTabUtil::CreateWindowValue(*browser, populate_tabs));
316 }
317 }
318
319 return true;
320 }
321
RunImpl()322 bool CreateWindowFunction::RunImpl() {
323 DictionaryValue* args = NULL;
324 std::vector<GURL> urls;
325 TabContentsWrapper* contents = NULL;
326
327 if (HasOptionalArgument(0))
328 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
329
330 // Look for optional url.
331 if (args) {
332 if (args->HasKey(keys::kUrlKey)) {
333 Value* url_value;
334 std::vector<std::string> url_strings;
335 args->Get(keys::kUrlKey, &url_value);
336
337 // First, get all the URLs the client wants to open.
338 if (url_value->IsType(Value::TYPE_STRING)) {
339 std::string url_string;
340 url_value->GetAsString(&url_string);
341 url_strings.push_back(url_string);
342 } else if (url_value->IsType(Value::TYPE_LIST)) {
343 const ListValue* url_list = static_cast<const ListValue*>(url_value);
344 for (size_t i = 0; i < url_list->GetSize(); ++i) {
345 std::string url_string;
346 EXTENSION_FUNCTION_VALIDATE(url_list->GetString(i, &url_string));
347 url_strings.push_back(url_string);
348 }
349 }
350
351 // Second, resolve, validate and convert them to GURLs.
352 for (std::vector<std::string>::iterator i = url_strings.begin();
353 i != url_strings.end(); ++i) {
354 GURL url = ResolvePossiblyRelativeURL(*i, GetExtension());
355 if (!url.is_valid()) {
356 error_ = ExtensionErrorUtils::FormatErrorMessage(
357 keys::kInvalidUrlError, *i);
358 return false;
359 }
360 urls.push_back(url);
361 }
362 }
363 }
364
365 // Don't let the extension crash the browser or renderers.
366 GURL browser_crash(chrome::kAboutBrowserCrash);
367 GURL renderer_crash(chrome::kAboutCrashURL);
368 if (std::find(urls.begin(), urls.end(), browser_crash) != urls.end() ||
369 std::find(urls.begin(), urls.end(), renderer_crash) != urls.end()) {
370 error_ = keys::kNoCrashBrowserError;
371 return false;
372 }
373
374 // Look for optional tab id.
375 if (args) {
376 int tab_id;
377 if (args->HasKey(keys::kTabIdKey)) {
378 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id));
379
380 // Find the tab and detach it from the original window.
381 Browser* source_browser = NULL;
382 TabStripModel* source_tab_strip = NULL;
383 int tab_index = -1;
384 if (!GetTabById(tab_id, profile(), include_incognito(),
385 &source_browser, &source_tab_strip, &contents,
386 &tab_index, &error_))
387 return false;
388 contents = source_tab_strip->DetachTabContentsAt(tab_index);
389 if (!contents) {
390 error_ = ExtensionErrorUtils::FormatErrorMessage(
391 keys::kTabNotFoundError, base::IntToString(tab_id));
392 return false;
393 }
394 }
395 }
396
397 // Try to position the new browser relative its originating browser window.
398 gfx::Rect window_bounds;
399 bool maximized;
400 // The call offsets the bounds by kWindowTilePixels (defined in WindowSizer to
401 // be 10)
402 //
403 // NOTE(rafaelw): It's ok if GetCurrentBrowser() returns NULL here.
404 // GetBrowserWindowBounds will default to saved "default" values for the app.
405 WindowSizer::GetBrowserWindowBounds(std::string(), gfx::Rect(),
406 GetCurrentBrowser(), &window_bounds,
407 &maximized);
408
409 // Calculate popup bounds separately. In ChromiumOS the default is 0x0 which
410 // indicates default window sizes in PanelBrowserView. In other OSs popups
411 // use the same default bounds as windows.
412 gfx::Rect popup_bounds;
413 #if !defined(OS_CHROMEOS)
414 popup_bounds = window_bounds; // Use window size as default for popups
415 #endif
416
417 Profile* window_profile = profile();
418 Browser::Type window_type = Browser::TYPE_NORMAL;
419 bool focused = true;
420
421 if (args) {
422 // Any part of the bounds can optionally be set by the caller.
423 int bounds_val;
424 if (args->HasKey(keys::kLeftKey)) {
425 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey,
426 &bounds_val));
427 window_bounds.set_x(bounds_val);
428 popup_bounds.set_x(bounds_val);
429 }
430
431 if (args->HasKey(keys::kTopKey)) {
432 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTopKey,
433 &bounds_val));
434 window_bounds.set_y(bounds_val);
435 popup_bounds.set_y(bounds_val);
436 }
437
438 if (args->HasKey(keys::kWidthKey)) {
439 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey,
440 &bounds_val));
441 window_bounds.set_width(bounds_val);
442 popup_bounds.set_width(bounds_val);
443 }
444
445 if (args->HasKey(keys::kHeightKey)) {
446 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kHeightKey,
447 &bounds_val));
448 window_bounds.set_height(bounds_val);
449 popup_bounds.set_height(bounds_val);
450 }
451
452 bool incognito = false;
453 if (args->HasKey(keys::kIncognitoKey)) {
454 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey,
455 &incognito));
456 if (!profile_->GetPrefs()->GetBoolean(prefs::kIncognitoEnabled)) {
457 error_ = keys::kIncognitoModeIsDisabled;
458 return false;
459 }
460
461 if (incognito)
462 window_profile = window_profile->GetOffTheRecordProfile();
463 }
464
465 if (args->HasKey(keys::kFocusedKey))
466 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kFocusedKey,
467 &focused));
468
469 std::string type_str;
470 if (args->HasKey(keys::kWindowTypeKey)) {
471 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kWindowTypeKey,
472 &type_str));
473 if (type_str == keys::kWindowTypeValueNormal) {
474 window_type = Browser::TYPE_NORMAL;
475 } else if (type_str == keys::kWindowTypeValuePopup) {
476 window_type = Browser::TYPE_APP_POPUP;
477 } else if (type_str == keys::kWindowTypeValuePanel) {
478 if (GetExtension()->HasApiPermission(
479 Extension::kExperimentalPermission)) {
480 window_type = Browser::TYPE_APP_PANEL;
481 } else {
482 error_ = errors::kExperimentalFeature;
483 return false;
484 }
485 } else {
486 EXTENSION_FUNCTION_VALIDATE(false);
487 }
488 }
489 }
490
491 Browser* new_window = Browser::CreateForType(window_type, window_profile);
492 for (std::vector<GURL>::iterator i = urls.begin(); i != urls.end(); ++i)
493 new_window->AddSelectedTabWithURL(*i, PageTransition::LINK);
494 if (contents) {
495 TabStripModel* target_tab_strip = new_window->tabstrip_model();
496 target_tab_strip->InsertTabContentsAt(urls.size(), contents,
497 TabStripModel::ADD_NONE);
498 } else if (urls.empty()) {
499 new_window->NewTab();
500 }
501 new_window->SelectNumberedTab(0);
502 if (window_type & Browser::TYPE_POPUP)
503 new_window->window()->SetBounds(popup_bounds);
504 else
505 new_window->window()->SetBounds(window_bounds);
506
507 if (focused)
508 new_window->window()->Show();
509 else
510 new_window->window()->ShowInactive();
511
512 if (new_window->profile()->IsOffTheRecord() && !include_incognito()) {
513 // Don't expose incognito windows if the extension isn't allowed.
514 result_.reset(Value::CreateNullValue());
515 } else {
516 result_.reset(ExtensionTabUtil::CreateWindowValue(new_window, true));
517 }
518
519 return true;
520 }
521
RunImpl()522 bool UpdateWindowFunction::RunImpl() {
523 int window_id;
524 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
525 DictionaryValue* update_props;
526 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
527
528 Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
529 include_incognito(), &error_);
530 if (!browser || !browser->window()) {
531 error_ = ExtensionErrorUtils::FormatErrorMessage(
532 keys::kWindowNotFoundError, base::IntToString(window_id));
533 return false;
534 }
535
536 gfx::Rect bounds = browser->window()->GetRestoredBounds();
537 bool set_bounds = false;
538 // Any part of the bounds can optionally be set by the caller.
539 int bounds_val;
540 if (update_props->HasKey(keys::kLeftKey)) {
541 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
542 keys::kLeftKey,
543 &bounds_val));
544 bounds.set_x(bounds_val);
545 set_bounds = true;
546 }
547
548 if (update_props->HasKey(keys::kTopKey)) {
549 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
550 keys::kTopKey,
551 &bounds_val));
552 bounds.set_y(bounds_val);
553 set_bounds = true;
554 }
555
556 if (update_props->HasKey(keys::kWidthKey)) {
557 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
558 keys::kWidthKey,
559 &bounds_val));
560 bounds.set_width(bounds_val);
561 set_bounds = true;
562 }
563
564 if (update_props->HasKey(keys::kHeightKey)) {
565 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
566 keys::kHeightKey,
567 &bounds_val));
568 bounds.set_height(bounds_val);
569 set_bounds = true;
570 }
571 if (set_bounds)
572 browser->window()->SetBounds(bounds);
573
574 bool selected_val = false;
575 if (update_props->HasKey(keys::kFocusedKey)) {
576 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(
577 keys::kFocusedKey, &selected_val));
578 if (selected_val)
579 browser->window()->Activate();
580 else
581 browser->window()->Deactivate();
582 }
583
584 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
585
586 return true;
587 }
588
RunImpl()589 bool RemoveWindowFunction::RunImpl() {
590 int window_id;
591 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
592
593 Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
594 include_incognito(), &error_);
595 if (!browser)
596 return false;
597
598 // Don't let the extension remove the window if the user is dragging tabs
599 // in that window.
600 if (!browser->IsTabStripEditable()) {
601 error_ = keys::kTabStripNotEditableError;
602 return false;
603 }
604
605 browser->CloseWindow();
606
607 return true;
608 }
609
610 // Tabs ------------------------------------------------------------------------
611
RunImpl()612 bool GetSelectedTabFunction::RunImpl() {
613 Browser* browser;
614 // windowId defaults to "current" window.
615 int window_id = -1;
616
617 if (HasOptionalArgument(0)) {
618 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
619 browser = GetBrowserInProfileWithId(profile(), window_id,
620 include_incognito(), &error_);
621 } else {
622 browser = GetCurrentBrowser();
623 if (!browser)
624 error_ = keys::kNoCurrentWindowError;
625 }
626 if (!browser)
627 return false;
628
629 TabStripModel* tab_strip = browser->tabstrip_model();
630 TabContentsWrapper* contents = tab_strip->GetSelectedTabContents();
631 if (!contents) {
632 error_ = keys::kNoSelectedTabError;
633 return false;
634 }
635 result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(),
636 tab_strip,
637 tab_strip->active_index()));
638 return true;
639 }
640
RunImpl()641 bool GetAllTabsInWindowFunction::RunImpl() {
642 Browser* browser;
643 // windowId defaults to "current" window.
644 int window_id = -1;
645 if (HasOptionalArgument(0)) {
646 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
647 browser = GetBrowserInProfileWithId(profile(), window_id,
648 include_incognito(), &error_);
649 } else {
650 browser = GetCurrentBrowser();
651 if (!browser)
652 error_ = keys::kNoCurrentWindowError;
653 }
654 if (!browser)
655 return false;
656
657 result_.reset(ExtensionTabUtil::CreateTabList(browser));
658
659 return true;
660 }
661
RunImpl()662 bool CreateTabFunction::RunImpl() {
663 DictionaryValue* args;
664 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
665
666 Browser *browser;
667 // windowId defaults to "current" window.
668 int window_id = -1;
669 if (args->HasKey(keys::kWindowIdKey)) {
670 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(
671 keys::kWindowIdKey, &window_id));
672 browser = GetBrowserInProfileWithId(profile(), window_id,
673 include_incognito(), &error_);
674 } else {
675 browser = GetCurrentBrowser();
676 if (!browser)
677 error_ = keys::kNoCurrentWindowError;
678 }
679 if (!browser)
680 return false;
681
682 // TODO(rafaelw): handle setting remaining tab properties:
683 // -title
684 // -favIconUrl
685
686 std::string url_string;
687 GURL url;
688 if (args->HasKey(keys::kUrlKey)) {
689 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey,
690 &url_string));
691 url = ResolvePossiblyRelativeURL(url_string, GetExtension());
692 if (!url.is_valid()) {
693 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
694 url_string);
695 return false;
696 }
697 }
698
699 // Don't let extensions crash the browser or renderers.
700 if (url == GURL(chrome::kAboutBrowserCrash) ||
701 url == GURL(chrome::kAboutCrashURL)) {
702 error_ = keys::kNoCrashBrowserError;
703 return false;
704 }
705
706 // Default to foreground for the new tab. The presence of 'selected' property
707 // will override this default.
708 bool selected = true;
709 if (args->HasKey(keys::kSelectedKey))
710 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kSelectedKey,
711 &selected));
712
713 // Default to not pinning the tab. Setting the 'pinned' property to true
714 // will override this default.
715 bool pinned = false;
716 if (args->HasKey(keys::kPinnedKey))
717 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPinnedKey, &pinned));
718
719 // We can't load extension URLs into incognito windows unless the extension
720 // uses split mode. Special case to fall back to a normal window.
721 if (url.SchemeIs(chrome::kExtensionScheme) &&
722 !GetExtension()->incognito_split_mode() &&
723 browser->profile()->IsOffTheRecord()) {
724 Profile* profile = browser->profile()->GetOriginalProfile();
725 browser = BrowserList::FindBrowserWithType(profile,
726 Browser::TYPE_NORMAL, false);
727 if (!browser) {
728 browser = Browser::Create(profile);
729 browser->window()->Show();
730 }
731 }
732
733 // If index is specified, honor the value, but keep it bound to
734 // -1 <= index <= tab_strip->count() where -1 invokes the default behavior.
735 int index = -1;
736 if (args->HasKey(keys::kIndexKey))
737 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kIndexKey, &index));
738
739 TabStripModel* tab_strip = browser->tabstrip_model();
740
741 index = std::min(std::max(index, -1), tab_strip->count());
742
743 int add_types = selected ? TabStripModel::ADD_ACTIVE :
744 TabStripModel::ADD_NONE;
745 add_types |= TabStripModel::ADD_FORCE_INDEX;
746 if (pinned)
747 add_types |= TabStripModel::ADD_PINNED;
748 browser::NavigateParams params(browser, url, PageTransition::LINK);
749 params.disposition = selected ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB;
750 params.tabstrip_index = index;
751 params.tabstrip_add_types = add_types;
752 browser::Navigate(¶ms);
753
754 if (selected)
755 params.target_contents->view()->SetInitialFocus();
756
757 // Return data about the newly created tab.
758 if (has_callback()) {
759 result_.reset(ExtensionTabUtil::CreateTabValue(
760 params.target_contents->tab_contents(),
761 params.browser->tabstrip_model(),
762 params.browser->tabstrip_model()->GetIndexOfTabContents(
763 params.target_contents)));
764 }
765
766 return true;
767 }
768
RunImpl()769 bool GetTabFunction::RunImpl() {
770 int tab_id;
771 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
772
773 TabStripModel* tab_strip = NULL;
774 TabContentsWrapper* contents = NULL;
775 int tab_index = -1;
776 if (!GetTabById(tab_id, profile(), include_incognito(),
777 NULL, &tab_strip, &contents, &tab_index, &error_))
778 return false;
779
780 result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(),
781 tab_strip,
782 tab_index));
783 return true;
784 }
785
RunImpl()786 bool GetCurrentTabFunction::RunImpl() {
787 DCHECK(dispatcher());
788
789 TabContents* contents = dispatcher()->delegate()->associated_tab_contents();
790 if (contents)
791 result_.reset(ExtensionTabUtil::CreateTabValue(contents));
792
793 return true;
794 }
795
UpdateTabFunction()796 UpdateTabFunction::UpdateTabFunction()
797 : ALLOW_THIS_IN_INITIALIZER_LIST(registrar_(this)) {
798 }
799
RunImpl()800 bool UpdateTabFunction::RunImpl() {
801 int tab_id;
802 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
803 DictionaryValue* update_props;
804 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
805
806 TabStripModel* tab_strip = NULL;
807 TabContentsWrapper* contents = NULL;
808 int tab_index = -1;
809 if (!GetTabById(tab_id, profile(), include_incognito(),
810 NULL, &tab_strip, &contents, &tab_index, &error_))
811 return false;
812
813 NavigationController& controller = contents->controller();
814
815 // TODO(rafaelw): handle setting remaining tab properties:
816 // -title
817 // -favIconUrl
818
819 // Navigate the tab to a new location if the url different.
820 std::string url_string;
821 if (update_props->HasKey(keys::kUrlKey)) {
822 EXTENSION_FUNCTION_VALIDATE(update_props->GetString(
823 keys::kUrlKey, &url_string));
824 GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension());
825
826 if (!url.is_valid()) {
827 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
828 url_string);
829 return false;
830 }
831
832 // Don't let the extension crash the browser or renderers.
833 if (url == GURL(chrome::kAboutBrowserCrash) ||
834 url == GURL(chrome::kAboutCrashURL)) {
835 error_ = keys::kNoCrashBrowserError;
836 return false;
837 }
838
839 // JavaScript URLs can do the same kinds of things as cross-origin XHR, so
840 // we need to check host permissions before allowing them.
841 if (url.SchemeIs(chrome::kJavaScriptScheme)) {
842 if (!GetExtension()->CanExecuteScriptOnPage(
843 contents->tab_contents()->GetURL(), NULL, &error_)) {
844 return false;
845 }
846
847 ExtensionMsg_ExecuteCode_Params params;
848 params.request_id = request_id();
849 params.extension_id = extension_id();
850 params.is_javascript = true;
851 params.code = url.path();
852 params.all_frames = false;
853 params.in_main_world = true;
854
855 RenderViewHost* render_view_host =
856 contents->tab_contents()->render_view_host();
857 render_view_host->Send(
858 new ExtensionMsg_ExecuteCode(render_view_host->routing_id(),
859 params));
860
861 registrar_.Observe(contents->tab_contents());
862 AddRef(); // balanced in Observe()
863
864 return true;
865 }
866
867 controller.LoadURL(url, GURL(), PageTransition::LINK);
868
869 // The URL of a tab contents never actually changes to a JavaScript URL, so
870 // this check only makes sense in other cases.
871 if (!url.SchemeIs(chrome::kJavaScriptScheme))
872 DCHECK_EQ(url.spec(), contents->tab_contents()->GetURL().spec());
873 }
874
875 bool selected = false;
876 // TODO(rafaelw): Setting |selected| from js doesn't make much sense.
877 // Move tab selection management up to window.
878 if (update_props->HasKey(keys::kSelectedKey)) {
879 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(
880 keys::kSelectedKey,
881 &selected));
882 if (selected) {
883 if (tab_strip->active_index() != tab_index) {
884 tab_strip->ActivateTabAt(tab_index, false);
885 DCHECK_EQ(contents, tab_strip->GetSelectedTabContents());
886 }
887 contents->tab_contents()->Focus();
888 }
889 }
890
891 bool pinned = false;
892 if (update_props->HasKey(keys::kPinnedKey)) {
893 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(keys::kPinnedKey,
894 &pinned));
895 tab_strip->SetTabPinned(tab_index, pinned);
896
897 // Update the tab index because it may move when being pinned.
898 tab_index = tab_strip->GetIndexOfTabContents(contents);
899 }
900
901 if (has_callback())
902 result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(),
903 tab_strip,
904 tab_index));
905
906 SendResponse(true);
907 return true;
908 }
909
OnMessageReceived(const IPC::Message & message)910 bool UpdateTabFunction::OnMessageReceived(const IPC::Message& message) {
911 if (message.type() != ExtensionHostMsg_ExecuteCodeFinished::ID)
912 return false;
913
914 int message_request_id;
915 void* iter = NULL;
916 if (!message.ReadInt(&iter, &message_request_id)) {
917 NOTREACHED() << "malformed extension message";
918 return true;
919 }
920
921 if (message_request_id != request_id())
922 return false;
923
924 IPC_BEGIN_MESSAGE_MAP(UpdateTabFunction, message)
925 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExecuteCodeFinished,
926 OnExecuteCodeFinished)
927 IPC_END_MESSAGE_MAP()
928 return true;
929 }
930
OnExecuteCodeFinished(int request_id,bool success,const std::string & error)931 void UpdateTabFunction::OnExecuteCodeFinished(int request_id,
932 bool success,
933 const std::string& error) {
934 if (!error.empty()) {
935 CHECK(!success);
936 error_ = error;
937 }
938
939 SendResponse(success);
940
941 registrar_.Observe(NULL);
942 Release(); // balanced in Execute()
943 }
944
RunImpl()945 bool MoveTabFunction::RunImpl() {
946 int tab_id;
947 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
948 DictionaryValue* update_props;
949 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
950
951 int new_index;
952 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
953 keys::kIndexKey, &new_index));
954 EXTENSION_FUNCTION_VALIDATE(new_index >= 0);
955
956 Browser* source_browser = NULL;
957 TabStripModel* source_tab_strip = NULL;
958 TabContentsWrapper* contents = NULL;
959 int tab_index = -1;
960 if (!GetTabById(tab_id, profile(), include_incognito(),
961 &source_browser, &source_tab_strip, &contents,
962 &tab_index, &error_))
963 return false;
964
965 // Don't let the extension move the tab if the user is dragging tabs.
966 if (!source_browser->IsTabStripEditable()) {
967 error_ = keys::kTabStripNotEditableError;
968 return false;
969 }
970
971 if (update_props->HasKey(keys::kWindowIdKey)) {
972 Browser* target_browser;
973 int window_id;
974 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
975 keys::kWindowIdKey, &window_id));
976 target_browser = GetBrowserInProfileWithId(profile(), window_id,
977 include_incognito(), &error_);
978 if (!target_browser)
979 return false;
980
981 if (!target_browser->IsTabStripEditable()) {
982 error_ = keys::kTabStripNotEditableError;
983 return false;
984 }
985
986 if (target_browser->type() != Browser::TYPE_NORMAL) {
987 error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError;
988 return false;
989 }
990
991 if (target_browser->profile() != source_browser->profile()) {
992 error_ = keys::kCanOnlyMoveTabsWithinSameProfileError;
993 return false;
994 }
995
996 // If windowId is different from the current window, move between windows.
997 if (ExtensionTabUtil::GetWindowId(target_browser) !=
998 ExtensionTabUtil::GetWindowId(source_browser)) {
999 TabStripModel* target_tab_strip = target_browser->tabstrip_model();
1000 contents = source_tab_strip->DetachTabContentsAt(tab_index);
1001 if (!contents) {
1002 error_ = ExtensionErrorUtils::FormatErrorMessage(
1003 keys::kTabNotFoundError, base::IntToString(tab_id));
1004 return false;
1005 }
1006
1007 // Clamp move location to the last position.
1008 // This is ">" because it can append to a new index position.
1009 if (new_index > target_tab_strip->count())
1010 new_index = target_tab_strip->count();
1011
1012 target_tab_strip->InsertTabContentsAt(new_index, contents,
1013 TabStripModel::ADD_NONE);
1014
1015 if (has_callback())
1016 result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(),
1017 target_tab_strip, new_index));
1018
1019 return true;
1020 }
1021 }
1022
1023 // Perform a simple within-window move.
1024 // Clamp move location to the last position.
1025 // This is ">=" because the move must be to an existing location.
1026 if (new_index >= source_tab_strip->count())
1027 new_index = source_tab_strip->count() - 1;
1028
1029 if (new_index != tab_index)
1030 source_tab_strip->MoveTabContentsAt(tab_index, new_index, false);
1031
1032 if (has_callback())
1033 result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(),
1034 source_tab_strip,
1035 new_index));
1036 return true;
1037 }
1038
1039
RunImpl()1040 bool RemoveTabFunction::RunImpl() {
1041 int tab_id;
1042 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
1043
1044 Browser* browser = NULL;
1045 TabContentsWrapper* contents = NULL;
1046 if (!GetTabById(tab_id, profile(), include_incognito(),
1047 &browser, NULL, &contents, NULL, &error_))
1048 return false;
1049
1050 // Don't let the extension remove a tab if the user is dragging tabs around.
1051 if (!browser->IsTabStripEditable()) {
1052 error_ = keys::kTabStripNotEditableError;
1053 return false;
1054 }
1055
1056 // Close the tab in this convoluted way, since there's a chance that the tab
1057 // is being dragged, or we're in some other nested event loop. This code path
1058 // should ensure that the tab is safely closed under such circumstances,
1059 // whereas |Browser::CloseTabContents()| does not.
1060 RenderViewHost* render_view_host = contents->render_view_host();
1061 render_view_host->delegate()->Close(render_view_host);
1062 return true;
1063 }
1064
RunImpl()1065 bool CaptureVisibleTabFunction::RunImpl() {
1066 Browser* browser;
1067 // windowId defaults to "current" window.
1068 int window_id = -1;
1069
1070 if (HasOptionalArgument(0)) {
1071 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
1072 browser = GetBrowserInProfileWithId(profile(), window_id,
1073 include_incognito(), &error_);
1074 } else {
1075 browser = GetCurrentBrowser();
1076 }
1077
1078 if (!browser) {
1079 error_ = keys::kNoCurrentWindowError;
1080 return false;
1081 }
1082
1083 image_format_ = FORMAT_JPEG; // Default format is JPEG.
1084 image_quality_ = kDefaultQuality; // Default quality setting.
1085
1086 if (HasOptionalArgument(1)) {
1087 DictionaryValue* options;
1088 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
1089
1090 if (options->HasKey(keys::kFormatKey)) {
1091 std::string format;
1092 EXTENSION_FUNCTION_VALIDATE(
1093 options->GetString(keys::kFormatKey, &format));
1094
1095 if (format == keys::kFormatValueJpeg) {
1096 image_format_ = FORMAT_JPEG;
1097 } else if (format == keys::kFormatValuePng) {
1098 image_format_ = FORMAT_PNG;
1099 } else {
1100 // Schema validation should make this unreachable.
1101 EXTENSION_FUNCTION_VALIDATE(0);
1102 }
1103 }
1104
1105 if (options->HasKey(keys::kQualityKey)) {
1106 EXTENSION_FUNCTION_VALIDATE(
1107 options->GetInteger(keys::kQualityKey, &image_quality_));
1108 }
1109 }
1110
1111 TabContents* tab_contents = browser->GetSelectedTabContents();
1112 if (!tab_contents) {
1113 error_ = keys::kInternalVisibleTabCaptureError;
1114 return false;
1115 }
1116
1117 // captureVisibleTab() can return an image containing sensitive information
1118 // that the browser would otherwise protect. Ensure the extension has
1119 // permission to do this.
1120 if (!GetExtension()->CanCaptureVisiblePage(tab_contents->GetURL(), &error_))
1121 return false;
1122
1123 RenderViewHost* render_view_host = tab_contents->render_view_host();
1124
1125 // If a backing store is cached for the tab we want to capture,
1126 // and it can be copied into a bitmap, then use it to generate the image.
1127 BackingStore* backing_store = render_view_host->GetBackingStore(false);
1128 if (backing_store && CaptureSnapshotFromBackingStore(backing_store))
1129 return true;
1130
1131 // Ask the renderer for a snapshot of the tab.
1132 render_view_host->CaptureSnapshot();
1133 registrar_.Add(this,
1134 NotificationType::TAB_SNAPSHOT_TAKEN,
1135 NotificationService::AllSources());
1136 AddRef(); // Balanced in CaptureVisibleTabFunction::Observe().
1137
1138 return true;
1139 }
1140
1141 // Build the image of a tab's contents out of a backing store.
1142 // This may fail if we can not copy a backing store into a bitmap.
1143 // For example, some uncommon X11 visual modes are not supported by
1144 // CopyFromBackingStore().
CaptureSnapshotFromBackingStore(BackingStore * backing_store)1145 bool CaptureVisibleTabFunction::CaptureSnapshotFromBackingStore(
1146 BackingStore* backing_store) {
1147
1148 skia::PlatformCanvas temp_canvas;
1149 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()),
1150 &temp_canvas)) {
1151 return false;
1152 }
1153 VLOG(1) << "captureVisibleTab() got image from backing store.";
1154
1155 SendResultFromBitmap(
1156 temp_canvas.getTopPlatformDevice().accessBitmap(false));
1157 return true;
1158 }
1159
1160 // If a backing store was not available in CaptureVisibleTabFunction::RunImpl,
1161 // than the renderer was asked for a snapshot. Listen for a notification
1162 // that the snapshot is available.
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)1163 void CaptureVisibleTabFunction::Observe(NotificationType type,
1164 const NotificationSource& source,
1165 const NotificationDetails& details) {
1166 DCHECK(type == NotificationType::TAB_SNAPSHOT_TAKEN);
1167
1168 const SkBitmap *screen_capture = Details<const SkBitmap>(details).ptr();
1169 const bool error = screen_capture->empty();
1170
1171 if (error) {
1172 error_ = keys::kInternalVisibleTabCaptureError;
1173 SendResponse(false);
1174 } else {
1175 VLOG(1) << "captureVisibleTab() got image from renderer.";
1176 SendResultFromBitmap(*screen_capture);
1177 }
1178
1179 Release(); // Balanced in CaptureVisibleTabFunction::RunImpl().
1180 }
1181
1182 // Turn a bitmap of the screen into an image, set that image as the result,
1183 // and call SendResponse().
SendResultFromBitmap(const SkBitmap & screen_capture)1184 void CaptureVisibleTabFunction::SendResultFromBitmap(
1185 const SkBitmap& screen_capture) {
1186 scoped_refptr<RefCountedBytes> image_data(new RefCountedBytes);
1187 SkAutoLockPixels screen_capture_lock(screen_capture);
1188 bool encoded = false;
1189 std::string mime_type;
1190 switch (image_format_) {
1191 case FORMAT_JPEG:
1192 encoded = gfx::JPEGCodec::Encode(
1193 reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)),
1194 gfx::JPEGCodec::FORMAT_SkBitmap,
1195 screen_capture.width(),
1196 screen_capture.height(),
1197 static_cast<int>(screen_capture.rowBytes()),
1198 image_quality_,
1199 &image_data->data);
1200 mime_type = keys::kMimeTypeJpeg;
1201 break;
1202 case FORMAT_PNG:
1203 encoded = gfx::PNGCodec::EncodeBGRASkBitmap(
1204 screen_capture,
1205 true, // Discard transparency.
1206 &image_data->data);
1207 mime_type = keys::kMimeTypePng;
1208 break;
1209 default:
1210 NOTREACHED() << "Invalid image format.";
1211 }
1212
1213 if (!encoded) {
1214 error_ = ExtensionErrorUtils::FormatErrorMessage(
1215 keys::kInternalVisibleTabCaptureError, "");
1216 SendResponse(false);
1217 return;
1218 }
1219
1220 std::string base64_result;
1221 std::string stream_as_string;
1222 stream_as_string.resize(image_data->data.size());
1223 memcpy(&stream_as_string[0],
1224 reinterpret_cast<const char*>(&image_data->data[0]),
1225 image_data->data.size());
1226
1227 base::Base64Encode(stream_as_string, &base64_result);
1228 base64_result.insert(0, base::StringPrintf("data:%s;base64,",
1229 mime_type.c_str()));
1230 result_.reset(new StringValue(base64_result));
1231 SendResponse(true);
1232 }
1233
RunImpl()1234 bool DetectTabLanguageFunction::RunImpl() {
1235 int tab_id = 0;
1236 Browser* browser = NULL;
1237 TabContentsWrapper* contents = NULL;
1238
1239 // If |tab_id| is specified, look for it. Otherwise default to selected tab
1240 // in the current window.
1241 if (HasOptionalArgument(0)) {
1242 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
1243 if (!GetTabById(tab_id, profile(), include_incognito(),
1244 &browser, NULL, &contents, NULL, &error_)) {
1245 return false;
1246 }
1247 if (!browser || !contents)
1248 return false;
1249 } else {
1250 browser = GetCurrentBrowser();
1251 if (!browser)
1252 return false;
1253 contents = browser->tabstrip_model()->GetSelectedTabContents();
1254 if (!contents)
1255 return false;
1256 }
1257
1258 if (contents->controller().needs_reload()) {
1259 // If the tab hasn't been loaded, don't wait for the tab to load.
1260 error_ = keys::kCannotDetermineLanguageOfUnloadedTab;
1261 return false;
1262 }
1263
1264 AddRef(); // Balanced in GotLanguage()
1265
1266 TranslateTabHelper* helper = contents->translate_tab_helper();
1267 if (!helper->language_state().original_language().empty()) {
1268 // Delay the callback invocation until after the current JS call has
1269 // returned.
1270 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
1271 this, &DetectTabLanguageFunction::GotLanguage,
1272 helper->language_state().original_language()));
1273 return true;
1274 }
1275 // The tab contents does not know its language yet. Let's wait until it
1276 // receives it, or until the tab is closed/navigates to some other page.
1277 registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED,
1278 Source<TabContents>(contents->tab_contents()));
1279 registrar_.Add(this, NotificationType::TAB_CLOSING,
1280 Source<NavigationController>(&(contents->controller())));
1281 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
1282 Source<NavigationController>(&(contents->controller())));
1283 return true;
1284 }
1285
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)1286 void DetectTabLanguageFunction::Observe(NotificationType type,
1287 const NotificationSource& source,
1288 const NotificationDetails& details) {
1289 std::string language;
1290 if (type == NotificationType::TAB_LANGUAGE_DETERMINED)
1291 language = *Details<std::string>(details).ptr();
1292
1293 registrar_.RemoveAll();
1294
1295 // Call GotLanguage in all cases as we want to guarantee the callback is
1296 // called for every API call the extension made.
1297 GotLanguage(language);
1298 }
1299
GotLanguage(const std::string & language)1300 void DetectTabLanguageFunction::GotLanguage(const std::string& language) {
1301 result_.reset(Value::CreateStringValue(language.c_str()));
1302 SendResponse(true);
1303
1304 Release(); // Balanced in Run()
1305 }
1306
1307 // static helpers
1308 // TODO(jhawkins): Move these to unnamed namespace and remove static modifier.
1309
GetBrowserInProfileWithId(Profile * profile,const int window_id,bool include_incognito,std::string * error_message)1310 static Browser* GetBrowserInProfileWithId(Profile* profile,
1311 const int window_id,
1312 bool include_incognito,
1313 std::string* error_message) {
1314 Profile* incognito_profile =
1315 include_incognito && profile->HasOffTheRecordProfile() ?
1316 profile->GetOffTheRecordProfile() : NULL;
1317 for (BrowserList::const_iterator browser = BrowserList::begin();
1318 browser != BrowserList::end(); ++browser) {
1319 if (((*browser)->profile() == profile ||
1320 (*browser)->profile() == incognito_profile) &&
1321 ExtensionTabUtil::GetWindowId(*browser) == window_id)
1322 return *browser;
1323 }
1324
1325 if (error_message)
1326 *error_message = ExtensionErrorUtils::FormatErrorMessage(
1327 keys::kWindowNotFoundError, base::IntToString(window_id));
1328
1329 return NULL;
1330 }
1331
GetTabById(int tab_id,Profile * profile,bool include_incognito,Browser ** browser,TabStripModel ** tab_strip,TabContentsWrapper ** contents,int * tab_index,std::string * error_message)1332 static bool GetTabById(int tab_id, Profile* profile,
1333 bool include_incognito,
1334 Browser** browser,
1335 TabStripModel** tab_strip,
1336 TabContentsWrapper** contents,
1337 int* tab_index,
1338 std::string* error_message) {
1339 if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito,
1340 browser, tab_strip, contents, tab_index))
1341 return true;
1342
1343 if (error_message)
1344 *error_message = ExtensionErrorUtils::FormatErrorMessage(
1345 keys::kTabNotFoundError, base::IntToString(tab_id));
1346
1347 return false;
1348 }
1349
GetWindowTypeText(Browser::Type type)1350 static std::string GetWindowTypeText(Browser::Type type) {
1351 if (type == Browser::TYPE_APP_PANEL &&
1352 CommandLine::ForCurrentProcess()->HasSwitch(
1353 switches::kEnableExperimentalExtensionApis))
1354 return keys::kWindowTypeValuePanel;
1355
1356 if ((type & Browser::TYPE_POPUP) == Browser::TYPE_POPUP)
1357 return keys::kWindowTypeValuePopup;
1358
1359 if ((type & Browser::TYPE_APP) == Browser::TYPE_APP)
1360 return keys::kWindowTypeValueApp;
1361
1362 DCHECK(type == Browser::TYPE_NORMAL);
1363 return keys::kWindowTypeValueNormal;
1364 }
1365
ResolvePossiblyRelativeURL(const std::string & url_string,const Extension * extension)1366 static GURL ResolvePossiblyRelativeURL(const std::string& url_string,
1367 const Extension* extension) {
1368 GURL url = GURL(url_string);
1369 if (!url.is_valid())
1370 url = extension->GetResourceURL(url_string);
1371
1372 return url;
1373 }
1374