Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / chromeos / first_run / drive_first_run_controller.cc
blob5154e0884f471cfb49a9f2007eda5d052cc3c8c8
1 // Copyright 2013 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/chromeos/first_run/drive_first_run_controller.h"
7 #include "ash/shell.h"
8 #include "ash/system/tray/system_tray_delegate.h"
9 #include "base/callback.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/background/background_contents_service.h"
15 #include "chrome/browser/background/background_contents_service_factory.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
18 #include "chrome/browser/extensions/extension_service.h"
19 #include "chrome/browser/tab_contents/background_contents.h"
20 #include "chrome/browser/ui/browser_navigator.h"
21 #include "chrome/browser/ui/host_desktop.h"
22 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
23 #include "chrome/browser/ui/singleton_tabs.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "chrome/grit/theme_resources.h"
26 #include "components/user_manager/user_manager.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/navigation_controller.h"
29 #include "content/public/browser/notification_details.h"
30 #include "content/public/browser/notification_observer.h"
31 #include "content/public/browser/notification_registrar.h"
32 #include "content/public/browser/notification_source.h"
33 #include "content/public/browser/notification_types.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/site_instance.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/browser/web_contents_observer.h"
38 #include "extensions/browser/extension_registry.h"
39 #include "extensions/browser/extension_system.h"
40 #include "extensions/common/extension.h"
41 #include "extensions/common/extension_set.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/resource/resource_bundle.h"
44 #include "ui/message_center/message_center.h"
45 #include "ui/message_center/notification.h"
46 #include "ui/message_center/notification_delegate.h"
47 #include "url/gurl.h"
49 namespace chromeos {
51 namespace {
53 // The initial time to wait in seconds before enabling offline mode.
54 int kInitialDelaySeconds = 180;
56 // Time to wait for Drive app background page to come up before giving up.
57 int kWebContentsTimeoutSeconds = 15;
59 // Google Drive enable offline endpoint.
60 const char kDriveOfflineEndpointUrl[] =
61 "https://docs.google.com/offline/autoenable";
63 // Google Drive app id.
64 const char kDriveHostedAppId[] = "apdfllckaahabafndbhieahigkjlhalf";
66 // Id of the notification shown when offline mode is enabled.
67 const char kDriveOfflineNotificationId[] = "chrome://drive/enable-offline";
69 // The URL of the support page opened when the notification button is clicked.
70 const char kDriveOfflineSupportUrl[] =
71 "https://support.google.com/drive/answer/1628467";
73 } // namespace
75 ////////////////////////////////////////////////////////////////////////////////
76 // DriveOfflineNotificationDelegate
78 // NotificationDelegate for the notification that is displayed when Drive
79 // offline mode is enabled automatically. Clicking on the notification button
80 // will open the Drive settings page.
81 class DriveOfflineNotificationDelegate
82 : public message_center::NotificationDelegate {
83 public:
84 explicit DriveOfflineNotificationDelegate(Profile* profile)
85 : profile_(profile) {}
87 // message_center::NotificationDelegate overrides:
88 virtual void Display() override {}
89 virtual void Error() override {}
90 virtual void Close(bool by_user) override {}
91 virtual void Click() override {}
92 virtual void ButtonClick(int button_index) override;
94 protected:
95 virtual ~DriveOfflineNotificationDelegate() {}
97 private:
98 Profile* profile_;
100 DISALLOW_COPY_AND_ASSIGN(DriveOfflineNotificationDelegate);
103 void DriveOfflineNotificationDelegate::ButtonClick(int button_index) {
104 DCHECK_EQ(0, button_index);
106 // The support page will be localized based on the user's GAIA account.
107 const GURL url = GURL(kDriveOfflineSupportUrl);
109 chrome::ScopedTabbedBrowserDisplayer displayer(
110 profile_,
111 chrome::HOST_DESKTOP_TYPE_ASH);
112 chrome::ShowSingletonTabOverwritingNTP(
113 displayer.browser(),
114 chrome::GetSingletonTabNavigateParams(displayer.browser(), url));
117 ////////////////////////////////////////////////////////////////////////////////
118 // DriveWebContentsManager
120 // Manages web contents that initializes Google Drive offline mode. We create
121 // a background WebContents that loads a Drive endpoint to initialize offline
122 // mode. If successful, a background page will be opened to sync the user's
123 // files for offline use.
124 class DriveWebContentsManager : public content::WebContentsObserver,
125 public content::WebContentsDelegate,
126 public content::NotificationObserver {
127 public:
128 typedef base::Callback<
129 void(bool, DriveFirstRunController::UMAOutcome)> CompletionCallback;
131 DriveWebContentsManager(Profile* profile,
132 const std::string& app_id,
133 const std::string& endpoint_url,
134 const CompletionCallback& completion_callback);
135 virtual ~DriveWebContentsManager();
137 // Start loading the WebContents for the endpoint in the context of the Drive
138 // hosted app that will initialize offline mode and open a background page.
139 void StartLoad();
141 // Stop loading the endpoint. The |completion_callback| will not be called.
142 void StopLoad();
144 private:
145 // Called when when offline initialization succeeds or fails and schedules
146 // |RunCompletionCallback|.
147 void OnOfflineInit(bool success,
148 DriveFirstRunController::UMAOutcome outcome);
150 // Runs |completion_callback|.
151 void RunCompletionCallback(bool success,
152 DriveFirstRunController::UMAOutcome outcome);
154 // content::WebContentsObserver overrides:
155 virtual void DidFailProvisionalLoad(
156 content::RenderFrameHost* render_frame_host,
157 const GURL& validated_url,
158 int error_code,
159 const base::string16& error_description) override;
161 virtual void DidFailLoad(content::RenderFrameHost* render_frame_host,
162 const GURL& validated_url,
163 int error_code,
164 const base::string16& error_description) override;
166 // content::WebContentsDelegate overrides:
167 virtual bool ShouldCreateWebContents(
168 content::WebContents* web_contents,
169 int route_id,
170 WindowContainerType window_container_type,
171 const base::string16& frame_name,
172 const GURL& target_url,
173 const std::string& partition_id,
174 content::SessionStorageNamespace* session_storage_namespace) override;
176 // content::NotificationObserver overrides:
177 virtual void Observe(int type,
178 const content::NotificationSource& source,
179 const content::NotificationDetails& details) override;
181 Profile* profile_;
182 const std::string app_id_;
183 const std::string endpoint_url_;
184 scoped_ptr<content::WebContents> web_contents_;
185 content::NotificationRegistrar registrar_;
186 bool started_;
187 CompletionCallback completion_callback_;
188 base::WeakPtrFactory<DriveWebContentsManager> weak_ptr_factory_;
190 DISALLOW_COPY_AND_ASSIGN(DriveWebContentsManager);
193 DriveWebContentsManager::DriveWebContentsManager(
194 Profile* profile,
195 const std::string& app_id,
196 const std::string& endpoint_url,
197 const CompletionCallback& completion_callback)
198 : profile_(profile),
199 app_id_(app_id),
200 endpoint_url_(endpoint_url),
201 started_(false),
202 completion_callback_(completion_callback),
203 weak_ptr_factory_(this) {
204 DCHECK(!completion_callback_.is_null());
205 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
206 content::Source<Profile>(profile_));
209 DriveWebContentsManager::~DriveWebContentsManager() {
212 void DriveWebContentsManager::StartLoad() {
213 started_ = true;
214 const GURL url(endpoint_url_);
215 content::WebContents::CreateParams create_params(
216 profile_, content::SiteInstance::CreateForURL(profile_, url));
218 web_contents_.reset(content::WebContents::Create(create_params));
219 web_contents_->SetDelegate(this);
220 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
221 web_contents_.get());
223 content::NavigationController::LoadURLParams load_params(url);
224 load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
225 web_contents_->GetController().LoadURLWithParams(load_params);
227 content::WebContentsObserver::Observe(web_contents_.get());
230 void DriveWebContentsManager::StopLoad() {
231 started_ = false;
234 void DriveWebContentsManager::OnOfflineInit(
235 bool success,
236 DriveFirstRunController::UMAOutcome outcome) {
237 if (started_) {
238 // We postpone notifying the controller as we may be in the middle
239 // of a call stack for some routine of the contained WebContents.
240 base::MessageLoop::current()->PostTask(
241 FROM_HERE,
242 base::Bind(&DriveWebContentsManager::RunCompletionCallback,
243 weak_ptr_factory_.GetWeakPtr(),
244 success,
245 outcome));
246 StopLoad();
250 void DriveWebContentsManager::RunCompletionCallback(
251 bool success,
252 DriveFirstRunController::UMAOutcome outcome) {
253 completion_callback_.Run(success, outcome);
256 void DriveWebContentsManager::DidFailProvisionalLoad(
257 content::RenderFrameHost* render_frame_host,
258 const GURL& validated_url,
259 int error_code,
260 const base::string16& error_description) {
261 if (!render_frame_host->GetParent()) {
262 LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
263 OnOfflineInit(false,
264 DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
268 void DriveWebContentsManager::DidFailLoad(
269 content::RenderFrameHost* render_frame_host,
270 const GURL& validated_url,
271 int error_code,
272 const base::string16& error_description) {
273 if (!render_frame_host->GetParent()) {
274 LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
275 OnOfflineInit(false,
276 DriveFirstRunController::OUTCOME_WEB_CONTENTS_LOAD_FAILED);
280 bool DriveWebContentsManager::ShouldCreateWebContents(
281 content::WebContents* web_contents,
282 int route_id,
283 WindowContainerType window_container_type,
284 const base::string16& frame_name,
285 const GURL& target_url,
286 const std::string& partition_id,
287 content::SessionStorageNamespace* session_storage_namespace) {
289 if (window_container_type == WINDOW_CONTAINER_TYPE_NORMAL)
290 return true;
292 // Check that the target URL is for the Drive app.
293 const extensions::Extension* extension =
294 extensions::ExtensionRegistry::Get(profile_)
295 ->enabled_extensions().GetAppByURL(target_url);
296 if (!extension || extension->id() != app_id_)
297 return true;
299 // The background contents creation is normally done in Browser, but
300 // because we're using a detached WebContents, we need to do it ourselves.
301 BackgroundContentsService* background_contents_service =
302 BackgroundContentsServiceFactory::GetForProfile(profile_);
304 // Prevent redirection if background contents already exists.
305 if (background_contents_service->GetAppBackgroundContents(
306 base::UTF8ToUTF16(app_id_))) {
307 return false;
309 BackgroundContents* contents = background_contents_service
310 ->CreateBackgroundContents(content::SiteInstance::Create(profile_),
311 route_id,
312 profile_,
313 frame_name,
314 base::ASCIIToUTF16(app_id_),
315 partition_id,
316 session_storage_namespace);
318 contents->web_contents()->GetController().LoadURL(
319 target_url,
320 content::Referrer(),
321 ui::PAGE_TRANSITION_LINK,
322 std::string());
324 // Return false as we already created the WebContents here.
325 return false;
328 void DriveWebContentsManager::Observe(
329 int type,
330 const content::NotificationSource& source,
331 const content::NotificationDetails& details) {
332 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED) {
333 const std::string app_id = base::UTF16ToUTF8(
334 content::Details<BackgroundContentsOpenedDetails>(details)
335 ->application_id);
336 if (app_id == app_id_)
337 OnOfflineInit(true, DriveFirstRunController::OUTCOME_OFFLINE_ENABLED);
341 ////////////////////////////////////////////////////////////////////////////////
342 // DriveFirstRunController
344 DriveFirstRunController::DriveFirstRunController(Profile* profile)
345 : profile_(profile),
346 started_(false),
347 initial_delay_secs_(kInitialDelaySeconds),
348 web_contents_timeout_secs_(kWebContentsTimeoutSeconds),
349 drive_offline_endpoint_url_(kDriveOfflineEndpointUrl),
350 drive_hosted_app_id_(kDriveHostedAppId) {
353 DriveFirstRunController::~DriveFirstRunController() {
356 void DriveFirstRunController::EnableOfflineMode() {
357 if (!started_) {
358 started_ = true;
359 initial_delay_timer_.Start(
360 FROM_HERE,
361 base::TimeDelta::FromSeconds(initial_delay_secs_),
362 this,
363 &DriveFirstRunController::EnableOfflineMode);
364 return;
367 if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser()) {
368 LOG(ERROR) << "Attempting to enable offline access "
369 "but not logged in a regular user.";
370 OnOfflineInit(false, OUTCOME_WRONG_USER_TYPE);
371 return;
374 ExtensionService* extension_service =
375 extensions::ExtensionSystem::Get(profile_)->extension_service();
376 if (!extension_service->GetExtensionById(drive_hosted_app_id_, false)) {
377 LOG(WARNING) << "Drive app is not installed.";
378 OnOfflineInit(false, OUTCOME_APP_NOT_INSTALLED);
379 return;
382 BackgroundContentsService* background_contents_service =
383 BackgroundContentsServiceFactory::GetForProfile(profile_);
384 if (background_contents_service->GetAppBackgroundContents(
385 base::UTF8ToUTF16(drive_hosted_app_id_))) {
386 LOG(WARNING) << "Background page for Drive app already exists";
387 OnOfflineInit(false, OUTCOME_BACKGROUND_PAGE_EXISTS);
388 return;
391 web_contents_manager_.reset(new DriveWebContentsManager(
392 profile_,
393 drive_hosted_app_id_,
394 drive_offline_endpoint_url_,
395 base::Bind(&DriveFirstRunController::OnOfflineInit,
396 base::Unretained(this))));
397 web_contents_manager_->StartLoad();
398 web_contents_timer_.Start(
399 FROM_HERE,
400 base::TimeDelta::FromSeconds(web_contents_timeout_secs_),
401 this,
402 &DriveFirstRunController::OnWebContentsTimedOut);
405 void DriveFirstRunController::AddObserver(Observer* observer) {
406 observer_list_.AddObserver(observer);
409 void DriveFirstRunController::RemoveObserver(Observer* observer) {
410 observer_list_.RemoveObserver(observer);
413 void DriveFirstRunController::SetDelaysForTest(int initial_delay_secs,
414 int timeout_secs) {
415 DCHECK(!started_);
416 initial_delay_secs_ = initial_delay_secs;
417 web_contents_timeout_secs_ = timeout_secs;
420 void DriveFirstRunController::SetAppInfoForTest(
421 const std::string& app_id,
422 const std::string& endpoint_url) {
423 DCHECK(!started_);
424 drive_hosted_app_id_ = app_id;
425 drive_offline_endpoint_url_ = endpoint_url;
428 void DriveFirstRunController::OnWebContentsTimedOut() {
429 LOG(WARNING) << "Timed out waiting for web contents.";
430 FOR_EACH_OBSERVER(Observer, observer_list_, OnTimedOut());
431 OnOfflineInit(false, OUTCOME_WEB_CONTENTS_TIMED_OUT);
434 void DriveFirstRunController::CleanUp() {
435 if (web_contents_manager_)
436 web_contents_manager_->StopLoad();
437 web_contents_timer_.Stop();
438 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
441 void DriveFirstRunController::OnOfflineInit(bool success, UMAOutcome outcome) {
442 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
443 if (success)
444 ShowNotification();
445 UMA_HISTOGRAM_ENUMERATION("DriveOffline.CrosAutoEnableOutcome",
446 outcome, OUTCOME_MAX);
447 FOR_EACH_OBSERVER(Observer, observer_list_, OnCompletion(success));
448 CleanUp();
451 void DriveFirstRunController::ShowNotification() {
452 ExtensionService* service =
453 extensions::ExtensionSystem::Get(profile_)->extension_service();
454 DCHECK(service);
455 const extensions::Extension* extension =
456 service->GetExtensionById(drive_hosted_app_id_, false);
457 DCHECK(extension);
459 message_center::RichNotificationData data;
460 data.buttons.push_back(message_center::ButtonInfo(
461 l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_BUTTON)));
462 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
463 scoped_ptr<message_center::Notification> notification(
464 new message_center::Notification(
465 message_center::NOTIFICATION_TYPE_SIMPLE,
466 kDriveOfflineNotificationId,
467 base::string16(), // title
468 l10n_util::GetStringUTF16(IDS_DRIVE_OFFLINE_NOTIFICATION_MESSAGE),
469 resource_bundle.GetImageNamed(IDR_NOTIFICATION_DRIVE),
470 base::UTF8ToUTF16(extension->name()),
471 message_center::NotifierId(message_center::NotifierId::APPLICATION,
472 kDriveHostedAppId),
473 data,
474 new DriveOfflineNotificationDelegate(profile_)));
475 notification->set_priority(message_center::LOW_PRIORITY);
476 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
479 } // namespace chromeos