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