tab_helper.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/tab_helper.h"
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/strings/string_util.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/chrome_notification_types.h"
12#include "chrome/browser/extensions/activity_log/activity_log.h"
13#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
14#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
15#include "chrome/browser/extensions/api/webstore/webstore_api.h"
16#include "chrome/browser/extensions/bookmark_app_helper.h"
17#include "chrome/browser/extensions/error_console/error_console.h"
18#include "chrome/browser/extensions/extension_action.h"
19#include "chrome/browser/extensions/extension_action_manager.h"
20#include "chrome/browser/extensions/extension_service.h"
21#include "chrome/browser/extensions/extension_tab_util.h"
22#include "chrome/browser/extensions/image_loader.h"
23#include "chrome/browser/extensions/page_action_controller.h"
24#include "chrome/browser/extensions/script_executor.h"
25#include "chrome/browser/extensions/webstore_inline_installer.h"
26#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/sessions/session_id.h"
29#include "chrome/browser/sessions/session_tab_helper.h"
30#include "chrome/browser/shell_integration.h"
31#include "chrome/browser/ui/browser_commands.h"
32#include "chrome/browser/ui/browser_dialogs.h"
33#include "chrome/browser/ui/browser_finder.h"
34#include "chrome/browser/ui/browser_window.h"
35#include "chrome/browser/ui/host_desktop.h"
36#include "chrome/browser/web_applications/web_app.h"
37#include "chrome/common/chrome_switches.h"
38#include "chrome/common/extensions/chrome_extension_messages.h"
39#include "chrome/common/extensions/extension_constants.h"
40#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
41#include "chrome/common/render_messages.h"
42#include "chrome/common/url_constants.h"
43#include "content/public/browser/invalidate_type.h"
44#include "content/public/browser/navigation_controller.h"
45#include "content/public/browser/navigation_details.h"
46#include "content/public/browser/navigation_entry.h"
47#include "content/public/browser/notification_service.h"
48#include "content/public/browser/notification_source.h"
49#include "content/public/browser/notification_types.h"
50#include "content/public/browser/render_process_host.h"
51#include "content/public/browser/render_view_host.h"
52#include "content/public/browser/render_widget_host_view.h"
53#include "content/public/browser/web_contents.h"
54#include "content/public/common/frame_navigate_params.h"
55#include "extensions/browser/extension_error.h"
56#include "extensions/browser/extension_registry.h"
57#include "extensions/browser/extension_system.h"
58#include "extensions/common/extension.h"
59#include "extensions/common/extension_icon_set.h"
60#include "extensions/common/extension_messages.h"
61#include "extensions/common/extension_resource.h"
62#include "extensions/common/extension_urls.h"
63#include "extensions/common/feature_switch.h"
64#include "extensions/common/manifest_handlers/icons_handler.h"
65
66#if defined(OS_CHROMEOS)
67#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
68#endif
69
70#if defined(OS_WIN)
71#include "chrome/browser/web_applications/web_app_win.h"
72#endif
73
74using content::NavigationController;
75using content::NavigationEntry;
76using content::RenderViewHost;
77using content::WebContents;
78
79DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
80
81namespace extensions {
82
83TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
84    TabHelper* tab_helper)
85    : tab_helper_(tab_helper) {
86  tab_helper_->AddScriptExecutionObserver(this);
87}
88
89TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
90    : tab_helper_(NULL) {
91}
92
93TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
94  if (tab_helper_)
95    tab_helper_->RemoveScriptExecutionObserver(this);
96}
97
98TabHelper::TabHelper(content::WebContents* web_contents)
99    : content::WebContentsObserver(web_contents),
100      extension_app_(NULL),
101      extension_function_dispatcher_(
102          Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
103      pending_web_app_action_(NONE),
104      script_executor_(new ScriptExecutor(web_contents,
105                                          &script_execution_observers_)),
106      location_bar_controller_(new PageActionController(web_contents)),
107      image_loader_ptr_factory_(this),
108      webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
109  // The ActiveTabPermissionManager requires a session ID; ensure this
110  // WebContents has one.
111  SessionTabHelper::CreateForWebContents(web_contents);
112  if (web_contents->GetRenderViewHost())
113    SetTabId(web_contents->GetRenderViewHost());
114  active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
115      web_contents,
116      SessionID::IdForTab(web_contents),
117      Profile::FromBrowserContext(web_contents->GetBrowserContext())));
118
119  // If more classes need to listen to global content script activity, then
120  // a separate routing class with an observer interface should be written.
121  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
122
123#if defined(ENABLE_EXTENSIONS)
124  AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
125#endif
126
127  registrar_.Add(this,
128                 content::NOTIFICATION_LOAD_STOP,
129                 content::Source<NavigationController>(
130                     &web_contents->GetController()));
131}
132
133TabHelper::~TabHelper() {
134#if defined(ENABLE_EXTENSIONS)
135  RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
136#endif
137}
138
139void TabHelper::CreateApplicationShortcuts() {
140  DCHECK(CanCreateApplicationShortcuts());
141  NavigationEntry* entry =
142      web_contents()->GetController().GetLastCommittedEntry();
143  if (!entry)
144    return;
145
146  pending_web_app_action_ = CREATE_SHORTCUT;
147
148  // Start fetching web app info for CreateApplicationShortcut dialog and show
149  // the dialog when the data is available in OnDidGetApplicationInfo.
150  GetApplicationInfo(entry->GetPageID());
151}
152
153void TabHelper::CreateHostedAppFromWebContents() {
154  DCHECK(CanCreateBookmarkApp());
155  NavigationEntry* entry =
156      web_contents()->GetController().GetLastCommittedEntry();
157  if (!entry)
158    return;
159
160  pending_web_app_action_ = CREATE_HOSTED_APP;
161
162  // Start fetching web app info for CreateApplicationShortcut dialog and show
163  // the dialog when the data is available in OnDidGetApplicationInfo.
164  GetApplicationInfo(entry->GetPageID());
165}
166
167bool TabHelper::CanCreateApplicationShortcuts() const {
168#if defined(OS_MACOSX)
169  return false;
170#else
171  return web_app::IsValidUrl(web_contents()->GetURL()) &&
172      pending_web_app_action_ == NONE;
173#endif
174}
175
176bool TabHelper::CanCreateBookmarkApp() const {
177#if defined(OS_MACOSX)
178  return false;
179#else
180  return IsValidBookmarkAppUrl(web_contents()->GetURL()) &&
181         pending_web_app_action_ == NONE;
182#endif
183}
184
185void TabHelper::SetExtensionApp(const Extension* extension) {
186  DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
187  if (extension_app_ == extension)
188    return;
189
190  extension_app_ = extension;
191
192  UpdateExtensionAppIcon(extension_app_);
193
194  content::NotificationService::current()->Notify(
195      chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
196      content::Source<TabHelper>(this),
197      content::NotificationService::NoDetails());
198}
199
200void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
201  const Extension* extension = GetExtension(extension_app_id);
202  if (extension)
203    SetExtensionApp(extension);
204}
205
206void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
207  const Extension* extension = GetExtension(extension_app_id);
208  if (extension)
209    UpdateExtensionAppIcon(extension);
210}
211
212SkBitmap* TabHelper::GetExtensionAppIcon() {
213  if (extension_app_icon_.empty())
214    return NULL;
215
216  return &extension_app_icon_;
217}
218
219void TabHelper::FinishCreateBookmarkApp(
220    const extensions::Extension* extension,
221    const WebApplicationInfo& web_app_info) {
222  pending_web_app_action_ = NONE;
223
224  // There was an error with downloading the icons or installing the app.
225  if (!extension)
226    return;
227
228#if defined(OS_CHROMEOS)
229  ChromeLauncherController::instance()->PinAppWithID(extension->id());
230#endif
231
232// Android does not implement browser_finder.cc.
233#if !defined(OS_ANDROID)
234  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
235  if (browser) {
236    browser->window()->ShowBookmarkAppBubble(web_app_info, extension->id());
237  }
238#endif
239}
240
241void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
242  SetTabId(render_view_host);
243}
244
245void TabHelper::DidNavigateMainFrame(
246    const content::LoadCommittedDetails& details,
247    const content::FrameNavigateParams& params) {
248#if defined(ENABLE_EXTENSIONS)
249  if (ExtensionSystem::Get(profile_)->extension_service() &&
250      RulesRegistryService::Get(profile_)) {
251    RulesRegistryService::Get(profile_)->content_rules_registry()->
252        DidNavigateMainFrame(web_contents(), details, params);
253  }
254#endif  // defined(ENABLE_EXTENSIONS)
255
256  content::BrowserContext* context = web_contents()->GetBrowserContext();
257  ExtensionRegistry* registry = ExtensionRegistry::Get(context);
258  const ExtensionSet& enabled_extensions = registry->enabled_extensions();
259
260  if (CommandLine::ForCurrentProcess()->HasSwitch(
261          switches::kEnableStreamlinedHostedApps)) {
262#if !defined(OS_ANDROID)
263    Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
264    if (browser && browser->is_app()) {
265      SetExtensionApp(registry->GetExtensionById(
266          web_app::GetExtensionIdFromApplicationName(browser->app_name()),
267          ExtensionRegistry::EVERYTHING));
268    } else {
269      UpdateExtensionAppIcon(
270          enabled_extensions.GetExtensionOrAppByURL(params.url));
271    }
272#endif
273  } else {
274    UpdateExtensionAppIcon(
275        enabled_extensions.GetExtensionOrAppByURL(params.url));
276  }
277
278  if (details.is_in_page)
279    return;
280
281  ExtensionActionManager* extension_action_manager =
282      ExtensionActionManager::Get(Profile::FromBrowserContext(context));
283  for (ExtensionSet::const_iterator it = enabled_extensions.begin();
284       it != enabled_extensions.end();
285       ++it) {
286    ExtensionAction* browser_action =
287        extension_action_manager->GetBrowserAction(*it->get());
288    if (browser_action) {
289      browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
290      content::NotificationService::current()->Notify(
291          chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
292          content::Source<ExtensionAction>(browser_action),
293          content::NotificationService::NoDetails());
294    }
295  }
296}
297
298bool TabHelper::OnMessageReceived(const IPC::Message& message) {
299  bool handled = true;
300  IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
301    IPC_MESSAGE_HANDLER(ChromeExtensionHostMsg_DidGetApplicationInfo,
302                        OnDidGetApplicationInfo)
303    IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
304                        OnInlineWebstoreInstall)
305    IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
306                        OnGetAppInstallState);
307    IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
308    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
309                        OnContentScriptsExecuting)
310    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
311                        OnWatchedPageChange)
312    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
313                        OnDetailedConsoleMessageAdded)
314    IPC_MESSAGE_UNHANDLED(handled = false)
315  IPC_END_MESSAGE_MAP()
316  return handled;
317}
318
319void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
320                                         WebContents* new_web_contents) {
321  // When the WebContents that this is attached to is cloned, give the new clone
322  // a TabHelper and copy state over.
323  CreateForWebContents(new_web_contents);
324  TabHelper* new_helper = FromWebContents(new_web_contents);
325
326  new_helper->SetExtensionApp(extension_app());
327  new_helper->extension_app_icon_ = extension_app_icon_;
328}
329
330void TabHelper::OnDidGetApplicationInfo(int32 page_id,
331                                        const WebApplicationInfo& info) {
332  // Android does not implement BrowserWindow.
333#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
334  web_app_info_ = info;
335
336  NavigationEntry* entry =
337      web_contents()->GetController().GetLastCommittedEntry();
338  if (!entry || (entry->GetPageID() != page_id))
339    return;
340
341  switch (pending_web_app_action_) {
342    case CREATE_SHORTCUT: {
343      chrome::ShowCreateWebAppShortcutsDialog(
344          web_contents()->GetTopLevelNativeWindow(),
345          web_contents());
346      break;
347    }
348    case CREATE_HOSTED_APP: {
349      if (web_app_info_.app_url.is_empty())
350        web_app_info_.app_url = web_contents()->GetURL();
351
352      if (web_app_info_.title.empty())
353        web_app_info_.title = web_contents()->GetTitle();
354      if (web_app_info_.title.empty())
355        web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
356
357      bookmark_app_helper_.reset(new BookmarkAppHelper(
358          profile_->GetExtensionService(), web_app_info_, web_contents()));
359      bookmark_app_helper_->Create(base::Bind(
360          &TabHelper::FinishCreateBookmarkApp, base::Unretained(this)));
361      break;
362    }
363    case UPDATE_SHORTCUT: {
364      web_app::UpdateShortcutForTabContents(web_contents());
365      break;
366    }
367    default:
368      NOTREACHED();
369      break;
370  }
371
372  // The hosted app action will be cleared once the installation completes or
373  // fails.
374  if (pending_web_app_action_ != CREATE_HOSTED_APP)
375    pending_web_app_action_ = NONE;
376#endif
377}
378
379void TabHelper::OnInlineWebstoreInstall(int install_id,
380                                        int return_route_id,
381                                        const std::string& webstore_item_id,
382                                        const GURL& requestor_url,
383                                        int listeners_mask) {
384#if defined(ENABLE_EXTENSIONS)
385  // Check that the listener is reasonable. We should never get anything other
386  // than an install stage listener, a download listener, or both.
387  if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
388                          api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) {
389    NOTREACHED();
390    return;
391  }
392  // Inform the Webstore API that an inline install is happening, in case the
393  // page requested status updates.
394  Profile* profile =
395      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
396  WebstoreAPI::Get(profile)->OnInlineInstallStart(
397      return_route_id, this, webstore_item_id, listeners_mask);
398#endif
399
400  WebstoreStandaloneInstaller::Callback callback =
401      base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
402                 install_id, return_route_id);
403  scoped_refptr<WebstoreInlineInstaller> installer(
404      webstore_inline_installer_factory_->CreateInstaller(
405          web_contents(),
406          webstore_item_id,
407          requestor_url,
408          callback));
409  installer->BeginInstall();
410}
411
412void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
413                                     int return_route_id,
414                                     int callback_id) {
415  ExtensionRegistry* registry =
416      ExtensionRegistry::Get(web_contents()->GetBrowserContext());
417  const ExtensionSet& extensions = registry->enabled_extensions();
418  const ExtensionSet& disabled_extensions = registry->disabled_extensions();
419
420  std::string state;
421  if (extensions.GetHostedAppByURL(requestor_url))
422    state = extension_misc::kAppStateInstalled;
423  else if (disabled_extensions.GetHostedAppByURL(requestor_url))
424    state = extension_misc::kAppStateDisabled;
425  else
426    state = extension_misc::kAppStateNotInstalled;
427
428  Send(new ExtensionMsg_GetAppInstallStateResponse(
429      return_route_id, state, callback_id));
430}
431
432void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
433  extension_function_dispatcher_.Dispatch(request,
434                                          web_contents()->GetRenderViewHost());
435}
436
437void TabHelper::OnContentScriptsExecuting(
438    const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
439    int32 on_page_id,
440    const GURL& on_url) {
441  FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
442                    OnScriptsExecuted(web_contents(),
443                                      executing_scripts_map,
444                                      on_page_id,
445                                      on_url));
446}
447
448void TabHelper::OnWatchedPageChange(
449    const std::vector<std::string>& css_selectors) {
450#if defined(ENABLE_EXTENSIONS)
451  if (ExtensionSystem::Get(profile_)->extension_service() &&
452      RulesRegistryService::Get(profile_)) {
453    RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
454        web_contents(), css_selectors);
455  }
456#endif  // defined(ENABLE_EXTENSIONS)
457}
458
459void TabHelper::OnDetailedConsoleMessageAdded(
460    const base::string16& message,
461    const base::string16& source,
462    const StackTrace& stack_trace,
463    int32 severity_level) {
464  if (IsSourceFromAnExtension(source)) {
465    content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
466    ErrorConsole::Get(profile_)->ReportError(
467        scoped_ptr<ExtensionError>(new RuntimeError(
468            extension_app_ ? extension_app_->id() : std::string(),
469            profile_->IsOffTheRecord(),
470            source,
471            message,
472            stack_trace,
473            web_contents() ?
474                web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
475            static_cast<logging::LogSeverity>(severity_level),
476            rvh->GetRoutingID(),
477            rvh->GetProcess()->GetID())));
478  }
479}
480
481const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
482  if (extension_app_id.empty())
483    return NULL;
484
485  Profile* profile =
486      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
487  ExtensionService* extension_service = profile->GetExtensionService();
488  if (!extension_service || !extension_service->is_ready())
489    return NULL;
490
491  const Extension* extension =
492      extension_service->GetExtensionById(extension_app_id, false);
493  return extension;
494}
495
496void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
497  extension_app_icon_.reset();
498  // Ensure previously enqueued callbacks are ignored.
499  image_loader_ptr_factory_.InvalidateWeakPtrs();
500
501  // Enqueue OnImageLoaded callback.
502  if (extension) {
503    Profile* profile =
504        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
505    extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
506    loader->LoadImageAsync(
507        extension,
508        IconsInfo::GetIconResource(extension,
509                                   extension_misc::EXTENSION_ICON_SMALL,
510                                   ExtensionIconSet::MATCH_BIGGER),
511        gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
512                  extension_misc::EXTENSION_ICON_SMALL),
513        base::Bind(&TabHelper::OnImageLoaded,
514                   image_loader_ptr_factory_.GetWeakPtr()));
515  }
516}
517
518void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
519  extension_app_icon_ = app_icon;
520  web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
521}
522
523void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
524    WebstoreInlineInstallerFactory* factory) {
525  webstore_inline_installer_factory_.reset(factory);
526}
527
528void TabHelper::OnImageLoaded(const gfx::Image& image) {
529  if (!image.IsEmpty()) {
530    extension_app_icon_ = *image.ToSkBitmap();
531    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
532  }
533}
534
535WindowController* TabHelper::GetExtensionWindowController() const  {
536  return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
537}
538
539void TabHelper::OnInlineInstallComplete(int install_id,
540                                        int return_route_id,
541                                        bool success,
542                                        const std::string& error) {
543  Send(new ExtensionMsg_InlineWebstoreInstallResponse(
544      return_route_id, install_id, success, success ? std::string() : error));
545}
546
547WebContents* TabHelper::GetAssociatedWebContents() const {
548  return web_contents();
549}
550
551void TabHelper::GetApplicationInfo(int32 page_id) {
552  Send(new ChromeExtensionMsg_GetApplicationInfo(routing_id(), page_id));
553}
554
555void TabHelper::Observe(int type,
556                        const content::NotificationSource& source,
557                        const content::NotificationDetails& details) {
558  switch (type) {
559    case content::NOTIFICATION_LOAD_STOP: {
560      const NavigationController& controller =
561          *content::Source<NavigationController>(source).ptr();
562      DCHECK_EQ(controller.GetWebContents(), web_contents());
563
564      if (pending_web_app_action_ == UPDATE_SHORTCUT) {
565        // Schedule a shortcut update when web application info is available if
566        // last committed entry is not NULL. Last committed entry could be NULL
567        // when an interstitial page is injected (e.g. bad https certificate,
568        // malware site etc). When this happens, we abort the shortcut update.
569        NavigationEntry* entry = controller.GetLastCommittedEntry();
570        if (entry)
571          GetApplicationInfo(entry->GetPageID());
572        else
573          pending_web_app_action_ = NONE;
574      }
575      break;
576    }
577  }
578}
579
580void TabHelper::SetTabId(RenderViewHost* render_view_host) {
581  render_view_host->Send(
582      new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
583                                SessionID::IdForTab(web_contents())));
584}
585
586}  // namespace extensions
587