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