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