Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / extensions / tab_helper.cc
blob72f9f5ad531ca2c00878a7704a8ccf043b58b9b3
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.
5 #include "chrome/browser/extensions/tab_helper.h"
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/chrome_extension_web_contents_observer.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/host_desktop.h"
30 #include "chrome/browser/web_applications/web_app.h"
31 #include "chrome/common/extensions/chrome_extension_messages.h"
32 #include "chrome/common/extensions/extension_constants.h"
33 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
34 #include "chrome/common/render_messages.h"
35 #include "chrome/common/url_constants.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_frame_host.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_prefs.h"
50 #include "extensions/browser/extension_registry.h"
51 #include "extensions/browser/extension_system.h"
52 #include "extensions/browser/extension_web_contents_observer.h"
53 #include "extensions/browser/image_loader.h"
54 #include "extensions/common/constants.h"
55 #include "extensions/common/extension.h"
56 #include "extensions/common/extension_icon_set.h"
57 #include "extensions/common/extension_messages.h"
58 #include "extensions/common/extension_resource.h"
59 #include "extensions/common/extension_urls.h"
60 #include "extensions/common/feature_switch.h"
61 #include "extensions/common/manifest_handlers/icons_handler.h"
63 #if defined(OS_WIN)
64 #include "chrome/browser/web_applications/web_app_win.h"
65 #endif
67 using content::NavigationController;
68 using content::NavigationEntry;
69 using content::WebContents;
71 DEFINE_WEB_CONTENTS_USER_DATA_KEY(extensions::TabHelper);
73 namespace extensions {
75 TabHelper::TabHelper(content::WebContents* web_contents)
76 : content::WebContentsObserver(web_contents),
77 profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
78 extension_app_(NULL),
79 pending_web_app_action_(NONE),
80 last_committed_nav_entry_unique_id_(0),
81 update_shortcut_on_load_complete_(false),
82 script_executor_(
83 new ScriptExecutor(web_contents, &script_execution_observers_)),
84 location_bar_controller_(new LocationBarController(web_contents)),
85 active_script_controller_(new ActiveScriptController(web_contents)),
86 webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
87 image_loader_ptr_factory_(this),
88 weak_ptr_factory_(this) {
89 // The ActiveTabPermissionManager requires a session ID; ensure this
90 // WebContents has one.
91 SessionTabHelper::CreateForWebContents(web_contents);
92 // The Unretained() is safe because ForEachFrame is synchronous.
93 web_contents->ForEachFrame(
94 base::Bind(&TabHelper::SetTabId, base::Unretained(this)));
95 active_tab_permission_granter_.reset(new ActiveTabPermissionGranter(
96 web_contents,
97 SessionTabHelper::IdForTab(web_contents),
98 profile_));
100 AddScriptExecutionObserver(ActivityLog::GetInstance(profile_));
102 InvokeForContentRulesRegistries([this](ContentRulesRegistry* registry) {
103 registry->MonitorWebContentsForRuleEvaluation(this->web_contents());
106 // We need an ExtensionWebContentsObserver, so make sure one exists (this is
107 // a no-op if one already does).
108 ChromeExtensionWebContentsObserver::CreateForWebContents(web_contents);
109 ExtensionWebContentsObserver::GetForWebContents(web_contents)->dispatcher()->
110 set_delegate(this);
112 registrar_.Add(this,
113 content::NOTIFICATION_LOAD_STOP,
114 content::Source<NavigationController>(
115 &web_contents->GetController()));
118 TabHelper::~TabHelper() {
119 RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
122 void TabHelper::CreateApplicationShortcuts() {
123 DCHECK(CanCreateApplicationShortcuts());
124 if (pending_web_app_action_ != NONE)
125 return;
127 // Start fetching web app info for CreateApplicationShortcut dialog and show
128 // the dialog when the data is available in OnDidGetApplicationInfo.
129 GetApplicationInfo(CREATE_SHORTCUT);
132 void TabHelper::CreateHostedAppFromWebContents() {
133 DCHECK(CanCreateBookmarkApp());
134 if (pending_web_app_action_ != NONE)
135 return;
137 // Start fetching web app info for CreateApplicationShortcut dialog and show
138 // the dialog when the data is available in OnDidGetApplicationInfo.
139 GetApplicationInfo(CREATE_HOSTED_APP);
142 bool TabHelper::CanCreateApplicationShortcuts() const {
143 #if defined(OS_MACOSX)
144 return false;
145 #else
146 return web_app::IsValidUrl(web_contents()->GetURL());
147 #endif
150 bool TabHelper::CanCreateBookmarkApp() const {
151 return !profile_->IsGuestSession() &&
152 !profile_->IsSystemProfile() &&
153 IsValidBookmarkAppUrl(web_contents()->GetURL());
156 void TabHelper::AddScriptExecutionObserver(ScriptExecutionObserver* observer) {
157 script_execution_observers_.AddObserver(observer);
160 void TabHelper::RemoveScriptExecutionObserver(
161 ScriptExecutionObserver* observer) {
162 script_execution_observers_.RemoveObserver(observer);
165 void TabHelper::SetExtensionApp(const Extension* extension) {
166 DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
167 if (extension_app_ == extension)
168 return;
170 extension_app_ = extension;
172 UpdateExtensionAppIcon(extension_app_);
174 content::NotificationService::current()->Notify(
175 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
176 content::Source<TabHelper>(this),
177 content::NotificationService::NoDetails());
180 void TabHelper::SetExtensionAppById(const std::string& extension_app_id) {
181 const Extension* extension = GetExtension(extension_app_id);
182 if (extension)
183 SetExtensionApp(extension);
186 void TabHelper::SetExtensionAppIconById(const std::string& extension_app_id) {
187 const Extension* extension = GetExtension(extension_app_id);
188 if (extension)
189 UpdateExtensionAppIcon(extension);
192 SkBitmap* TabHelper::GetExtensionAppIcon() {
193 if (extension_app_icon_.empty())
194 return NULL;
196 return &extension_app_icon_;
199 // Encapsulates the logic to decide which ContentRulesRegistries need to be
200 // invoked, depending on whether this WebContents is associated with an Original
201 // or OffTheRecord profile. In the latter case, we need to invoke on both the
202 // Original and OffTheRecord ContentRulesRegistries since the Original registry
203 // handles spanning-mode incognito extensions.
204 template <class Func>
205 void TabHelper::InvokeForContentRulesRegistries(const Func& func) {
206 RulesRegistryService* rules_registry_service =
207 RulesRegistryService::Get(profile_);
208 if (rules_registry_service) {
209 func(rules_registry_service->content_rules_registry());
210 if (profile_->IsOffTheRecord()) {
211 // The original profile's content rules registry handles rules for
212 // spanning extensions in incognito profiles, so invoke it also.
213 RulesRegistryService* original_profile_rules_registry_service =
214 RulesRegistryService::Get(profile_->GetOriginalProfile());
215 DCHECK_NE(rules_registry_service,
216 original_profile_rules_registry_service);
217 if (original_profile_rules_registry_service)
218 func(original_profile_rules_registry_service->content_rules_registry());
223 void TabHelper::FinishCreateBookmarkApp(
224 const Extension* extension,
225 const WebApplicationInfo& web_app_info) {
226 pending_web_app_action_ = NONE;
229 void TabHelper::RenderFrameCreated(content::RenderFrameHost* host) {
230 SetTabId(host);
233 void TabHelper::DidNavigateMainFrame(
234 const content::LoadCommittedDetails& details,
235 const content::FrameNavigateParams& params) {
236 InvokeForContentRulesRegistries(
237 [this, &details, &params](ContentRulesRegistry* registry) {
238 registry->DidNavigateMainFrame(web_contents(), details, params);
241 content::BrowserContext* context = web_contents()->GetBrowserContext();
242 ExtensionRegistry* registry = ExtensionRegistry::Get(context);
243 const ExtensionSet& enabled_extensions = registry->enabled_extensions();
245 if (util::IsNewBookmarkAppsEnabled()) {
246 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
247 if (browser && browser->is_app()) {
248 const Extension* extension = registry->GetExtensionById(
249 web_app::GetExtensionIdFromApplicationName(browser->app_name()),
250 ExtensionRegistry::EVERYTHING);
251 if (extension && AppLaunchInfo::GetFullLaunchURL(extension).is_valid())
252 SetExtensionApp(extension);
253 } else {
254 UpdateExtensionAppIcon(
255 enabled_extensions.GetExtensionOrAppByURL(params.url));
257 } else {
258 UpdateExtensionAppIcon(
259 enabled_extensions.GetExtensionOrAppByURL(params.url));
262 if (!details.is_in_page)
263 ExtensionActionAPI::Get(context)->ClearAllValuesForTab(web_contents());
266 bool TabHelper::OnMessageReceived(const IPC::Message& message) {
267 bool handled = true;
268 IPC_BEGIN_MESSAGE_MAP(TabHelper, message)
269 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
270 OnDidGetWebApplicationInfo)
271 IPC_MESSAGE_UNHANDLED(handled = false)
272 IPC_END_MESSAGE_MAP()
273 return handled;
276 bool TabHelper::OnMessageReceived(const IPC::Message& message,
277 content::RenderFrameHost* render_frame_host) {
278 bool handled = true;
279 IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(TabHelper, message, render_frame_host)
280 IPC_MESSAGE_HANDLER(ExtensionHostMsg_InlineWebstoreInstall,
281 OnInlineWebstoreInstall)
282 IPC_MESSAGE_HANDLER(ExtensionHostMsg_GetAppInstallState,
283 OnGetAppInstallState);
284 IPC_MESSAGE_HANDLER(ExtensionHostMsg_ContentScriptsExecuting,
285 OnContentScriptsExecuting)
286 IPC_MESSAGE_UNHANDLED(handled = false)
287 IPC_END_MESSAGE_MAP()
288 return handled;
291 void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
292 WebContents* new_web_contents) {
293 // When the WebContents that this is attached to is cloned, give the new clone
294 // a TabHelper and copy state over.
295 CreateForWebContents(new_web_contents);
296 TabHelper* new_helper = FromWebContents(new_web_contents);
298 new_helper->SetExtensionApp(extension_app());
299 new_helper->extension_app_icon_ = extension_app_icon_;
302 void TabHelper::OnDidGetWebApplicationInfo(const WebApplicationInfo& info) {
303 web_app_info_ = info;
305 NavigationEntry* entry =
306 web_contents()->GetController().GetLastCommittedEntry();
307 if (!entry || last_committed_nav_entry_unique_id_ != entry->GetUniqueID())
308 return;
309 last_committed_nav_entry_unique_id_ = 0;
311 switch (pending_web_app_action_) {
312 #if !defined(OS_MACOSX)
313 case CREATE_SHORTCUT: {
314 chrome::ShowCreateWebAppShortcutsDialog(
315 web_contents()->GetTopLevelNativeWindow(),
316 web_contents());
317 break;
319 #endif
320 case CREATE_HOSTED_APP: {
321 if (web_app_info_.app_url.is_empty())
322 web_app_info_.app_url = web_contents()->GetURL();
324 if (web_app_info_.title.empty())
325 web_app_info_.title = web_contents()->GetTitle();
326 if (web_app_info_.title.empty())
327 web_app_info_.title = base::UTF8ToUTF16(web_app_info_.app_url.spec());
329 bookmark_app_helper_.reset(
330 new BookmarkAppHelper(profile_, web_app_info_, web_contents()));
331 bookmark_app_helper_->Create(base::Bind(
332 &TabHelper::FinishCreateBookmarkApp, weak_ptr_factory_.GetWeakPtr()));
333 break;
335 case UPDATE_SHORTCUT: {
336 web_app::UpdateShortcutForTabContents(web_contents());
337 break;
339 default:
340 NOTREACHED();
341 break;
344 // The hosted app action will be cleared once the installation completes or
345 // fails.
346 if (pending_web_app_action_ != CREATE_HOSTED_APP)
347 pending_web_app_action_ = NONE;
350 void TabHelper::OnInlineWebstoreInstall(content::RenderFrameHost* host,
351 int install_id,
352 int return_route_id,
353 const std::string& webstore_item_id,
354 const GURL& requestor_url,
355 int listeners_mask) {
356 // Check that the listener is reasonable. We should never get anything other
357 // than an install stage listener, a download listener, or both.
358 if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
359 api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0 ||
360 requestor_url.is_empty()) {
361 NOTREACHED();
362 return;
364 // Inform the Webstore API that an inline install is happening, in case the
365 // page requested status updates.
366 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
367 if (registry->disabled_extensions().Contains(webstore_item_id) &&
368 (ExtensionPrefs::Get(profile_)->GetDisableReasons(webstore_item_id) &
369 Extension::DISABLE_PERMISSIONS_INCREASE) != 0) {
370 // The extension was disabled due to permissions increase. Prompt for
371 // re-enable.
372 // TODO(devlin): We should also prompt for re-enable for other reasons,
373 // like user-disabled.
374 // For clarity, explicitly end any prior reenable process.
375 extension_reenabler_.reset();
376 extension_reenabler_ = ExtensionReenabler::PromptForReenable(
377 registry->disabled_extensions().GetByID(webstore_item_id),
378 profile_,
379 web_contents(),
380 requestor_url,
381 base::Bind(&TabHelper::OnReenableComplete,
382 weak_ptr_factory_.GetWeakPtr(),
383 install_id,
384 return_route_id));
385 } else {
386 // TODO(devlin): We should adddress the case of the extension already
387 // being installed and enabled.
388 WebstoreAPI::Get(profile_)->OnInlineInstallStart(
389 return_route_id, this, webstore_item_id, listeners_mask);
391 WebstoreStandaloneInstaller::Callback callback =
392 base::Bind(&TabHelper::OnInlineInstallComplete,
393 base::Unretained(this),
394 install_id,
395 return_route_id);
396 scoped_refptr<WebstoreInlineInstaller> installer(
397 webstore_inline_installer_factory_->CreateInstaller(
398 web_contents(),
399 webstore_item_id,
400 requestor_url,
401 callback));
402 installer->BeginInstall();
406 void TabHelper::OnGetAppInstallState(content::RenderFrameHost* host,
407 const GURL& requestor_url,
408 int return_route_id,
409 int callback_id) {
410 ExtensionRegistry* registry =
411 ExtensionRegistry::Get(web_contents()->GetBrowserContext());
412 const ExtensionSet& extensions = registry->enabled_extensions();
413 const ExtensionSet& disabled_extensions = registry->disabled_extensions();
415 std::string state;
416 if (extensions.GetHostedAppByURL(requestor_url))
417 state = extension_misc::kAppStateInstalled;
418 else if (disabled_extensions.GetHostedAppByURL(requestor_url))
419 state = extension_misc::kAppStateDisabled;
420 else
421 state = extension_misc::kAppStateNotInstalled;
423 // We use the |host| to send the message because using
424 // WebContentsObserver::Send() defaults to using the main RenderView, which
425 // might be in a different process if the request came from a frame.
426 host->Send(new ExtensionMsg_GetAppInstallStateResponse(return_route_id, state,
427 callback_id));
430 void TabHelper::OnContentScriptsExecuting(
431 content::RenderFrameHost* host,
432 const ScriptExecutionObserver::ExecutingScriptsMap& executing_scripts_map,
433 const GURL& on_url) {
434 FOR_EACH_OBSERVER(
435 ScriptExecutionObserver,
436 script_execution_observers_,
437 OnScriptsExecuted(web_contents(), executing_scripts_map, on_url));
440 const Extension* TabHelper::GetExtension(const std::string& extension_app_id) {
441 if (extension_app_id.empty())
442 return NULL;
444 content::BrowserContext* context = web_contents()->GetBrowserContext();
445 return ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
446 extension_app_id);
449 void TabHelper::UpdateExtensionAppIcon(const Extension* extension) {
450 extension_app_icon_.reset();
451 // Ensure previously enqueued callbacks are ignored.
452 image_loader_ptr_factory_.InvalidateWeakPtrs();
454 // Enqueue OnImageLoaded callback.
455 if (extension) {
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()));
469 void TabHelper::SetAppIcon(const SkBitmap& app_icon) {
470 extension_app_icon_ = app_icon;
471 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TITLE);
474 void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
475 WebstoreInlineInstallerFactory* factory) {
476 webstore_inline_installer_factory_.reset(factory);
479 void TabHelper::OnImageLoaded(const gfx::Image& image) {
480 if (!image.IsEmpty()) {
481 extension_app_icon_ = *image.ToSkBitmap();
482 web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
486 WindowController* TabHelper::GetExtensionWindowController() const {
487 return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
490 void TabHelper::OnReenableComplete(int install_id,
491 int return_route_id,
492 ExtensionReenabler::ReenableResult result) {
493 extension_reenabler_.reset();
494 // Map the re-enable results to webstore-install results.
495 webstore_install::Result webstore_result = webstore_install::SUCCESS;
496 std::string error;
497 switch (result) {
498 case ExtensionReenabler::REENABLE_SUCCESS:
499 break; // already set
500 case ExtensionReenabler::USER_CANCELED:
501 webstore_result = webstore_install::USER_CANCELLED;
502 error = "User canceled install.";
503 break;
504 case ExtensionReenabler::NOT_ALLOWED:
505 webstore_result = webstore_install::NOT_PERMITTED;
506 error = "Install not permitted.";
507 break;
508 case ExtensionReenabler::ABORTED:
509 webstore_result = webstore_install::ABORTED;
510 error = "Aborted due to tab closing.";
511 break;
514 OnInlineInstallComplete(install_id,
515 return_route_id,
516 result == ExtensionReenabler::REENABLE_SUCCESS,
517 error,
518 webstore_result);
521 void TabHelper::OnInlineInstallComplete(int install_id,
522 int return_route_id,
523 bool success,
524 const std::string& error,
525 webstore_install::Result result) {
526 Send(new ExtensionMsg_InlineWebstoreInstallResponse(
527 return_route_id,
528 install_id,
529 success,
530 success ? std::string() : error,
531 result));
534 WebContents* TabHelper::GetAssociatedWebContents() const {
535 return web_contents();
538 void TabHelper::GetApplicationInfo(WebAppAction action) {
539 NavigationEntry* entry =
540 web_contents()->GetController().GetLastCommittedEntry();
541 if (!entry)
542 return;
544 pending_web_app_action_ = action;
545 last_committed_nav_entry_unique_id_ = entry->GetUniqueID();
547 Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
550 void TabHelper::Observe(int type,
551 const content::NotificationSource& source,
552 const content::NotificationDetails& details) {
553 DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
554 const NavigationController& controller =
555 *content::Source<NavigationController>(source).ptr();
556 DCHECK_EQ(controller.GetWebContents(), web_contents());
558 if (update_shortcut_on_load_complete_) {
559 update_shortcut_on_load_complete_ = false;
560 // Schedule a shortcut update when web application info is available if
561 // last committed entry is not NULL. Last committed entry could be NULL
562 // when an interstitial page is injected (e.g. bad https certificate,
563 // malware site etc). When this happens, we abort the shortcut update.
564 if (controller.GetLastCommittedEntry())
565 GetApplicationInfo(UPDATE_SHORTCUT);
569 void TabHelper::SetTabId(content::RenderFrameHost* render_frame_host) {
570 render_frame_host->Send(
571 new ExtensionMsg_SetTabId(render_frame_host->GetRoutingID(),
572 SessionTabHelper::IdForTab(web_contents())));
575 } // namespace extensions