tab_helper.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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 "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/extensions/activity_log/activity_log.h"
11#include "chrome/browser/extensions/api/declarative/rules_registry_service.h"
12#include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h"
13#include "chrome/browser/extensions/crx_installer.h"
14#include "chrome/browser/extensions/error_console/error_console.h"
15#include "chrome/browser/extensions/extension_action.h"
16#include "chrome/browser/extensions/extension_action_manager.h"
17#include "chrome/browser/extensions/extension_service.h"
18#include "chrome/browser/extensions/extension_system.h"
19#include "chrome/browser/extensions/extension_tab_util.h"
20#include "chrome/browser/extensions/image_loader.h"
21#include "chrome/browser/extensions/page_action_controller.h"
22#include "chrome/browser/extensions/script_badge_controller.h"
23#include "chrome/browser/extensions/script_bubble_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/ui/browser_dialogs.h"
31#include "chrome/browser/ui/web_applications/web_app_ui.h"
32#include "chrome/browser/web_applications/web_app.h"
33#include "chrome/common/extensions/extension.h"
34#include "chrome/common/extensions/extension_constants.h"
35#include "chrome/common/extensions/extension_icon_set.h"
36#include "chrome/common/extensions/extension_messages.h"
37#include "chrome/common/extensions/feature_switch.h"
38#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
39#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
40#include "chrome/common/render_messages.h"
41#include "content/public/browser/invalidate_type.h"
42#include "content/public/browser/navigation_controller.h"
43#include "content/public/browser/navigation_details.h"
44#include "content/public/browser/navigation_entry.h"
45#include "content/public/browser/notification_service.h"
46#include "content/public/browser/notification_source.h"
47#include "content/public/browser/notification_types.h"
48#include "content/public/browser/render_process_host.h"
49#include "content/public/browser/render_view_host.h"
50#include "content/public/browser/render_widget_host_view.h"
51#include "content/public/browser/web_contents.h"
52#include "content/public/browser/web_contents_view.h"
53#include "extensions/browser/extension_error.h"
54#include "extensions/common/extension_resource.h"
55#include "extensions/common/extension_urls.h"
56#include "ui/gfx/image/image.h"
57
58using content::NavigationController;
59using content::NavigationEntry;
60using content::RenderViewHost;
61using content::WebContents;
62
63DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
64
65namespace extensions {
66
67TabHelper::ScriptExecutionObserver::ScriptExecutionObserver(
68    TabHelper* tab_helper)
69    : tab_helper_(tab_helper) {
70  tab_helper_->AddScriptExecutionObserver(this);
71}
72
73TabHelper::ScriptExecutionObserver::ScriptExecutionObserver()
74    : tab_helper_(NULL) {
75}
76
77TabHelper::ScriptExecutionObserver::~ScriptExecutionObserver() {
78  if (tab_helper_)
79    tab_helper_->RemoveScriptExecutionObserver(this);
80}
81
82TabHelper::TabHelper(content::WebContents* web_contents)
83    : content::WebContentsObserver(web_contents),
84      extension_app_(NULL),
85      extension_function_dispatcher_(
86          Profile::FromBrowserContext(web_contents->GetBrowserContext()), this),
87      pending_web_app_action_(NONE),
88      script_executor_(new ScriptExecutor(web_contents,
89                                          &script_execution_observers_)),
90      image_loader_ptr_factory_(this),
91      webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()) {
92  // The ActiveTabPermissionManager requires a session ID; ensure this
93  // WebContents has one.
94  SessionTabHelper::CreateForWebContents(web_contents);
95  if (web_contents->GetRenderViewHost())
96    SetTabId(web_contents->GetRenderViewHost());
97  active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
98      web_contents,
99      SessionID::IdForTab(web_contents),
100      Profile::FromBrowserContext(web_contents->GetBrowserContext())));
101  if (FeatureSwitch::script_badges()->IsEnabled()) {
102    location_bar_controller_.reset(
103        new ScriptBadgeController(web_contents, this));
104  } else {
105    location_bar_controller_.reset(
106        new PageActionController(web_contents));
107  }
108
109  if (FeatureSwitch::script_bubble()->IsEnabled()) {
110    script_bubble_controller_.reset(
111        new ScriptBubbleController(web_contents, this));
112  }
113
114  // If more classes need to listen to global content script activity, then
115  // a separate routing class with an observer interface should be written.
116  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
117
118#if defined(ENABLE_EXTENSIONS)
119  AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
120#endif
121
122  registrar_.Add(this,
123                 content::NOTIFICATION_LOAD_STOP,
124                 content::Source<NavigationController>(
125                     &web_contents->GetController()));
126
127  registrar_.Add(this,
128                 chrome::NOTIFICATION_EXTENSION_UNLOADED,
129                 content::NotificationService::AllSources());
130}
131
132TabHelper::~TabHelper() {
133#if defined(ENABLE_EXTENSIONS)
134  RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
135#endif
136}
137
138void TabHelper::CreateApplicationShortcuts() {
139  DCHECK(CanCreateApplicationShortcuts());
140  NavigationEntry* entry =
141      web_contents()->GetController().GetLastCommittedEntry();
142  if (!entry)
143    return;
144
145  pending_web_app_action_ = CREATE_SHORTCUT;
146
147  // Start fetching web app info for CreateApplicationShortcut dialog and show
148  // the dialog when the data is available in OnDidGetApplicationInfo.
149  GetApplicationInfo(entry->GetPageID());
150}
151
152bool TabHelper::CanCreateApplicationShortcuts() const {
153#if defined(OS_MACOSX)
154  return false;
155#else
156  return web_app::IsValidUrl(web_contents()->GetURL()) &&
157      pending_web_app_action_ == NONE;
158#endif
159}
160
161void TabHelper::SetExtensionApp(const Extension* extension) {
162  DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
163  extension_app_ = extension;
164
165  UpdateExtensionAppIcon(extension_app_);
166
167  content::NotificationService::current()->Notify(
168      chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
169      content::Source<TabHelper>(this),
170      content::NotificationService::NoDetails());
171}
172
173void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
174  const Extension* extension = GetExtension(extension_app_id);
175  if (extension)
176    SetExtensionApp(extension);
177}
178
179void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
180  const Extension* extension = GetExtension(extension_app_id);
181  if (extension)
182    UpdateExtensionAppIcon(extension);
183}
184
185SkBitmap* TabHelper::GetExtensionAppIcon() {
186  if (extension_app_icon_.empty())
187    return NULL;
188
189  return &extension_app_icon_;
190}
191
192void TabHelper::RenderViewCreated(RenderViewHost* render_view_host) {
193  SetTabId(render_view_host);
194}
195
196void TabHelper::DidNavigateMainFrame(
197    const content::LoadCommittedDetails& details,
198    const content::FrameNavigateParams& params) {
199#if defined(ENABLE_EXTENSIONS)
200  if (ExtensionSystem::Get(profile_)->extension_service() &&
201      RulesRegistryService::Get(profile_)) {
202    RulesRegistryService::Get(profile_)->content_rules_registry()->
203        DidNavigateMainFrame(web_contents(), details, params);
204  }
205#endif  // defined(ENABLE_EXTENSIONS)
206
207  if (details.is_in_page)
208    return;
209
210  Profile* profile =
211      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
212  ExtensionService* service = profile->GetExtensionService();
213  if (!service)
214    return;
215
216  ExtensionActionManager* extension_action_manager =
217      ExtensionActionManager::Get(profile);
218  for (ExtensionSet::const_iterator it = service->extensions()->begin();
219       it != service->extensions()->end(); ++it) {
220    ExtensionAction* browser_action =
221        extension_action_manager->GetBrowserAction(*it->get());
222    if (browser_action) {
223      browser_action->ClearAllValuesForTab(SessionID::IdForTab(web_contents()));
224      content::NotificationService::current()->Notify(
225          chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
226          content::Source<ExtensionAction>(browser_action),
227          content::NotificationService::NoDetails());
228    }
229  }
230}
231
232bool TabHelper::OnMessageReceived(const IPC::Message& message) {
233  bool handled = true;
234  IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
235    IPC_MESSAGE_HANDLER(ExtensionHostMsg_DidGetApplicationInfo,
236                        OnDidGetApplicationInfo)
237    IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
238                        OnInlineWebstoreInstall)
239    IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
240                        OnGetAppInstallState);
241    IPC_MESSAGE_HANDLER(ExtensionHostMsg_Request, OnRequest)
242    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
243                        OnContentScriptsExecuting)
244    IPC_MESSAGE_HANDLER(ExtensionHostMsg_OnWatchedPageChange,
245                        OnWatchedPageChange)
246    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DetailedConsoleMessageAdded,
247                        OnDetailedConsoleMessageAdded)
248    IPC_MESSAGE_UNHANDLED(handled = false)
249  IPC_END_MESSAGE_MAP()
250  return handled;
251}
252
253void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
254                                         WebContents* new_web_contents) {
255  // When the WebContents that this is attached to is cloned, give the new clone
256  // a TabHelper and copy state over.
257  CreateForWebContents(new_web_contents);
258  TabHelper* new_helper = FromWebContents(new_web_contents);
259
260  new_helper->SetExtensionApp(extension_app());
261  new_helper->extension_app_icon_ = extension_app_icon_;
262}
263
264void TabHelper::OnDidGetApplicationInfo(int32 page_id,
265                                        const WebApplicationInfo& info) {
266  // Android does not implement BrowserWindow.
267#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
268  web_app_info_ = info;
269
270  NavigationEntry* entry =
271      web_contents()->GetController().GetLastCommittedEntry();
272  if (!entry || (entry->GetPageID() != page_id))
273    return;
274
275  switch (pending_web_app_action_) {
276    case CREATE_SHORTCUT: {
277      chrome::ShowCreateWebAppShortcutsDialog(
278          web_contents()->GetView()->GetTopLevelNativeWindow(),
279          web_contents());
280      break;
281    }
282    case UPDATE_SHORTCUT: {
283      web_app::UpdateShortcutForTabContents(web_contents());
284      break;
285    }
286    default:
287      NOTREACHED();
288      break;
289  }
290
291  pending_web_app_action_ = NONE;
292#endif
293}
294
295void TabHelper::OnInlineWebstoreInstall(
296    int install_id,
297    int return_route_id,
298    const std::string& webstore_item_id,
299    const GURL& requestor_url) {
300  WebstoreStandaloneInstaller::Callback callback =
301      base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this),
302                 install_id, return_route_id);
303  scoped_refptr<WebstoreInlineInstaller> installer(
304      webstore_inline_installer_factory_->CreateInstaller(
305          web_contents(),
306          webstore_item_id,
307          requestor_url,
308          callback));
309  installer->BeginInstall();
310}
311
312void TabHelper::OnGetAppInstallState(const GURL& requestor_url,
313                                     int return_route_id,
314                                     int callback_id) {
315  Profile* profile =
316      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
317  ExtensionService* extension_service = profile->GetExtensionService();
318  const ExtensionSet* extensions = extension_service->extensions();
319  const ExtensionSet* disabled = extension_service->disabled_extensions();
320
321  std::string state;
322  if (extensions->GetHostedAppByURL(requestor_url))
323    state = extension_misc::kAppStateInstalled;
324  else if (disabled->GetHostedAppByURL(requestor_url))
325    state = extension_misc::kAppStateDisabled;
326  else
327    state = extension_misc::kAppStateNotInstalled;
328
329  Send(new ExtensionMsg_GetAppInstallStateResponse(
330      return_route_id, state, callback_id));
331}
332
333void TabHelper::OnRequest(const ExtensionHostMsg_Request_Params& request) {
334  extension_function_dispatcher_.Dispatch(request,
335                                          web_contents()->GetRenderViewHost());
336}
337
338void TabHelper::OnContentScriptsExecuting(
339    const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
340    int32 on_page_id,
341    const GURL& on_url) {
342  FOR_EACH_OBSERVER(ScriptExecutionObserver, script_execution_observers_,
343                    OnScriptsExecuted(web_contents(),
344                                      executing_scripts_map,
345                                      on_page_id,
346                                      on_url));
347}
348
349void TabHelper::OnWatchedPageChange(
350    const std::vector<std::string>& css_selectors) {
351#if defined(ENABLE_EXTENSIONS)
352  if (ExtensionSystem::Get(profile_)->extension_service() &&
353      RulesRegistryService::Get(profile_)) {
354    RulesRegistryService::Get(profile_)->content_rules_registry()->Apply(
355        web_contents(), css_selectors);
356  }
357#endif  // defined(ENABLE_EXTENSIONS)
358}
359
360void TabHelper::OnDetailedConsoleMessageAdded(
361    const base::string16& message,
362    const base::string16& source,
363    const StackTrace& stack_trace,
364    int32 severity_level) {
365  if (IsSourceFromAnExtension(source)) {
366    content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
367    ErrorConsole::Get(profile_)->ReportError(
368        scoped_ptr<ExtensionError>(new RuntimeError(
369            extension_app_ ? extension_app_->id() : EmptyString(),
370            profile_->IsOffTheRecord(),
371            source,
372            message,
373            stack_trace,
374            web_contents() ?
375                web_contents()->GetLastCommittedURL() : GURL::EmptyGURL(),
376            static_cast<logging::LogSeverity>(severity_level),
377            rvh->GetRoutingID(),
378            rvh->GetProcess()->GetID())));
379  }
380}
381
382const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
383  if (extension_app_id.empty())
384    return NULL;
385
386  Profile* profile =
387      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
388  ExtensionService* extension_service = profile->GetExtensionService();
389  if (!extension_service || !extension_service->is_ready())
390    return NULL;
391
392  const Extension* extension =
393      extension_service->GetExtensionById(extension_app_id, false);
394  return extension;
395}
396
397void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
398  extension_app_icon_.reset();
399  // Ensure previously enqueued callbacks are ignored.
400  image_loader_ptr_factory_.InvalidateWeakPtrs();
401
402  // Enqueue OnImageLoaded callback.
403  if (extension) {
404    Profile* profile =
405        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
406    extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile);
407    loader->LoadImageAsync(
408        extension,
409        IconsInfo::GetIconResource(extension,
410                                   extension_misc::EXTENSION_ICON_SMALLISH,
411                                   ExtensionIconSet::MATCH_EXACTLY),
412        gfx::Size(extension_misc::EXTENSION_ICON_SMALLISH,
413                  extension_misc::EXTENSION_ICON_SMALLISH),
414        base::Bind(&TabHelper::OnImageLoaded,
415                   image_loader_ptr_factory_.GetWeakPtr()));
416  }
417}
418
419void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
420  extension_app_icon_ = app_icon;
421  web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
422}
423
424void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
425    WebstoreInlineInstallerFactory* factory) {
426  webstore_inline_installer_factory_.reset(factory);
427}
428
429void TabHelper::OnImageLoaded(const gfx::Image& image) {
430  if (!image.IsEmpty()) {
431    extension_app_icon_ = *image.ToSkBitmap();
432    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
433  }
434}
435
436WindowController* TabHelper::GetExtensionWindowController() const  {
437  return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
438}
439
440void TabHelper::OnInlineInstallComplete(int install_id,
441                                        int return_route_id,
442                                        bool success,
443                                        const std::string& error) {
444  Send(new ExtensionMsg_InlineWebstoreInstallResponse(
445      return_route_id, install_id, success, success ? std::string() : error));
446}
447
448WebContents* TabHelper::GetAssociatedWebContents() const {
449  return web_contents();
450}
451
452void TabHelper::GetApplicationInfo(int32 page_id) {
453  Send(new ExtensionMsg_GetApplicationInfo(routing_id(), page_id));
454}
455
456void TabHelper::Observe(int type,
457                        const content::NotificationSource& source,
458                        const content::NotificationDetails& details) {
459  switch (type) {
460    case content::NOTIFICATION_LOAD_STOP: {
461      const NavigationController& controller =
462          *content::Source<NavigationController>(source).ptr();
463      DCHECK_EQ(controller.GetWebContents(), web_contents());
464
465      if (pending_web_app_action_ == UPDATE_SHORTCUT) {
466        // Schedule a shortcut update when web application info is available if
467        // last committed entry is not NULL. Last committed entry could be NULL
468        // when an interstitial page is injected (e.g. bad https certificate,
469        // malware site etc). When this happens, we abort the shortcut update.
470        NavigationEntry* entry = controller.GetLastCommittedEntry();
471        if (entry)
472          GetApplicationInfo(entry->GetPageID());
473        else
474          pending_web_app_action_ = NONE;
475      }
476      break;
477    }
478
479    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
480      if (script_bubble_controller_) {
481        script_bubble_controller_->OnExtensionUnloaded(
482            content::Details<extensions::UnloadedExtensionInfo>(
483                details)->extension->id());
484        break;
485      }
486    }
487  }
488}
489
490void TabHelper::SetTabId(RenderViewHost* render_view_host) {
491  render_view_host->Send(
492      new ExtensionMsg_SetTabId(render_view_host->GetRoutingID(),
493                                SessionID::IdForTab(web_contents())));
494}
495
496}  // namespace extensions
497