Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / app_list / start_page_service.cc
blob5095679bb00315c069bbe3a711f1e5f8af0c9f07
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/ui/app_list/start_page_service.h"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/json/json_string_value_serializer.h"
12 #include "base/memory/singleton.h"
13 #include "base/metrics/user_metrics.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_piece.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/media/media_stream_infobar_delegate.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/search/hotword_service.h"
21 #include "chrome/browser/search/hotword_service_factory.h"
22 #include "chrome/browser/search_engines/template_url_service_factory.h"
23 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
24 #include "chrome/browser/ui/app_list/speech_auth_helper.h"
25 #include "chrome/browser/ui/app_list/speech_recognizer.h"
26 #include "chrome/browser/ui/app_list/start_page_observer.h"
27 #include "chrome/browser/ui/app_list/start_page_service_factory.h"
28 #include "chrome/browser/ui/browser_navigator.h"
29 #include "chrome/browser/ui/browser_tabstrip.h"
30 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/url_constants.h"
34 #include "components/search_engines/template_url_prepopulate_data.h"
35 #include "components/search_engines/template_url_service.h"
36 #include "components/ui/zoom/zoom_controller.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/notification_details.h"
39 #include "content/public/browser/notification_observer.h"
40 #include "content/public/browser/notification_registrar.h"
41 #include "content/public/browser/notification_service.h"
42 #include "content/public/browser/notification_source.h"
43 #include "content/public/browser/render_view_host.h"
44 #include "content/public/browser/render_widget_host_view.h"
45 #include "content/public/browser/speech_recognition_session_preamble.h"
46 #include "content/public/browser/web_contents.h"
47 #include "content/public/browser/web_contents_delegate.h"
48 #include "content/public/common/content_switches.h"
49 #include "extensions/browser/extension_system_provider.h"
50 #include "extensions/browser/extensions_browser_client.h"
51 #include "extensions/common/extension.h"
52 #include "net/base/load_flags.h"
53 #include "net/base/network_change_notifier.h"
54 #include "net/url_request/url_fetcher.h"
55 #include "ui/app_list/app_list_switches.h"
57 #if defined(OS_CHROMEOS)
58 #include "chromeos/audio/cras_audio_handler.h"
59 #endif
61 using base::RecordAction;
62 using base::UserMetricsAction;
64 namespace app_list {
66 namespace {
68 // Path to google.com's doodle JSON.
69 const char kDoodleJsonPath[] = "async/ddljson";
71 // Maximum delay between checking for a new doodle when the doodle cannot be
72 // retrieved. This is also used as the delay once a doodle is retrieved.
73 const int kMaximumRecheckDelayMs = 1000 * 60 * 30; // 30 minutes.
75 // Delay before loading the start page WebContents on initialization.
76 const int kLoadContentsDelaySeconds = 5;
78 const net::BackoffEntry::Policy kDoodleBackoffPolicy = {
79 // Number of initial errors (in sequence) to ignore before applying
80 // exponential back-off rules.
83 // Initial delay for exponential back-off in ms.
84 2500,
86 // Factor by which the waiting time will be multiplied.
89 // Fuzzing percentage. ex: 10% will spread requests randomly
90 // between 90%-100% of the calculated time.
91 0.4,
93 // Maximum amount of time we are willing to delay our request in ms.
94 kMaximumRecheckDelayMs,
96 // Time to keep an entry from being discarded even when it
97 // has no significant state, -1 to never discard.
98 -1,
100 // Don't use initial delay unless the last request was an error.
101 false,
104 bool InSpeechRecognition(SpeechRecognitionState state) {
105 return state == SPEECH_RECOGNITION_RECOGNIZING ||
106 state == SPEECH_RECOGNITION_IN_SPEECH;
109 } // namespace
111 class StartPageService::ProfileDestroyObserver
112 : public content::NotificationObserver {
113 public:
114 explicit ProfileDestroyObserver(StartPageService* service)
115 : service_(service) {
116 if (service_->profile()->IsOffTheRecord()) {
117 // We need to be notified when the original profile gets destroyed as well
118 // as the OTR profile, because the original profile will be destroyed
119 // first, and a DCHECK at that time ensures that the OTR profile has 0
120 // hosts. See http://crbug.com/463419.
121 registrar_.Add(
122 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
123 content::Source<Profile>(service_->profile()->GetOriginalProfile()));
125 registrar_.Add(this,
126 chrome::NOTIFICATION_PROFILE_DESTROYED,
127 content::Source<Profile>(service_->profile()));
129 ~ProfileDestroyObserver() override {}
131 private:
132 // content::NotificationObserver
133 void Observe(int type,
134 const content::NotificationSource& source,
135 const content::NotificationDetails& details) override {
136 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
137 DCHECK(service_->profile()->IsSameProfile(
138 content::Source<Profile>(source).ptr()));
139 registrar_.RemoveAll();
140 service_->Shutdown();
143 StartPageService* service_; // Owner of this class.
144 content::NotificationRegistrar registrar_;
146 DISALLOW_COPY_AND_ASSIGN(ProfileDestroyObserver);
149 class StartPageService::StartPageWebContentsDelegate
150 : public content::WebContentsDelegate {
151 public:
152 explicit StartPageWebContentsDelegate(Profile* profile) : profile_(profile) {}
153 ~StartPageWebContentsDelegate() override {}
155 void RequestMediaAccessPermission(
156 content::WebContents* web_contents,
157 const content::MediaStreamRequest& request,
158 const content::MediaResponseCallback& callback) override {
159 if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
160 NOTREACHED() << "Media stream not allowed for WebUI";
163 bool CheckMediaAccessPermission(content::WebContents* web_contents,
164 const GURL& security_origin,
165 content::MediaStreamType type) override {
166 return MediaCaptureDevicesDispatcher::GetInstance()
167 ->CheckMediaAccessPermission(web_contents, security_origin, type);
170 void AddNewContents(content::WebContents* source,
171 content::WebContents* new_contents,
172 WindowOpenDisposition disposition,
173 const gfx::Rect& initial_pos,
174 bool user_gesture,
175 bool* was_blocked) override {
176 chrome::ScopedTabbedBrowserDisplayer displayer(
177 profile_, chrome::GetActiveDesktop());
178 // Force all links to open in a new tab, even if they were trying to open a
179 // new window.
180 disposition =
181 disposition == NEW_BACKGROUND_TAB ? disposition : NEW_FOREGROUND_TAB;
182 chrome::AddWebContents(displayer.browser(),
183 nullptr,
184 new_contents,
185 disposition,
186 initial_pos,
187 user_gesture,
188 was_blocked);
191 content::WebContents* OpenURLFromTab(
192 content::WebContents* source,
193 const content::OpenURLParams& params) override {
194 // Force all links to open in a new tab, even if they were trying to open a
195 // window.
196 chrome::NavigateParams new_tab_params(
197 static_cast<Browser*>(nullptr), params.url, params.transition);
198 if (params.disposition == NEW_BACKGROUND_TAB) {
199 new_tab_params.disposition = NEW_BACKGROUND_TAB;
200 } else {
201 new_tab_params.disposition = NEW_FOREGROUND_TAB;
202 new_tab_params.window_action = chrome::NavigateParams::SHOW_WINDOW;
205 new_tab_params.initiating_profile = profile_;
206 chrome::Navigate(&new_tab_params);
208 return new_tab_params.target_contents;
211 bool PreHandleGestureEvent(content::WebContents* /*source*/,
212 const blink::WebGestureEvent& event) override {
213 // Disable pinch zooming on the start page web contents.
214 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
215 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
216 event.type == blink::WebGestureEvent::GesturePinchEnd;
220 private:
221 Profile* profile_;
223 DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
226 #if defined(OS_CHROMEOS)
228 class StartPageService::AudioStatus
229 : public chromeos::CrasAudioHandler::AudioObserver {
230 public:
231 explicit AudioStatus(StartPageService* start_page_service)
232 : start_page_service_(start_page_service) {
233 chromeos::CrasAudioHandler::Get()->AddAudioObserver(this);
234 CheckAndUpdate();
237 ~AudioStatus() override {
238 chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
241 bool CanListen() {
242 chromeos::CrasAudioHandler* audio_handler =
243 chromeos::CrasAudioHandler::Get();
244 return (audio_handler->GetPrimaryActiveInputNode() != 0) &&
245 !audio_handler->IsInputMuted();
248 private:
249 void CheckAndUpdate() {
250 // TODO(mukai): If the system can listen, this should also restart the
251 // hotword recognition.
252 start_page_service_->OnMicrophoneChanged(CanListen());
255 // chromeos::CrasAudioHandler::AudioObserver:
256 void OnInputMuteChanged(bool /* mute_on */) override { CheckAndUpdate(); }
258 void OnActiveInputNodeChanged() override { CheckAndUpdate(); }
260 StartPageService* start_page_service_;
262 DISALLOW_COPY_AND_ASSIGN(AudioStatus);
265 #endif // OS_CHROMEOS
267 class StartPageService::NetworkChangeObserver
268 : public net::NetworkChangeNotifier::NetworkChangeObserver {
269 public:
270 explicit NetworkChangeObserver(StartPageService* start_page_service)
271 : start_page_service_(start_page_service) {
272 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
273 // NOTE: This is used to detect network connectivity changes. However, what
274 // we really want is internet connectivity changes because voice recognition
275 // needs to talk to a web service. However, this information isn't
276 // available, so network changes are the best we can do.
277 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
279 last_type_ = net::NetworkChangeNotifier::GetConnectionType();
280 // Handle the case where we're started with no network available.
281 if (last_type_ == net::NetworkChangeNotifier::CONNECTION_NONE)
282 start_page_service_->OnNetworkChanged(false);
285 ~NetworkChangeObserver() override {
286 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
287 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
290 private:
291 void OnNetworkChanged(
292 net::NetworkChangeNotifier::ConnectionType type) override {
293 // Threading note: NetworkChangeNotifier's contract is that observers are
294 // called on the same thread that they're registered. In this case, it
295 // should always be the UI thread.
296 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
297 if (type == net::NetworkChangeNotifier::CONNECTION_NONE) {
298 start_page_service_->OnNetworkChanged(false);
299 } else if (last_type_ == net::NetworkChangeNotifier::CONNECTION_NONE &&
300 type != net::NetworkChangeNotifier::CONNECTION_NONE) {
301 start_page_service_->OnNetworkChanged(true);
303 last_type_ = type;
306 StartPageService* start_page_service_;
307 net::NetworkChangeNotifier::ConnectionType last_type_;
309 DISALLOW_COPY_AND_ASSIGN(NetworkChangeObserver);
312 // static
313 StartPageService* StartPageService::Get(Profile* profile) {
314 return StartPageServiceFactory::GetForProfile(profile);
317 StartPageService::StartPageService(Profile* profile)
318 : profile_(profile),
319 profile_destroy_observer_(new ProfileDestroyObserver(this)),
320 state_(app_list::SPEECH_RECOGNITION_READY),
321 speech_button_toggled_manually_(false),
322 speech_result_obtained_(false),
323 webui_finished_loading_(false),
324 speech_auth_helper_(new SpeechAuthHelper(profile, &clock_)),
325 network_available_(true),
326 microphone_available_(true),
327 search_engine_is_google_(false),
328 backoff_entry_(&kDoodleBackoffPolicy),
329 weak_factory_(this) {
330 if (switches::IsExperimentalAppListEnabled()) {
331 TemplateURLService* template_url_service =
332 TemplateURLServiceFactory::GetForProfile(profile_);
333 const TemplateURL* default_provider =
334 template_url_service->GetDefaultSearchProvider();
335 search_engine_is_google_ =
336 TemplateURLPrepopulateData::GetEngineType(
337 *default_provider, template_url_service->search_terms_data()) ==
338 SEARCH_ENGINE_GOOGLE;
341 network_change_observer_.reset(new NetworkChangeObserver(this));
344 StartPageService::~StartPageService() {
347 void StartPageService::AddObserver(StartPageObserver* observer) {
348 observers_.AddObserver(observer);
351 void StartPageService::RemoveObserver(StartPageObserver* observer) {
352 observers_.RemoveObserver(observer);
355 void StartPageService::OnMicrophoneChanged(bool available) {
356 microphone_available_ = available;
357 UpdateRecognitionState();
360 void StartPageService::OnNetworkChanged(bool available) {
361 network_available_ = available;
362 UpdateRecognitionState();
365 void StartPageService::UpdateRecognitionState() {
366 if (ShouldEnableSpeechRecognition()) {
367 if (state_ == SPEECH_RECOGNITION_OFF ||
368 state_ == SPEECH_RECOGNITION_NETWORK_ERROR)
369 OnSpeechRecognitionStateChanged(SPEECH_RECOGNITION_READY);
370 } else {
371 OnSpeechRecognitionStateChanged(network_available_ ? SPEECH_RECOGNITION_OFF
372 : SPEECH_RECOGNITION_NETWORK_ERROR);
376 void StartPageService::Init() {
377 // Do not load the start page web contents in tests because many tests assume
378 // no WebContents exist except the ones they make.
379 if (switches::IsExperimentalAppListEnabled() &&
380 !base::CommandLine::ForCurrentProcess()->HasSwitch(
381 ::switches::kTestType)) {
382 content::BrowserThread::PostDelayedTask(
383 content::BrowserThread::UI, FROM_HERE,
384 base::Bind(&StartPageService::LoadContentsIfNeeded,
385 weak_factory_.GetWeakPtr()),
386 base::TimeDelta::FromSeconds(kLoadContentsDelaySeconds));
390 void StartPageService::LoadContentsIfNeeded() {
391 if (!contents_)
392 LoadContents();
395 bool StartPageService::ShouldEnableSpeechRecognition() const {
396 return microphone_available_ && network_available_;
399 void StartPageService::AppListShown() {
400 if (!contents_) {
401 LoadContents();
402 } else if (contents_->IsCrashed()) {
403 LoadStartPageURL();
404 } else if (contents_->GetWebUI()) {
405 contents_->GetWebUI()->CallJavascriptFunction(
406 "appList.startPage.onAppListShown");
409 #if defined(OS_CHROMEOS)
410 audio_status_.reset(new AudioStatus(this));
411 #endif
414 void StartPageService::AppListHidden() {
415 if (!app_list::switches::IsExperimentalAppListEnabled())
416 UnloadContents();
418 if (speech_recognizer_) {
419 speech_recognizer_->Stop();
420 speech_recognizer_.reset();
422 // When the SpeechRecognizer is destroyed above, we get stuck in the current
423 // speech state instead of being reset into the READY state. Reset the
424 // speech state explicitly so that speech works when the launcher is opened
425 // again.
426 OnSpeechRecognitionStateChanged(SPEECH_RECOGNITION_READY);
429 #if defined(OS_CHROMEOS)
430 audio_status_.reset();
431 #endif
434 void StartPageService::ToggleSpeechRecognition(
435 const scoped_refptr<content::SpeechRecognitionSessionPreamble>& preamble) {
436 DCHECK(contents_);
437 speech_button_toggled_manually_ = true;
439 if (!speech_recognizer_) {
440 std::string profile_locale;
441 #if defined(OS_CHROMEOS)
442 profile_locale = profile_->GetPrefs()->GetString(
443 prefs::kApplicationLocale);
444 #endif
445 if (profile_locale.empty())
446 profile_locale = g_browser_process->GetApplicationLocale();
448 speech_recognizer_.reset(
449 new SpeechRecognizer(weak_factory_.GetWeakPtr(),
450 profile_->GetRequestContext(),
451 profile_locale));
454 speech_recognizer_->Start(preamble);
457 bool StartPageService::HotwordEnabled() {
458 // Voice input for the launcher is unsupported on non-ChromeOS platforms.
459 // TODO(amistry): Make speech input, and hotwording, work on non-ChromeOS.
460 #if defined(OS_CHROMEOS)
461 HotwordService* service = HotwordServiceFactory::GetForProfile(profile_);
462 return state_ != SPEECH_RECOGNITION_OFF &&
463 service &&
464 (service->IsSometimesOnEnabled() || service->IsAlwaysOnEnabled()) &&
465 service->IsServiceAvailable();
466 #else
467 return false;
468 #endif
471 content::WebContents* StartPageService::GetStartPageContents() {
472 return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
473 : NULL;
476 content::WebContents* StartPageService::GetSpeechRecognitionContents() {
477 if (app_list::switches::IsVoiceSearchEnabled()) {
478 if (!contents_)
479 LoadContents();
480 return contents_.get();
482 return NULL;
485 void StartPageService::OnSpeechResult(
486 const base::string16& query, bool is_final) {
487 if (is_final) {
488 speech_result_obtained_ = true;
489 RecordAction(UserMetricsAction("AppList_SearchedBySpeech"));
491 FOR_EACH_OBSERVER(StartPageObserver,
492 observers_,
493 OnSpeechResult(query, is_final));
496 void StartPageService::OnSpeechSoundLevelChanged(int16_t level) {
497 FOR_EACH_OBSERVER(StartPageObserver,
498 observers_,
499 OnSpeechSoundLevelChanged(level));
502 void StartPageService::OnSpeechRecognitionStateChanged(
503 SpeechRecognitionState new_state) {
504 #if defined(OS_CHROMEOS)
505 // Sometimes this can be called even though there are no audio input devices.
506 if (audio_status_ && !audio_status_->CanListen())
507 new_state = SPEECH_RECOGNITION_OFF;
508 #endif
509 if (!microphone_available_)
510 new_state = SPEECH_RECOGNITION_OFF;
511 if (!network_available_)
512 new_state = SPEECH_RECOGNITION_NETWORK_ERROR;
514 if (state_ == new_state)
515 return;
517 if ((new_state == SPEECH_RECOGNITION_READY ||
518 new_state == SPEECH_RECOGNITION_OFF ||
519 new_state == SPEECH_RECOGNITION_NETWORK_ERROR) &&
520 speech_recognizer_) {
521 speech_recognizer_->Stop();
524 if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
525 if (!speech_button_toggled_manually_ &&
526 state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
527 RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
528 } else {
529 RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
531 } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
532 !speech_result_obtained_) {
533 RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
535 speech_button_toggled_manually_ = false;
536 speech_result_obtained_ = false;
537 state_ = new_state;
538 FOR_EACH_OBSERVER(StartPageObserver,
539 observers_,
540 OnSpeechRecognitionStateChanged(new_state));
543 void StartPageService::GetSpeechAuthParameters(std::string* auth_scope,
544 std::string* auth_token) {
545 HotwordService* service = HotwordServiceFactory::GetForProfile(profile_);
546 if (service &&
547 service->IsOptedIntoAudioLogging() &&
548 service->IsAlwaysOnEnabled() &&
549 !speech_auth_helper_->GetToken().empty()) {
550 *auth_scope = speech_auth_helper_->GetScope();
551 *auth_token = speech_auth_helper_->GetToken();
555 void StartPageService::Shutdown() {
556 UnloadContents();
557 #if defined(OS_CHROMEOS)
558 audio_status_.reset();
559 #endif
561 speech_auth_helper_.reset();
562 network_change_observer_.reset();
565 void StartPageService::DidNavigateMainFrame(
566 const content::LoadCommittedDetails& /*details*/,
567 const content::FrameNavigateParams& /*params*/) {
568 // Set the zoom level in DidNavigateMainFrame, as this is the earliest point
569 // at which it can be done and not be affected by the ZoomController's
570 // DidNavigateMainFrame handler.
572 // Use a temporary zoom level for this web contents (aka isolated zoom
573 // mode) so changes to its zoom aren't reflected in any preferences.
574 ui_zoom::ZoomController::FromWebContents(contents_.get())
575 ->SetZoomMode(ui_zoom::ZoomController::ZOOM_MODE_ISOLATED);
576 // Set to have a zoom level of 0, which corresponds to 100%, so the
577 // contents aren't affected by the browser's default zoom level.
578 ui_zoom::ZoomController::FromWebContents(contents_.get())->SetZoomLevel(0);
581 void StartPageService::WebUILoaded() {
582 // There's a race condition between the WebUI loading, and calling its JS
583 // functions. Specifically, calling LoadContents() doesn't mean that the page
584 // has loaded, but several code paths make this assumption. This function
585 // allows us to defer calling JS functions until after the page has finished
586 // loading.
587 webui_finished_loading_ = true;
588 for (const auto& cb : pending_webui_callbacks_)
589 cb.Run();
590 pending_webui_callbacks_.clear();
592 FetchDoodleJson();
595 void StartPageService::LoadContents() {
596 contents_.reset(content::WebContents::Create(
597 content::WebContents::CreateParams(profile_)));
598 contents_delegate_.reset(new StartPageWebContentsDelegate(profile_));
599 contents_->SetDelegate(contents_delegate_.get());
601 // The ZoomController needs to be created before the web contents is observed
602 // by this object. Otherwise it will react to DidNavigateMainFrame after this
603 // object does, resetting the zoom mode in the process.
604 ui_zoom::ZoomController::CreateForWebContents(contents_.get());
605 Observe(contents_.get());
607 LoadStartPageURL();
610 void StartPageService::UnloadContents() {
611 contents_.reset();
612 webui_finished_loading_ = false;
615 void StartPageService::LoadStartPageURL() {
616 contents_->GetController().LoadURL(
617 GURL(chrome::kChromeUIAppListStartPageURL),
618 content::Referrer(),
619 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
620 std::string());
622 contents_->GetRenderViewHost()->GetView()->SetBackgroundColor(
623 SK_ColorTRANSPARENT);
626 void StartPageService::FetchDoodleJson() {
627 if (!search_engine_is_google_)
628 return;
630 GURL::Replacements replacements;
631 replacements.SetPathStr(kDoodleJsonPath);
633 GURL google_base_url(UIThreadSearchTermsData(profile_).GoogleBaseURLValue());
634 GURL doodle_url = google_base_url.ReplaceComponents(replacements);
635 doodle_fetcher_ =
636 net::URLFetcher::Create(0, doodle_url, net::URLFetcher::GET, this);
637 doodle_fetcher_->SetRequestContext(profile_->GetRequestContext());
638 doodle_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
639 doodle_fetcher_->Start();
642 void StartPageService::OnURLFetchComplete(const net::URLFetcher* source) {
643 std::string json_data;
644 source->GetResponseAsString(&json_data);
646 // Remove XSSI guard for JSON parsing.
647 size_t json_start_index = json_data.find("{");
648 base::StringPiece json_data_substr(json_data);
649 if (json_start_index != std::string::npos)
650 json_data_substr.remove_prefix(json_start_index);
652 JSONStringValueDeserializer deserializer(json_data_substr);
653 deserializer.set_allow_trailing_comma(true);
654 int error_code = 0;
655 scoped_ptr<base::Value> doodle_json(
656 deserializer.Deserialize(&error_code, nullptr));
658 base::TimeDelta recheck_delay;
659 if (error_code != 0) {
660 // On failure, use expotential backoff.
661 backoff_entry_.InformOfRequest(false);
662 recheck_delay = backoff_entry_.GetTimeUntilRelease();
663 } else {
664 // If we received information, even if there's no doodle, reset the backoff
665 // entry and start rechecking for the doodle at the maximum interval.
666 backoff_entry_.Reset();
667 recheck_delay = base::TimeDelta::FromMilliseconds(kMaximumRecheckDelayMs);
669 if (contents_ && contents_->GetWebUI()) {
670 contents_->GetWebUI()->CallJavascriptFunction(
671 "appList.startPage.onAppListDoodleUpdated", *doodle_json,
672 base::StringValue(
673 UIThreadSearchTermsData(profile_).GoogleBaseURLValue()));
677 // Check for a new doodle.
678 content::BrowserThread::PostDelayedTask(
679 content::BrowserThread::UI, FROM_HERE,
680 base::Bind(&StartPageService::FetchDoodleJson,
681 weak_factory_.GetWeakPtr()),
682 recheck_delay);
685 } // namespace app_list