350ea95dda719866c3ea1c744afb64a35ddfc822
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_tab_util.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/extension_tab_util.h"
6
7 #include "apps/app_window.h"
8 #include "apps/app_window_registry.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
11 #include "chrome/browser/extensions/chrome_extension_function.h"
12 #include "chrome/browser/extensions/tab_helper.h"
13 #include "chrome/browser/extensions/window_controller.h"
14 #include "chrome/browser/extensions/window_controller_list.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/session_id.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_iterator.h"
20 #include "chrome/browser/ui/browser_window.h"
21 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
22 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/common/extensions/api/tabs.h"
25 #include "chrome/common/extensions/manifest_url_handler.h"
26 #include "chrome/common/url_constants.h"
27 #include "components/url_fixer/url_fixer.h"
28 #include "content/public/browser/favicon_status.h"
29 #include "content/public/browser/navigation_entry.h"
30 #include "content/public/browser/web_contents.h"
31 #include "extensions/common/constants.h"
32 #include "extensions/common/error_utils.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/manifest_constants.h"
35 #include "extensions/common/manifest_handlers/incognito_info.h"
36 #include "extensions/common/permissions/api_permission.h"
37 #include "extensions/common/permissions/permissions_data.h"
38 #include "url/gurl.h"
39
40 using apps::AppWindow;
41 using content::NavigationEntry;
42 using content::WebContents;
43
44 namespace extensions {
45
46 namespace {
47
48 namespace keys = tabs_constants;
49
50 WindowController* GetAppWindowController(const WebContents* contents) {
51   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
52   apps::AppWindowRegistry* registry = apps::AppWindowRegistry::Get(profile);
53   if (!registry)
54     return NULL;
55   AppWindow* app_window =
56       registry->GetAppWindowForRenderViewHost(contents->GetRenderViewHost());
57   if (!app_window)
58     return NULL;
59   return WindowControllerList::GetInstance()->FindWindowById(
60       app_window->session_id().id());
61 }
62
63 // |error_message| can optionally be passed in and will be set with an
64 // appropriate message if the window cannot be found by id.
65 Browser* GetBrowserInProfileWithId(Profile* profile,
66                                    const int window_id,
67                                    bool include_incognito,
68                                    std::string* error_message) {
69   Profile* incognito_profile =
70       include_incognito && profile->HasOffTheRecordProfile()
71           ? profile->GetOffTheRecordProfile()
72           : NULL;
73   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
74     Browser* browser = *it;
75     if ((browser->profile() == profile ||
76          browser->profile() == incognito_profile) &&
77         ExtensionTabUtil::GetWindowId(browser) == window_id &&
78         browser->window()) {
79       return browser;
80     }
81   }
82
83   if (error_message)
84     *error_message = ErrorUtils::FormatErrorMessage(
85         keys::kWindowNotFoundError, base::IntToString(window_id));
86
87   return NULL;
88 }
89
90 Browser* CreateBrowser(ChromeUIThreadExtensionFunction* function,
91                        int window_id,
92                        std::string* error) {
93   content::WebContents* web_contents = function->GetAssociatedWebContents();
94   DCHECK(web_contents);
95   DCHECK(web_contents->GetNativeView());
96   DCHECK(!chrome::FindBrowserWithWebContents(web_contents));
97
98   chrome::HostDesktopType desktop_type =
99       chrome::GetHostDesktopTypeForNativeView(web_contents->GetNativeView());
100   Browser::CreateParams params(
101       Browser::TYPE_TABBED, function->GetProfile(), desktop_type);
102   Browser* browser = new Browser(params);
103   browser->window()->Show();
104   return browser;
105 }
106
107 }  // namespace
108
109 ExtensionTabUtil::OpenTabParams::OpenTabParams()
110     : create_browser_if_needed(false) {
111 }
112
113 ExtensionTabUtil::OpenTabParams::~OpenTabParams() {
114 }
115
116 // Opens a new tab for a given extension. Returns NULL and sets |error| if an
117 // error occurs.
118 base::DictionaryValue* ExtensionTabUtil::OpenTab(
119     ChromeUIThreadExtensionFunction* function,
120     const OpenTabParams& params,
121     std::string* error) {
122   // windowId defaults to "current" window.
123   int window_id = extension_misc::kCurrentWindowId;
124   if (params.window_id.get())
125     window_id = *params.window_id;
126
127   Browser* browser = GetBrowserFromWindowID(function, window_id, error);
128   if (!browser) {
129     if (!params.create_browser_if_needed) {
130       return NULL;
131     }
132     browser = CreateBrowser(function, window_id, error);
133     if (!browser)
134       return NULL;
135   }
136
137   // Ensure the selected browser is tabbed.
138   if (!browser->is_type_tabbed() && browser->IsAttemptingToCloseBrowser())
139     browser = chrome::FindTabbedBrowser(function->GetProfile(),
140                                         function->include_incognito(),
141                                         browser->host_desktop_type());
142
143   if (!browser || !browser->window()) {
144     // TODO(rpaquay): Error message?
145     return NULL;
146   }
147
148   // TODO(jstritar): Add a constant, chrome.tabs.TAB_ID_ACTIVE, that
149   // represents the active tab.
150   WebContents* opener = NULL;
151   if (params.opener_tab_id.get()) {
152     int opener_id = *params.opener_tab_id;
153
154     if (!ExtensionTabUtil::GetTabById(opener_id,
155                                       function->GetProfile(),
156                                       function->include_incognito(),
157                                       NULL,
158                                       NULL,
159                                       &opener,
160                                       NULL)) {
161       // TODO(rpaquay): Error message?
162       return NULL;
163     }
164   }
165
166   // TODO(rafaelw): handle setting remaining tab properties:
167   // -title
168   // -favIconUrl
169
170   GURL url;
171   if (params.url.get()) {
172     std::string url_string= *params.url;
173     url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string,
174                                                        function->extension());
175     if (!url.is_valid()) {
176       *error =
177           ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string);
178       return NULL;
179     }
180   } else {
181     url = GURL(chrome::kChromeUINewTabURL);
182   }
183
184   // Don't let extensions crash the browser or renderers.
185   if (ExtensionTabUtil::IsCrashURL(url)) {
186     *error = keys::kNoCrashBrowserError;
187     return NULL;
188   }
189
190   // Default to foreground for the new tab. The presence of 'active' property
191   // will override this default.
192   bool active = true;
193   if (params.active.get())
194     active = *params.active;
195
196   // Default to not pinning the tab. Setting the 'pinned' property to true
197   // will override this default.
198   bool pinned = false;
199   if (params.pinned.get())
200     pinned = *params.pinned;
201
202   // We can't load extension URLs into incognito windows unless the extension
203   // uses split mode. Special case to fall back to a tabbed window.
204   if (url.SchemeIs(kExtensionScheme) &&
205       !IncognitoInfo::IsSplitMode(function->extension()) &&
206       browser->profile()->IsOffTheRecord()) {
207     Profile* profile = browser->profile()->GetOriginalProfile();
208     chrome::HostDesktopType desktop_type = browser->host_desktop_type();
209
210     browser = chrome::FindTabbedBrowser(profile, false, desktop_type);
211     if (!browser) {
212       browser = new Browser(
213           Browser::CreateParams(Browser::TYPE_TABBED, profile, desktop_type));
214       browser->window()->Show();
215     }
216   }
217
218   // If index is specified, honor the value, but keep it bound to
219   // -1 <= index <= tab_strip->count() where -1 invokes the default behavior.
220   int index = -1;
221   if (params.index.get())
222     index = *params.index;
223
224   TabStripModel* tab_strip = browser->tab_strip_model();
225
226   index = std::min(std::max(index, -1), tab_strip->count());
227
228   int add_types = active ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE;
229   add_types |= TabStripModel::ADD_FORCE_INDEX;
230   if (pinned)
231     add_types |= TabStripModel::ADD_PINNED;
232   chrome::NavigateParams navigate_params(
233       browser, url, content::PAGE_TRANSITION_LINK);
234   navigate_params.disposition =
235       active ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB;
236   navigate_params.tabstrip_index = index;
237   navigate_params.tabstrip_add_types = add_types;
238   chrome::Navigate(&navigate_params);
239
240   // The tab may have been created in a different window, so make sure we look
241   // at the right tab strip.
242   tab_strip = navigate_params.browser->tab_strip_model();
243   int new_index =
244       tab_strip->GetIndexOfWebContents(navigate_params.target_contents);
245   if (opener)
246     tab_strip->SetOpenerOfWebContentsAt(new_index, opener);
247
248   if (active)
249     navigate_params.target_contents->SetInitialFocus();
250
251   // Return data about the newly created tab.
252   return ExtensionTabUtil::CreateTabValue(navigate_params.target_contents,
253                                           tab_strip,
254                                           new_index,
255                                           function->extension());
256 }
257
258 Browser* ExtensionTabUtil::GetBrowserFromWindowID(
259     ChromeUIThreadExtensionFunction* function,
260     int window_id,
261     std::string* error) {
262   if (window_id == extension_misc::kCurrentWindowId) {
263     Browser* result = function->GetCurrentBrowser();
264     if (!result || !result->window()) {
265       if (error)
266         *error = keys::kNoCurrentWindowError;
267       return NULL;
268     }
269     return result;
270   } else {
271     return GetBrowserInProfileWithId(function->GetProfile(),
272                                      window_id,
273                                      function->include_incognito(),
274                                      error);
275   }
276 }
277
278 int ExtensionTabUtil::GetWindowId(const Browser* browser) {
279   return browser->session_id().id();
280 }
281
282 int ExtensionTabUtil::GetWindowIdOfTabStripModel(
283     const TabStripModel* tab_strip_model) {
284   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
285     if (it->tab_strip_model() == tab_strip_model)
286       return GetWindowId(*it);
287   }
288   return -1;
289 }
290
291 int ExtensionTabUtil::GetTabId(const WebContents* web_contents) {
292   return SessionID::IdForTab(web_contents);
293 }
294
295 std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
296   return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
297 }
298
299 int ExtensionTabUtil::GetWindowIdOfTab(const WebContents* web_contents) {
300   return SessionID::IdForWindowContainingTab(web_contents);
301 }
302
303 base::DictionaryValue* ExtensionTabUtil::CreateTabValue(
304     WebContents* contents,
305     TabStripModel* tab_strip,
306     int tab_index,
307     const Extension* extension) {
308   // If we have a matching AppWindow with a controller, get the tab value
309   // from its controller instead.
310   WindowController* controller = GetAppWindowController(contents);
311   if (controller &&
312       (!extension || controller->IsVisibleToExtension(extension))) {
313     return controller->CreateTabValue(extension, tab_index);
314   }
315   base::DictionaryValue* result =
316       CreateTabValue(contents, tab_strip, tab_index);
317   ScrubTabValueForExtension(contents, extension, result);
318   return result;
319 }
320
321 base::ListValue* ExtensionTabUtil::CreateTabList(
322     const Browser* browser,
323     const Extension* extension) {
324   base::ListValue* tab_list = new base::ListValue();
325   TabStripModel* tab_strip = browser->tab_strip_model();
326   for (int i = 0; i < tab_strip->count(); ++i) {
327     tab_list->Append(CreateTabValue(tab_strip->GetWebContentsAt(i),
328                                     tab_strip,
329                                     i,
330                                     extension));
331   }
332
333   return tab_list;
334 }
335
336 base::DictionaryValue* ExtensionTabUtil::CreateTabValue(
337     WebContents* contents,
338     TabStripModel* tab_strip,
339     int tab_index) {
340   // If we have a matching AppWindow with a controller, get the tab value
341   // from its controller instead.
342   WindowController* controller = GetAppWindowController(contents);
343   if (controller)
344     return controller->CreateTabValue(NULL, tab_index);
345
346   if (!tab_strip)
347     ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index);
348
349   base::DictionaryValue* result = new base::DictionaryValue();
350   bool is_loading = contents->IsLoading();
351   result->SetInteger(keys::kIdKey, GetTabId(contents));
352   result->SetInteger(keys::kIndexKey, tab_index);
353   result->SetInteger(keys::kWindowIdKey, GetWindowIdOfTab(contents));
354   result->SetString(keys::kStatusKey, GetTabStatusText(is_loading));
355   result->SetBoolean(keys::kActiveKey,
356                      tab_strip && tab_index == tab_strip->active_index());
357   result->SetBoolean(keys::kSelectedKey,
358                      tab_strip && tab_index == tab_strip->active_index());
359   result->SetBoolean(keys::kHighlightedKey,
360                    tab_strip && tab_strip->IsTabSelected(tab_index));
361   result->SetBoolean(keys::kPinnedKey,
362                      tab_strip && tab_strip->IsTabPinned(tab_index));
363   result->SetBoolean(keys::kIncognitoKey,
364                      contents->GetBrowserContext()->IsOffTheRecord());
365   result->SetInteger(keys::kWidthKey,
366                      contents->GetContainerBounds().size().width());
367   result->SetInteger(keys::kHeightKey,
368                      contents->GetContainerBounds().size().height());
369
370   // Privacy-sensitive fields: these should be stripped off by
371   // ScrubTabValueForExtension if the extension should not see them.
372   result->SetString(keys::kUrlKey, contents->GetURL().spec());
373   result->SetString(keys::kTitleKey, contents->GetTitle());
374   if (!is_loading) {
375     NavigationEntry* entry = contents->GetController().GetVisibleEntry();
376     if (entry && entry->GetFavicon().valid)
377       result->SetString(keys::kFaviconUrlKey, entry->GetFavicon().url.spec());
378   }
379
380   if (tab_strip) {
381     WebContents* opener = tab_strip->GetOpenerOfWebContentsAt(tab_index);
382     if (opener)
383       result->SetInteger(keys::kOpenerTabIdKey, GetTabId(opener));
384   }
385
386   return result;
387 }
388
389 void ExtensionTabUtil::ScrubTabValueForExtension(
390     WebContents* contents,
391     const Extension* extension,
392     base::DictionaryValue* tab_info) {
393   bool has_permission = extension &&
394                         extension->permissions_data()->HasAPIPermissionForTab(
395                             GetTabId(contents), APIPermission::kTab);
396
397   if (!has_permission) {
398     tab_info->Remove(keys::kUrlKey, NULL);
399     tab_info->Remove(keys::kTitleKey, NULL);
400     tab_info->Remove(keys::kFaviconUrlKey, NULL);
401   }
402 }
403
404 void ExtensionTabUtil::ScrubTabForExtension(const Extension* extension,
405                                             api::tabs::Tab* tab) {
406   bool has_permission =
407       extension &&
408       extension->permissions_data()->HasAPIPermission(APIPermission::kTab);
409
410   if (!has_permission) {
411     tab->url.reset();
412     tab->title.reset();
413     tab->fav_icon_url.reset();
414   }
415 }
416
417 bool ExtensionTabUtil::GetTabStripModel(const WebContents* web_contents,
418                                         TabStripModel** tab_strip_model,
419                                         int* tab_index) {
420   DCHECK(web_contents);
421   DCHECK(tab_strip_model);
422   DCHECK(tab_index);
423
424   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
425     TabStripModel* tab_strip = it->tab_strip_model();
426     int index = tab_strip->GetIndexOfWebContents(web_contents);
427     if (index != -1) {
428       *tab_strip_model = tab_strip;
429       *tab_index = index;
430       return true;
431     }
432   }
433
434   return false;
435 }
436
437 bool ExtensionTabUtil::GetDefaultTab(Browser* browser,
438                                      WebContents** contents,
439                                      int* tab_id) {
440   DCHECK(browser);
441   DCHECK(contents);
442
443   *contents = browser->tab_strip_model()->GetActiveWebContents();
444   if (*contents) {
445     if (tab_id)
446       *tab_id = GetTabId(*contents);
447     return true;
448   }
449
450   return false;
451 }
452
453 bool ExtensionTabUtil::GetTabById(int tab_id,
454                                   Profile* profile,
455                                   bool include_incognito,
456                                   Browser** browser,
457                                   TabStripModel** tab_strip,
458                                   WebContents** contents,
459                                   int* tab_index) {
460   Profile* incognito_profile =
461       include_incognito && profile->HasOffTheRecordProfile() ?
462           profile->GetOffTheRecordProfile() : NULL;
463   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
464     Browser* target_browser = *it;
465     if (target_browser->profile() == profile ||
466         target_browser->profile() == incognito_profile) {
467       TabStripModel* target_tab_strip = target_browser->tab_strip_model();
468       for (int i = 0; i < target_tab_strip->count(); ++i) {
469         WebContents* target_contents = target_tab_strip->GetWebContentsAt(i);
470         if (SessionID::IdForTab(target_contents) == tab_id) {
471           if (browser)
472             *browser = target_browser;
473           if (tab_strip)
474             *tab_strip = target_tab_strip;
475           if (contents)
476             *contents = target_contents;
477           if (tab_index)
478             *tab_index = i;
479           return true;
480         }
481       }
482     }
483   }
484   return false;
485 }
486
487 GURL ExtensionTabUtil::ResolvePossiblyRelativeURL(const std::string& url_string,
488                                                   const Extension* extension) {
489   GURL url = GURL(url_string);
490   if (!url.is_valid())
491     url = extension->GetResourceURL(url_string);
492
493   return url;
494 }
495
496 bool ExtensionTabUtil::IsCrashURL(const GURL& url) {
497   // Check a fixed-up URL, to normalize the scheme and parse hosts correctly.
498   GURL fixed_url =
499       url_fixer::FixupURL(url.possibly_invalid_spec(), std::string());
500   return (fixed_url.SchemeIs(content::kChromeUIScheme) &&
501           (fixed_url.host() == content::kChromeUIBrowserCrashHost ||
502            fixed_url.host() == chrome::kChromeUICrashHost));
503 }
504
505 void ExtensionTabUtil::CreateTab(WebContents* web_contents,
506                                  const std::string& extension_id,
507                                  WindowOpenDisposition disposition,
508                                  const gfx::Rect& initial_pos,
509                                  bool user_gesture) {
510   Profile* profile =
511       Profile::FromBrowserContext(web_contents->GetBrowserContext());
512   chrome::HostDesktopType active_desktop = chrome::GetActiveDesktop();
513   Browser* browser = chrome::FindTabbedBrowser(profile, false, active_desktop);
514   const bool browser_created = !browser;
515   if (!browser)
516     browser = new Browser(Browser::CreateParams(profile, active_desktop));
517   chrome::NavigateParams params(browser, web_contents);
518
519   // The extension_app_id parameter ends up as app_name in the Browser
520   // which causes the Browser to return true for is_app().  This affects
521   // among other things, whether the location bar gets displayed.
522   // TODO(mpcomplete): This seems wrong. What if the extension content is hosted
523   // in a tab?
524   if (disposition == NEW_POPUP)
525     params.extension_app_id = extension_id;
526
527   params.disposition = disposition;
528   params.window_bounds = initial_pos;
529   params.window_action = chrome::NavigateParams::SHOW_WINDOW;
530   params.user_gesture = user_gesture;
531   chrome::Navigate(&params);
532
533   // Close the browser if chrome::Navigate created a new one.
534   if (browser_created && (browser != params.browser))
535     browser->window()->Close();
536 }
537
538 // static
539 void ExtensionTabUtil::ForEachTab(
540     const base::Callback<void(WebContents*)>& callback) {
541   for (TabContentsIterator iterator; !iterator.done(); iterator.Next())
542     callback.Run(*iterator);
543 }
544
545 // static
546 WindowController* ExtensionTabUtil::GetWindowControllerOfTab(
547     const WebContents* web_contents) {
548   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
549   if (browser != NULL)
550     return browser->extension_window_controller();
551
552   return NULL;
553 }
554
555 void ExtensionTabUtil::OpenOptionsPage(const Extension* extension,
556                                        Browser* browser) {
557   DCHECK(!ManifestURL::GetOptionsPage(extension).is_empty());
558
559   // Force the options page to open in non-OTR window, because it won't be
560   // able to save settings from OTR.
561   scoped_ptr<chrome::ScopedTabbedBrowserDisplayer> displayer;
562   if (browser->profile()->IsOffTheRecord()) {
563     displayer.reset(new chrome::ScopedTabbedBrowserDisplayer(
564         browser->profile()->GetOriginalProfile(),
565         browser->host_desktop_type()));
566     browser = displayer->browser();
567   }
568
569   content::OpenURLParams params(ManifestURL::GetOptionsPage(extension),
570                                 content::Referrer(),
571                                 SINGLETON_TAB,
572                                 content::PAGE_TRANSITION_LINK,
573                                 false);
574   browser->OpenURL(params);
575   browser->window()->Show();
576   WebContents* web_contents =
577       browser->tab_strip_model()->GetActiveWebContents();
578   web_contents->GetDelegate()->ActivateContents(web_contents);
579 }
580
581 }  // namespace extensions