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