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