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
47namespace keys = extension_tabs_module_constants;
48namespace errors = extension_manifest_errors;
49
50const 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.
56static 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.
63static 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.
77static GURL ResolvePossiblyRelativeURL(const std::string& url_string,
78                                       const Extension* extension);
79
80// Return the type name for a browser window type.
81static std::string GetWindowTypeText(Browser::Type type);
82
83int ExtensionTabUtil::GetWindowId(const Browser* browser) {
84  return browser->session_id().id();
85}
86
87int ExtensionTabUtil::GetTabId(const TabContents* tab_contents) {
88  return tab_contents->controller().session_id().id();
89}
90
91std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
92  return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
93}
94
95int ExtensionTabUtil::GetWindowIdOfTab(const TabContents* tab_contents) {
96  return tab_contents->controller().window_id().id();
97}
98
99DictionaryValue* 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
111ListValue* 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
122DictionaryValue* 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.
152DictionaryValue* 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
180bool 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
201bool 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
218bool 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
255bool 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
271bool 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
281bool 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
292bool 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
322bool 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
522bool 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
589bool 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
612bool 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
641bool 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
662bool 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(&params);
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
769bool 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
786bool 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
796UpdateTabFunction::UpdateTabFunction()
797    : ALLOW_THIS_IN_INITIALIZER_LIST(registrar_(this)) {
798}
799
800bool 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
910bool 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
931void 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
945bool 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
1040bool 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
1065bool 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().
1145bool 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.
1163void 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().
1184void 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
1234bool 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
1286void 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
1300void 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
1310static 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
1332static 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
1350static 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
1366static 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