extension_tabs_module.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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 "base/base64.h"
8#include "base/string_number_conversions.h"
9#include "base/string_util.h"
10#include "base/stringprintf.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/browser.h"
13#include "chrome/browser/browser_list.h"
14#include "chrome/browser/browser_window.h"
15#include "chrome/browser/extensions/extension_function_dispatcher.h"
16#include "chrome/browser/extensions/extension_host.h"
17#include "chrome/browser/extensions/extension_infobar_delegate.h"
18#include "chrome/browser/extensions/extension_tabs_module_constants.h"
19#include "chrome/browser/extensions/extensions_service.h"
20#include "chrome/browser/profile.h"
21#include "chrome/browser/renderer_host/backing_store.h"
22#include "chrome/browser/renderer_host/render_view_host.h"
23#include "chrome/browser/renderer_host/render_view_host_delegate.h"
24#include "chrome/browser/tab_contents/navigation_entry.h"
25#include "chrome/browser/tab_contents/tab_contents.h"
26#include "chrome/browser/tabs/tab_strip_model.h"
27#include "chrome/browser/window_sizer.h"
28#include "chrome/common/extensions/extension.h"
29#include "chrome/common/extensions/extension_error_utils.h"
30#include "chrome/common/url_constants.h"
31#include "gfx/codec/jpeg_codec.h"
32#include "gfx/codec/png_codec.h"
33#include "skia/ext/image_operations.h"
34#include "skia/ext/platform_canvas.h"
35#include "third_party/skia/include/core/SkBitmap.h"
36
37namespace keys = extension_tabs_module_constants;
38
39const int CaptureVisibleTabFunction::kDefaultQuality = 90;
40
41// Forward declare static helper functions defined below.
42
43// |error_message| can optionally be passed in a will be set with an appropriate
44// message if the window cannot be found by id.
45static Browser* GetBrowserInProfileWithId(Profile* profile,
46                                          const int window_id,
47                                          bool include_incognito,
48                                          std::string* error_message);
49
50// |error_message| can optionally be passed in and will be set with an
51// appropriate message if the tab cannot be found by id.
52static bool GetTabById(int tab_id, Profile* profile,
53                       bool include_incognito,
54                       Browser** browser,
55                       TabStripModel** tab_strip,
56                       TabContents** contents,
57                       int* tab_index, std::string* error_message);
58
59// Takes |url_string| and returns a GURL which is either valid and absolute
60// or invalid. If |url_string| is not directly interpretable as a valid (it is
61// likely a relative URL) an attempt is made to resolve it. |extension| is
62// provided so it can be resolved relative to its extension base
63// (chrome-extension://<id>/). Using the source frame url would be more correct,
64// but because the api shipped with urls resolved relative to their extension
65// base, we decided it wasn't worth breaking existing extensions to fix.
66static GURL ResolvePossiblyRelativeURL(std::string url_string,
67                                       Extension* extension);
68
69// Return the type name for a browser window type.
70static std::string GetWindowTypeText(Browser::Type type);
71
72int ExtensionTabUtil::GetWindowId(const Browser* browser) {
73  return browser->session_id().id();
74}
75
76int ExtensionTabUtil::GetTabId(const TabContents* tab_contents) {
77  return tab_contents->controller().session_id().id();
78}
79
80std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
81  return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
82}
83
84int ExtensionTabUtil::GetWindowIdOfTab(const TabContents* tab_contents) {
85  return tab_contents->controller().window_id().id();
86}
87
88DictionaryValue* ExtensionTabUtil::CreateTabValue(
89    const TabContents* contents) {
90  // Find the tab strip and index of this guy.
91  for (BrowserList::const_iterator it = BrowserList::begin();
92      it != BrowserList::end(); ++it) {
93    TabStripModel* tab_strip = (*it)->tabstrip_model();
94    int tab_index = tab_strip->GetIndexOfTabContents(contents);
95    if (tab_index != -1) {
96      return ExtensionTabUtil::CreateTabValue(contents, tab_strip, tab_index);
97    }
98  }
99
100  // Couldn't find it.  This can happen if the tab is being dragged.
101  return ExtensionTabUtil::CreateTabValue(contents, NULL, -1);
102}
103
104ListValue* ExtensionTabUtil::CreateTabList(const Browser* browser) {
105  ListValue* tab_list = new ListValue();
106  TabStripModel* tab_strip = browser->tabstrip_model();
107  for (int i = 0; i < tab_strip->count(); ++i) {
108    tab_list->Append(ExtensionTabUtil::CreateTabValue(
109        tab_strip->GetTabContentsAt(i), tab_strip, i));
110  }
111
112  return tab_list;
113}
114
115DictionaryValue* ExtensionTabUtil::CreateTabValue(
116    const TabContents* contents, TabStripModel* tab_strip, int tab_index) {
117  DictionaryValue* result = new DictionaryValue();
118  result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetTabId(contents));
119  result->SetInteger(keys::kIndexKey, tab_index);
120  result->SetInteger(keys::kWindowIdKey,
121                     ExtensionTabUtil::GetWindowIdOfTab(contents));
122  result->SetString(keys::kUrlKey, contents->GetURL().spec());
123  result->SetString(keys::kStatusKey, GetTabStatusText(contents->is_loading()));
124  result->SetBoolean(keys::kSelectedKey,
125                     tab_strip && tab_index == tab_strip->selected_index());
126  result->SetString(keys::kTitleKey, contents->GetTitle());
127  result->SetBoolean(keys::kIncognitoKey,
128                     contents->profile()->IsOffTheRecord());
129
130  if (!contents->is_loading()) {
131    NavigationEntry* entry = contents->controller().GetActiveEntry();
132    if (entry) {
133      if (entry->favicon().is_valid())
134        result->SetString(keys::kFavIconUrlKey, entry->favicon().url().spec());
135    }
136  }
137
138  return result;
139}
140
141// if |populate| is true, each window gets a list property |tabs| which contains
142// fully populated tab objects.
143DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser,
144                                                     bool populate_tabs) {
145  DCHECK(browser);
146  DCHECK(browser->window());
147  DictionaryValue* result = new DictionaryValue();
148  result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetWindowId(browser));
149  result->SetBoolean(keys::kIncognitoKey,
150                     browser->profile()->IsOffTheRecord());
151  result->SetBoolean(keys::kFocusedKey, browser->window()->IsActive());
152  gfx::Rect bounds = browser->window()->GetRestoredBounds();
153
154  result->SetInteger(keys::kLeftKey, bounds.x());
155  result->SetInteger(keys::kTopKey, bounds.y());
156  result->SetInteger(keys::kWidthKey, bounds.width());
157  result->SetInteger(keys::kHeightKey, bounds.height());
158  result->SetString(keys::kWindowTypeKey, GetWindowTypeText(browser->type()));
159
160  if (populate_tabs) {
161    result->Set(keys::kTabsKey, ExtensionTabUtil::CreateTabList(browser));
162  }
163
164  return result;
165}
166
167bool ExtensionTabUtil::GetDefaultTab(Browser* browser, TabContents** contents,
168                                     int* tab_id) {
169  DCHECK(browser);
170  DCHECK(contents);
171  DCHECK(tab_id);
172
173  *contents = browser->tabstrip_model()->GetSelectedTabContents();
174  if (*contents) {
175    if (tab_id)
176      *tab_id = ExtensionTabUtil::GetTabId(*contents);
177    return true;
178  }
179
180  return false;
181}
182
183bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile,
184                                  bool include_incognito,
185                                  Browser** browser,
186                                  TabStripModel** tab_strip,
187                                  TabContents** contents,
188                                  int* tab_index) {
189  Browser* target_browser;
190  TabStripModel* target_tab_strip;
191  TabContents* target_contents;
192  Profile* incognito_profile =
193      include_incognito && profile->HasOffTheRecordProfile() ?
194          profile->GetOffTheRecordProfile() : NULL;
195  for (BrowserList::const_iterator iter = BrowserList::begin();
196       iter != BrowserList::end(); ++iter) {
197    target_browser = *iter;
198    if (target_browser->profile() == profile ||
199        target_browser->profile() == incognito_profile) {
200      target_tab_strip = target_browser->tabstrip_model();
201      for (int i = 0; i < target_tab_strip->count(); ++i) {
202        target_contents = target_tab_strip->GetTabContentsAt(i);
203        if (target_contents->controller().session_id().id() == tab_id) {
204          if (browser)
205            *browser = target_browser;
206          if (tab_strip)
207            *tab_strip = target_tab_strip;
208          if (contents)
209            *contents = target_contents;
210          if (tab_index)
211            *tab_index = i;
212          return true;
213        }
214      }
215    }
216  }
217  return false;
218}
219
220// Windows ---------------------------------------------------------------------
221
222bool GetWindowFunction::RunImpl() {
223  int window_id;
224  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
225
226  Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
227                                               include_incognito(), &error_);
228  if (!browser || !browser->window()) {
229    error_ = ExtensionErrorUtils::FormatErrorMessage(
230        keys::kWindowNotFoundError, base::IntToString(window_id));
231    return false;
232  }
233
234  result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
235  return true;
236}
237
238bool GetCurrentWindowFunction::RunImpl() {
239  Browser* browser = GetCurrentBrowser();
240  if (!browser || !browser->window()) {
241    error_ = keys::kNoCurrentWindowError;
242    return false;
243  }
244  result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
245  return true;
246}
247
248bool GetLastFocusedWindowFunction::RunImpl() {
249  Browser* browser = BrowserList::FindBrowserWithType(
250      profile(), Browser::TYPE_ANY, include_incognito());
251  if (!browser || !browser->window()) {
252    error_ = keys::kNoLastFocusedWindowError;
253    return false;
254  }
255  result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
256  return true;
257}
258
259bool GetAllWindowsFunction::RunImpl() {
260  bool populate_tabs = false;
261  if (HasOptionalArgument(0)) {
262    DictionaryValue* args;
263    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
264
265    if (args->HasKey(keys::kPopulateKey)) {
266      EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPopulateKey,
267          &populate_tabs));
268    }
269  }
270
271  result_.reset(new ListValue());
272  Profile* incognito_profile =
273      include_incognito() && profile()->HasOffTheRecordProfile() ?
274          profile()->GetOffTheRecordProfile() : NULL;
275  for (BrowserList::const_iterator browser = BrowserList::begin();
276    browser != BrowserList::end(); ++browser) {
277      // Only examine browsers in the current profile that have windows.
278      if (((*browser)->profile() == profile() ||
279           (*browser)->profile() == incognito_profile) &&
280          (*browser)->window()) {
281        static_cast<ListValue*>(result_.get())->
282          Append(ExtensionTabUtil::CreateWindowValue(*browser, populate_tabs));
283      }
284  }
285
286  return true;
287}
288
289bool CreateWindowFunction::RunImpl() {
290  GURL url;
291  DictionaryValue* args = NULL;
292
293  if (HasOptionalArgument(0))
294    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
295
296  // Look for optional url.
297  if (args) {
298    std::string url_string;
299    if (args->HasKey(keys::kUrlKey)) {
300      EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey,
301                                                  &url_string));
302      url = ResolvePossiblyRelativeURL(url_string, GetExtension());
303      if (!url.is_valid()) {
304        error_ = ExtensionErrorUtils::FormatErrorMessage(
305            keys::kInvalidUrlError, url_string);
306        return false;
307      }
308    }
309  }
310
311  // Try to position the new browser relative its originating browser window.
312  gfx::Rect empty_bounds;
313  gfx::Rect bounds;
314  bool maximized;
315  // The call offsets the bounds by kWindowTilePixels (defined in WindowSizer to
316  // be 10)
317  //
318  // NOTE(rafaelw): It's ok if GetCurrentBrowser() returns NULL here.
319  // GetBrowserWindowBounds will default to saved "default" values for the app.
320  WindowSizer::GetBrowserWindowBounds(std::string(), empty_bounds,
321                                      GetCurrentBrowser(), &bounds,
322                                      &maximized);
323
324  Profile* window_profile = profile();
325  Browser::Type window_type = Browser::TYPE_NORMAL;
326
327  if (args) {
328    // Any part of the bounds can optionally be set by the caller.
329    int bounds_val;
330    if (args->HasKey(keys::kLeftKey)) {
331      EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey,
332                                                   &bounds_val));
333      bounds.set_x(bounds_val);
334    }
335
336    if (args->HasKey(keys::kTopKey)) {
337      EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTopKey,
338                                                   &bounds_val));
339      bounds.set_y(bounds_val);
340    }
341
342    if (args->HasKey(keys::kWidthKey)) {
343      EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey,
344                                                   &bounds_val));
345      bounds.set_width(bounds_val);
346    }
347
348    if (args->HasKey(keys::kHeightKey)) {
349      EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kHeightKey,
350                                                   &bounds_val));
351      bounds.set_height(bounds_val);
352    }
353
354    bool incognito = false;
355    if (args->HasKey(keys::kIncognitoKey)) {
356      EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey,
357                                                   &incognito));
358      if (incognito)
359        window_profile = window_profile->GetOffTheRecordProfile();
360    }
361
362    std::string type_str;
363    if (args->HasKey(keys::kWindowTypeKey)) {
364      EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kWindowTypeKey,
365                                                  &type_str));
366      if (type_str == keys::kWindowTypeValueNormal) {
367        window_type = Browser::TYPE_NORMAL;
368      } else if (type_str == keys::kWindowTypeValuePopup) {
369        window_type = Browser::TYPE_POPUP;
370      } else {
371        EXTENSION_FUNCTION_VALIDATE(false);
372      }
373    }
374  }
375
376  Browser* new_window = new Browser(window_type, window_profile);
377  new_window->CreateBrowserWindow();
378  new_window->AddTabWithURL(url, GURL(), PageTransition::LINK, -1,
379                            TabStripModel::ADD_SELECTED, NULL, std::string(),
380                            &new_window);
381
382  new_window->window()->SetBounds(bounds);
383  new_window->window()->Show();
384
385  if (new_window->profile()->IsOffTheRecord() && !include_incognito()) {
386    // Don't expose incognito windows if the extension isn't allowed.
387    result_.reset(Value::CreateNullValue());
388  } else {
389    result_.reset(ExtensionTabUtil::CreateWindowValue(new_window, false));
390  }
391
392  return true;
393}
394
395bool UpdateWindowFunction::RunImpl() {
396  int window_id;
397  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
398  DictionaryValue* update_props;
399  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
400
401  Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
402                                               include_incognito(), &error_);
403  if (!browser || !browser->window()) {
404    error_ = ExtensionErrorUtils::FormatErrorMessage(
405        keys::kWindowNotFoundError, base::IntToString(window_id));
406    return false;
407  }
408
409  gfx::Rect bounds = browser->window()->GetRestoredBounds();
410  // Any part of the bounds can optionally be set by the caller.
411  int bounds_val;
412  if (update_props->HasKey(keys::kLeftKey)) {
413    EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
414        keys::kLeftKey,
415        &bounds_val));
416    bounds.set_x(bounds_val);
417  }
418
419  if (update_props->HasKey(keys::kTopKey)) {
420    EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
421        keys::kTopKey,
422        &bounds_val));
423    bounds.set_y(bounds_val);
424  }
425
426  if (update_props->HasKey(keys::kWidthKey)) {
427    EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
428        keys::kWidthKey,
429        &bounds_val));
430    bounds.set_width(bounds_val);
431  }
432
433  if (update_props->HasKey(keys::kHeightKey)) {
434    EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
435        keys::kHeightKey,
436        &bounds_val));
437    bounds.set_height(bounds_val);
438  }
439
440  browser->window()->SetBounds(bounds);
441  result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false));
442
443  return true;
444}
445
446bool RemoveWindowFunction::RunImpl() {
447  int window_id;
448  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
449
450  Browser* browser = GetBrowserInProfileWithId(profile(), window_id,
451                                               include_incognito(), &error_);
452  if (!browser)
453    return false;
454
455  browser->CloseWindow();
456
457  return true;
458}
459
460// Tabs ------------------------------------------------------------------------
461
462bool GetSelectedTabFunction::RunImpl() {
463  Browser* browser;
464  // windowId defaults to "current" window.
465  int window_id = -1;
466
467  if (HasOptionalArgument(0)) {
468    EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
469    browser = GetBrowserInProfileWithId(profile(), window_id,
470                                        include_incognito(), &error_);
471  } else {
472    browser = GetCurrentBrowser();
473    if (!browser)
474      error_ = keys::kNoCurrentWindowError;
475  }
476  if (!browser)
477    return false;
478
479  TabStripModel* tab_strip = browser->tabstrip_model();
480  TabContents* contents = tab_strip->GetSelectedTabContents();
481  if (!contents) {
482    error_ = keys::kNoSelectedTabError;
483    return false;
484  }
485  result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip,
486      tab_strip->selected_index()));
487  return true;
488}
489
490bool GetAllTabsInWindowFunction::RunImpl() {
491  Browser* browser;
492  // windowId defaults to "current" window.
493  int window_id = -1;
494  if (HasOptionalArgument(0)) {
495    EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
496    browser = GetBrowserInProfileWithId(profile(), window_id,
497                                        include_incognito(), &error_);
498  } else {
499    browser = GetCurrentBrowser();
500    if (!browser)
501      error_ = keys::kNoCurrentWindowError;
502  }
503  if (!browser)
504    return false;
505
506  result_.reset(ExtensionTabUtil::CreateTabList(browser));
507
508  return true;
509}
510
511bool CreateTabFunction::RunImpl() {
512  DictionaryValue* args;
513  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
514
515  Browser *browser;
516  // windowId defaults to "current" window.
517  int window_id = -1;
518  if (args->HasKey(keys::kWindowIdKey)) {
519    EXTENSION_FUNCTION_VALIDATE(args->GetInteger(
520        keys::kWindowIdKey, &window_id));
521    browser = GetBrowserInProfileWithId(profile(), window_id,
522                                        include_incognito(), &error_);
523  } else {
524    browser = GetCurrentBrowser();
525    if (!browser)
526      error_ = keys::kNoCurrentWindowError;
527  }
528  if (!browser)
529    return false;
530
531  // TODO(rafaelw): handle setting remaining tab properties:
532  // -title
533  // -favIconUrl
534
535  std::string url_string;
536  GURL url;
537  if (args->HasKey(keys::kUrlKey)) {
538    EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey,
539                                                &url_string));
540    url = ResolvePossiblyRelativeURL(url_string, GetExtension());
541    if (!url.is_valid()) {
542      error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
543                                                       url_string);
544      return false;
545    }
546  }
547
548  // Default to foreground for the new tab. The presence of 'selected' property
549  // will override this default.
550  bool selected = true;
551  if (args->HasKey(keys::kSelectedKey))
552    EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kSelectedKey,
553                                                 &selected));
554  // If index is specified, honor the value, but keep it bound to
555  // 0 <= index <= tab_strip->count()
556  int index = -1;
557  if (args->HasKey(keys::kIndexKey))
558    EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kIndexKey,
559                                                 &index));
560
561  // We can't load extension URLs into incognito windows. Special case to
562  // fall back to a normal window.
563  if (url.SchemeIs(chrome::kExtensionScheme) &&
564      browser->profile()->IsOffTheRecord()) {
565    Profile* profile = browser->profile()->GetOriginalProfile();
566    browser = BrowserList::FindBrowserWithType(profile,
567                                               Browser::TYPE_NORMAL, false);
568    if (!browser) {
569      browser = Browser::Create(profile);
570      browser->window()->Show();
571    }
572  }
573
574  TabStripModel* tab_strip = browser->tabstrip_model();
575
576  if (index < 0) {
577    // Default insert behavior.
578    index = -1;
579  }
580  if (index > tab_strip->count()) {
581    index = tab_strip->count();
582  }
583
584  int add_types = selected ? TabStripModel::ADD_SELECTED :
585                             TabStripModel::ADD_NONE;
586  add_types |= TabStripModel::ADD_FORCE_INDEX;
587  TabContents* contents = browser->AddTabWithURL(url, GURL(),
588      PageTransition::LINK, index, add_types, NULL, std::string(), &browser);
589  index = browser->tabstrip_model()->GetIndexOfTabContents(contents);
590
591  if (selected)
592    contents->Focus();
593
594  // Return data about the newly created tab.
595  if (has_callback())
596    result_.reset(ExtensionTabUtil::CreateTabValue(contents,
597                                                   browser->tabstrip_model(),
598                                                   index));
599
600  return true;
601}
602
603bool GetTabFunction::RunImpl() {
604  int tab_id;
605  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
606
607  TabStripModel* tab_strip = NULL;
608  TabContents* contents = NULL;
609  int tab_index = -1;
610  if (!GetTabById(tab_id, profile(), include_incognito(),
611                  NULL, &tab_strip, &contents, &tab_index, &error_))
612    return false;
613
614  result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip,
615      tab_index));
616  return true;
617}
618
619bool GetCurrentTabFunction::RunImpl() {
620  DCHECK(dispatcher());
621
622  TabContents* contents = dispatcher()->delegate()->associated_tab_contents();
623  if (contents)
624    result_.reset(ExtensionTabUtil::CreateTabValue(contents));
625
626  return true;
627}
628
629bool UpdateTabFunction::RunImpl() {
630  int tab_id;
631  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
632  DictionaryValue* update_props;
633  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
634
635  TabStripModel* tab_strip = NULL;
636  TabContents* contents = NULL;
637  int tab_index = -1;
638  if (!GetTabById(tab_id, profile(), include_incognito(),
639                  NULL, &tab_strip, &contents, &tab_index, &error_))
640    return false;
641
642  NavigationController& controller = contents->controller();
643
644  // TODO(rafaelw): handle setting remaining tab properties:
645  // -title
646  // -favIconUrl
647
648  // Navigate the tab to a new location if the url different.
649  std::string url_string;
650  if (update_props->HasKey(keys::kUrlKey)) {
651    EXTENSION_FUNCTION_VALIDATE(update_props->GetString(
652        keys::kUrlKey, &url_string));
653    GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension());
654
655    if (!url.is_valid()) {
656      error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
657                                                       url_string);
658      return false;
659    }
660
661    // JavaScript URLs can do the same kinds of things as cross-origin XHR, so
662    // we need to check host permissions before allowing them.
663    if (url.SchemeIs(chrome::kJavaScriptScheme)) {
664      if (!profile()->GetExtensionsService()->CanExecuteScriptOnHost(
665          GetExtension(), contents->GetURL(), &error_))
666        return false;
667
668      // TODO(aa): How does controller queue URLs? Is there any chance that this
669      // JavaScript URL will end up applying to something other than
670      // controller->GetURL()?
671    }
672
673    if (tab_strip->IsTabPinned(tab_index)) {
674      // Don't allow changing the url of pinned tabs.
675      error_ = keys::kCannotUpdatePinnedTab;
676      return false;
677    }
678
679    controller.LoadURL(url, GURL(), PageTransition::LINK);
680
681    // The URL of a tab contents never actually changes to a JavaScript URL, so
682    // this check only makes sense in other cases.
683    if (!url.SchemeIs(chrome::kJavaScriptScheme))
684      DCHECK_EQ(url.spec(), contents->GetURL().spec());
685  }
686
687  bool selected = false;
688  // TODO(rafaelw): Setting |selected| from js doesn't make much sense.
689  // Move tab selection management up to window.
690  if (update_props->HasKey(keys::kSelectedKey)) {
691    EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(
692        keys::kSelectedKey,
693        &selected));
694    if (selected) {
695      if (tab_strip->selected_index() != tab_index) {
696        tab_strip->SelectTabContentsAt(tab_index, false);
697        DCHECK_EQ(contents, tab_strip->GetSelectedTabContents());
698      }
699      contents->Focus();
700    }
701  }
702
703  if (has_callback())
704    result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip,
705        tab_index));
706
707  return true;
708}
709
710bool MoveTabFunction::RunImpl() {
711  int tab_id;
712  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
713  DictionaryValue* update_props;
714  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
715
716  int new_index;
717  EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
718      keys::kIndexKey, &new_index));
719  EXTENSION_FUNCTION_VALIDATE(new_index >= 0);
720
721  Browser* source_browser = NULL;
722  TabStripModel* source_tab_strip = NULL;
723  TabContents* contents = NULL;
724  int tab_index = -1;
725  if (!GetTabById(tab_id, profile(), include_incognito(),
726                  &source_browser, &source_tab_strip, &contents,
727                  &tab_index, &error_))
728    return false;
729
730  if (source_browser->type() != Browser::TYPE_NORMAL) {
731    error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError;
732    return false;
733  }
734
735  if (update_props->HasKey(keys::kWindowIdKey)) {
736    Browser* target_browser;
737    int window_id;
738    EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
739        keys::kWindowIdKey, &window_id));
740    target_browser = GetBrowserInProfileWithId(profile(), window_id,
741                                               include_incognito(), &error_);
742    if (!target_browser)
743      return false;
744
745    if (target_browser->type() != Browser::TYPE_NORMAL) {
746      error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError;
747      return false;
748    }
749
750    // If windowId is different from the current window, move between windows.
751    if (ExtensionTabUtil::GetWindowId(target_browser) !=
752        ExtensionTabUtil::GetWindowId(source_browser)) {
753      TabStripModel* target_tab_strip = target_browser->tabstrip_model();
754      contents = source_tab_strip->DetachTabContentsAt(tab_index);
755      if (!contents) {
756        error_ = ExtensionErrorUtils::FormatErrorMessage(
757            keys::kTabNotFoundError, base::IntToString(tab_id));
758        return false;
759      }
760
761      // Clamp move location to the last position.
762      // This is ">" because it can append to a new index position.
763      if (new_index > target_tab_strip->count())
764        new_index = target_tab_strip->count();
765
766      target_tab_strip->InsertTabContentsAt(new_index, contents,
767                                            TabStripModel::ADD_NONE);
768
769      if (has_callback())
770        result_.reset(ExtensionTabUtil::CreateTabValue(contents,
771            target_tab_strip, new_index));
772
773      return true;
774    }
775  }
776
777  // Perform a simple within-window move.
778  // Clamp move location to the last position.
779  // This is ">=" because the move must be to an existing location.
780  if (new_index >= source_tab_strip->count())
781    new_index = source_tab_strip->count() - 1;
782
783  if (new_index != tab_index)
784    source_tab_strip->MoveTabContentsAt(tab_index, new_index, false);
785
786  if (has_callback())
787    result_.reset(ExtensionTabUtil::CreateTabValue(contents, source_tab_strip,
788        new_index));
789  return true;
790}
791
792
793bool RemoveTabFunction::RunImpl() {
794  int tab_id;
795  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
796
797  Browser* browser = NULL;
798  TabContents* contents = NULL;
799  if (!GetTabById(tab_id, profile(), include_incognito(),
800                  &browser, NULL, &contents, NULL, &error_))
801    return false;
802
803  int tab_index = browser->GetIndexOfController(&contents->controller());
804  if (browser->tabstrip_model()->IsPhantomTab(tab_index)) {
805    // Don't allow closing phantom tabs.
806    error_ = keys::kCannotRemovePhantomTab;
807    return false;
808  }
809
810  // Close the tab in this convoluted way, since there's a chance that the tab
811  // is being dragged, or we're in some other nested event loop. This code path
812  // should ensure that the tab is safely closed under such circumstances,
813  // whereas |Browser::CloseTabContents()| does not.
814  RenderViewHost* render_view_host = contents->render_view_host();
815  render_view_host->delegate()->Close(render_view_host);
816  return true;
817}
818
819bool CaptureVisibleTabFunction::RunImpl() {
820  Browser* browser;
821  // windowId defaults to "current" window.
822  int window_id = -1;
823
824  if (HasOptionalArgument(0)) {
825    EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
826    browser = GetBrowserInProfileWithId(profile(), window_id,
827                                        include_incognito(), &error_);
828  } else {
829    browser = GetCurrentBrowser();
830  }
831
832  if (!browser) {
833    error_ = keys::kNoCurrentWindowError;
834    return false;
835  }
836
837  image_format_ = FORMAT_JPEG;  // Default format is JPEG.
838  image_quality_ = kDefaultQuality;  // Default quality setting.
839
840  if (HasOptionalArgument(1)) {
841    DictionaryValue* options;
842    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
843
844    if (options->HasKey(keys::kFormatKey)) {
845      std::string format;
846      EXTENSION_FUNCTION_VALIDATE(
847          options->GetString(keys::kFormatKey, &format));
848
849      if (format == keys::kFormatValueJpeg) {
850        image_format_ = FORMAT_JPEG;
851      } else if (format == keys::kFormatValuePng) {
852        image_format_ = FORMAT_PNG;
853      } else {
854        // Schema validation should make this unreachable.
855        EXTENSION_FUNCTION_VALIDATE(0);
856      }
857    }
858
859    if (options->HasKey(keys::kQualityKey)) {
860      EXTENSION_FUNCTION_VALIDATE(
861          options->GetInteger(keys::kQualityKey, &image_quality_));
862    }
863  }
864
865  TabContents* tab_contents = browser->GetSelectedTabContents();
866  if (!tab_contents) {
867    error_ = keys::kInternalVisibleTabCaptureError;
868    return false;
869  }
870  RenderViewHost* render_view_host = tab_contents->render_view_host();
871
872  // If a backing store is cached for the tab we want to capture,
873  // and it can be copied into a bitmap, then use it to generate the image.
874  BackingStore* backing_store = render_view_host->GetBackingStore(false);
875  if (backing_store && CaptureSnapshotFromBackingStore(backing_store))
876    return true;
877
878  // Ask the renderer for a snapshot of the tab.
879  render_view_host->CaptureSnapshot();
880  registrar_.Add(this,
881                 NotificationType::TAB_SNAPSHOT_TAKEN,
882                 NotificationService::AllSources());
883  AddRef();  // Balanced in CaptureVisibleTabFunction::Observe().
884
885  return true;
886}
887
888// Build the image of a tab's contents out of a backing store.
889// This may fail if we can not copy a backing store into a bitmap.
890// For example, some uncommon X11 visual modes are not supported by
891// CopyFromBackingStore().
892bool CaptureVisibleTabFunction::CaptureSnapshotFromBackingStore(
893    BackingStore* backing_store) {
894
895  skia::PlatformCanvas temp_canvas;
896  if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()),
897                                           &temp_canvas)) {
898    return false;
899  }
900  LOG(INFO) << "captureVisibleTab() Got image from backing store.";
901
902  SendResultFromBitmap(
903      temp_canvas.getTopPlatformDevice().accessBitmap(false));
904  return true;
905}
906
907// If a backing store was not available in CaptureVisibleTabFunction::RunImpl,
908// than the renderer was asked for a snapshot.  Listen for a notification
909// that the snapshot is available.
910void CaptureVisibleTabFunction::Observe(NotificationType type,
911                                        const NotificationSource& source,
912                                        const NotificationDetails& details) {
913  DCHECK(type == NotificationType::TAB_SNAPSHOT_TAKEN);
914
915  const SkBitmap *screen_capture = Details<const SkBitmap>(details).ptr();
916  const bool error = screen_capture->empty();
917
918  if (error) {
919    error_ = keys::kInternalVisibleTabCaptureError;
920    SendResponse(false);
921  } else {
922    LOG(INFO) << "captureVisibleTab() Got image from renderer.";
923    SendResultFromBitmap(*screen_capture);
924  }
925
926  Release();  // Balanced in CaptureVisibleTabFunction::RunImpl().
927}
928
929// Turn a bitmap of the screen into an image, set that image as the result,
930// and call SendResponse().
931void CaptureVisibleTabFunction::SendResultFromBitmap(
932    const SkBitmap& screen_capture) {
933  scoped_refptr<RefCountedBytes> image_data(new RefCountedBytes);
934  SkAutoLockPixels screen_capture_lock(screen_capture);
935  bool encoded = false;
936  std::string mime_type;
937  switch (image_format_) {
938    case FORMAT_JPEG:
939      encoded = gfx::JPEGCodec::Encode(
940          reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)),
941          gfx::JPEGCodec::FORMAT_BGRA,
942          screen_capture.width(),
943          screen_capture.height(),
944          static_cast<int>(screen_capture.rowBytes()),
945          image_quality_,
946          &image_data->data);
947      mime_type = keys::kMimeTypeJpeg;
948      break;
949    case FORMAT_PNG:
950      encoded = gfx::PNGCodec::EncodeBGRASkBitmap(
951          screen_capture,
952          true,  // Discard transparency.
953          &image_data->data);
954      mime_type = keys::kMimeTypePng;
955      break;
956    default:
957      NOTREACHED() << "Invalid image format.";
958  }
959
960  if (!encoded) {
961    error_ = ExtensionErrorUtils::FormatErrorMessage(
962        keys::kInternalVisibleTabCaptureError, "");
963    SendResponse(false);
964    return;
965  }
966
967  std::string base64_result;
968  std::string stream_as_string;
969  stream_as_string.resize(image_data->data.size());
970  memcpy(&stream_as_string[0],
971      reinterpret_cast<const char*>(&image_data->data[0]),
972      image_data->data.size());
973
974  base::Base64Encode(stream_as_string, &base64_result);
975  base64_result.insert(0, base::StringPrintf("data:%s;base64,",
976                                             mime_type.c_str()));
977  result_.reset(new StringValue(base64_result));
978  SendResponse(true);
979}
980
981bool DetectTabLanguageFunction::RunImpl() {
982  int tab_id = 0;
983  Browser* browser = NULL;
984  TabContents* contents = NULL;
985
986  // If |tab_id| is specified, look for it. Otherwise default to selected tab
987  // in the current window.
988  if (HasOptionalArgument(0)) {
989    EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
990    if (!GetTabById(tab_id, profile(), include_incognito(),
991                    &browser, NULL, &contents, NULL, &error_)) {
992      return false;
993    }
994    if (!browser || !contents)
995      return false;
996  } else {
997    browser = GetCurrentBrowser();
998    if (!browser)
999      return false;
1000    contents = browser->tabstrip_model()->GetSelectedTabContents();
1001    if (!contents)
1002      return false;
1003  }
1004
1005  if (contents->controller().needs_reload()) {
1006    // If the tab hasn't been loaded, such as happens with phantom tabs, don't
1007    // wait for the tab to load, instead return.
1008    error_ = keys::kCannotDetermineLanguageOfUnloadedTab;
1009    return false;
1010  }
1011
1012  AddRef();  // Balanced in GotLanguage()
1013
1014  if (!contents->language_state().original_language().empty()) {
1015    // Delay the callback invocation until after the current JS call has
1016    // returned.
1017    MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
1018        this, &DetectTabLanguageFunction::GotLanguage,
1019        contents->language_state().original_language()));
1020    return true;
1021  }
1022  // The tab contents does not know its language yet.  Let's  wait until it
1023  // receives it, or until the tab is closed/navigates to some other page.
1024  registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED,
1025                 Source<TabContents>(contents));
1026  registrar_.Add(this, NotificationType::TAB_CLOSING,
1027                 Source<NavigationController>(&(contents->controller())));
1028  registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
1029                 Source<NavigationController>(&(contents->controller())));
1030  return true;
1031}
1032
1033void DetectTabLanguageFunction::Observe(NotificationType type,
1034                                        const NotificationSource& source,
1035                                        const NotificationDetails& details) {
1036  std::string language;
1037  if (type == NotificationType::TAB_LANGUAGE_DETERMINED)
1038    language = *Details<std::string>(details).ptr();
1039
1040  registrar_.RemoveAll();
1041
1042  // Call GotLanguage in all cases as we want to guarantee the callback is
1043  // called for every API call the extension made.
1044  GotLanguage(language);
1045}
1046
1047void DetectTabLanguageFunction::GotLanguage(const std::string& language) {
1048  result_.reset(Value::CreateStringValue(language.c_str()));
1049  SendResponse(true);
1050
1051  Release();  // Balanced in Run()
1052}
1053
1054// static helpers
1055
1056static Browser* GetBrowserInProfileWithId(Profile* profile,
1057                                          const int window_id,
1058                                          bool include_incognito,
1059                                          std::string* error_message) {
1060  Profile* incognito_profile =
1061      include_incognito && profile->HasOffTheRecordProfile() ?
1062          profile->GetOffTheRecordProfile() : NULL;
1063  for (BrowserList::const_iterator browser = BrowserList::begin();
1064       browser != BrowserList::end(); ++browser) {
1065    if (((*browser)->profile() == profile ||
1066         (*browser)->profile() == incognito_profile) &&
1067        ExtensionTabUtil::GetWindowId(*browser) == window_id)
1068      return *browser;
1069  }
1070
1071  if (error_message)
1072    *error_message = ExtensionErrorUtils::FormatErrorMessage(
1073        keys::kWindowNotFoundError, base::IntToString(window_id));
1074
1075  return NULL;
1076}
1077
1078static bool GetTabById(int tab_id, Profile* profile,
1079                       bool include_incognito,
1080                       Browser** browser,
1081                       TabStripModel** tab_strip,
1082                       TabContents** contents,
1083                       int* tab_index,
1084                       std::string* error_message) {
1085  if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito,
1086                                   browser, tab_strip, contents, tab_index))
1087    return true;
1088
1089  if (error_message)
1090    *error_message = ExtensionErrorUtils::FormatErrorMessage(
1091        keys::kTabNotFoundError, base::IntToString(tab_id));
1092
1093  return false;
1094}
1095
1096static std::string GetWindowTypeText(Browser::Type type) {
1097  // Note: for app popups, we report "app".
1098  if ((type & Browser::TYPE_APP) != 0 || type == Browser::TYPE_EXTENSION_APP)
1099    return keys::kWindowTypeValueApp;
1100  if ((type & Browser::TYPE_POPUP) != 0)
1101    return keys::kWindowTypeValuePopup;
1102
1103  DCHECK(type == Browser::TYPE_NORMAL);
1104  return keys::kWindowTypeValueNormal;
1105}
1106
1107static GURL ResolvePossiblyRelativeURL(std::string url_string,
1108                                       Extension* extension) {
1109  GURL url = GURL(url_string);
1110  if (!url.is_valid())
1111    url = extension->GetResourceURL(url_string);
1112
1113  return url;
1114}
1115