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