• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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/translate/chrome_translate_client.h"
6 
7 #include <vector>
8 
9 #include "base/logging.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_split.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/infobars/infobar_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/translate/translate_accept_languages_factory.h"
16 #include "chrome/browser/translate/translate_service.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/browser_tabstrip.h"
20 #include "chrome/browser/ui/browser_window.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/browser/ui/translate/translate_bubble_factory.h"
23 #include "chrome/common/pref_names.h"
24 #include "components/infobars/core/infobar.h"
25 #include "components/translate/content/common/translate_messages.h"
26 #include "components/translate/core/browser/language_state.h"
27 #include "components/translate/core/browser/page_translated_details.h"
28 #include "components/translate/core/browser/translate_accept_languages.h"
29 #include "components/translate/core/browser/translate_download_manager.h"
30 #include "components/translate/core/browser/translate_infobar_delegate.h"
31 #include "components/translate/core/browser/translate_manager.h"
32 #include "components/translate/core/browser/translate_prefs.h"
33 #include "components/translate/core/common/language_detection_details.h"
34 #include "content/public/browser/navigation_details.h"
35 #include "content/public/browser/navigation_entry.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/render_view_host.h"
38 #include "content/public/browser/web_contents.h"
39 #include "grit/theme_resources.h"
40 #include "net/http/http_status_code.h"
41 #include "url/gurl.h"
42 
43 #if defined(CLD2_DYNAMIC_MODE)
44 #include "base/files/file.h"
45 #include "base/path_service.h"
46 #include "chrome/common/chrome_constants.h"
47 #include "chrome/common/chrome_paths.h"
48 #include "content/public/browser/browser_thread.h"
49 #include "content/public/browser/render_process_host.h"
50 #endif
51 
52 #if defined(CLD2_IS_COMPONENT)
53 #include "chrome/browser/component_updater/cld_component_installer.h"
54 #endif
55 
56 namespace {
57 
58 // The maximum number of attempts we'll do to see if the page has finshed
59 // loading before giving up the translation
60 const int kMaxTranslateLoadCheckAttempts = 20;
61 
62 }  // namespace
63 
64 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ChromeTranslateClient);
65 
66 #if defined(CLD2_DYNAMIC_MODE)
67 // Statics defined in the .h file:
68 base::File* ChromeTranslateClient::s_cached_file_ = NULL;
69 uint64 ChromeTranslateClient::s_cached_data_offset_ = 0;
70 uint64 ChromeTranslateClient::s_cached_data_length_ = 0;
71 base::LazyInstance<base::Lock> ChromeTranslateClient::s_file_lock_ =
72     LAZY_INSTANCE_INITIALIZER;
73 #endif
74 
ChromeTranslateClient(content::WebContents * web_contents)75 ChromeTranslateClient::ChromeTranslateClient(content::WebContents* web_contents)
76     : content::WebContentsObserver(web_contents),
77       max_reload_check_attempts_(kMaxTranslateLoadCheckAttempts),
78       translate_driver_(&web_contents->GetController()),
79       translate_manager_(new TranslateManager(this, prefs::kAcceptLanguages)),
80       weak_pointer_factory_(this) {
81 }
82 
~ChromeTranslateClient()83 ChromeTranslateClient::~ChromeTranslateClient() {
84 }
85 
GetLanguageState()86 LanguageState& ChromeTranslateClient::GetLanguageState() {
87   return translate_manager_->GetLanguageState();
88 }
89 
90 // static
CreateTranslatePrefs(PrefService * prefs)91 scoped_ptr<TranslatePrefs> ChromeTranslateClient::CreateTranslatePrefs(
92     PrefService* prefs) {
93 #if defined(OS_CHROMEOS)
94   const char* preferred_languages_prefs = prefs::kLanguagePreferredLanguages;
95 #else
96   const char* preferred_languages_prefs = NULL;
97 #endif
98   return scoped_ptr<TranslatePrefs>(new TranslatePrefs(
99       prefs, prefs::kAcceptLanguages, preferred_languages_prefs));
100 }
101 
102 // static
GetTranslateAcceptLanguages(content::BrowserContext * browser_context)103 TranslateAcceptLanguages* ChromeTranslateClient::GetTranslateAcceptLanguages(
104     content::BrowserContext* browser_context) {
105   return TranslateAcceptLanguagesFactory::GetForBrowserContext(browser_context);
106 }
107 
108 // static
GetManagerFromWebContents(content::WebContents * web_contents)109 TranslateManager* ChromeTranslateClient::GetManagerFromWebContents(
110     content::WebContents* web_contents) {
111   ChromeTranslateClient* chrome_translate_client =
112       FromWebContents(web_contents);
113   if (!chrome_translate_client)
114     return NULL;
115   return chrome_translate_client->GetTranslateManager();
116 }
117 
118 // static
GetTranslateLanguages(content::WebContents * web_contents,std::string * source,std::string * target)119 void ChromeTranslateClient::GetTranslateLanguages(
120     content::WebContents* web_contents,
121     std::string* source,
122     std::string* target) {
123   DCHECK(source != NULL);
124   DCHECK(target != NULL);
125 
126   ChromeTranslateClient* chrome_translate_client =
127       FromWebContents(web_contents);
128   if (!chrome_translate_client)
129     return;
130 
131   *source = TranslateDownloadManager::GetLanguageCode(
132       chrome_translate_client->GetLanguageState().original_language());
133 
134   Profile* profile =
135       Profile::FromBrowserContext(web_contents->GetBrowserContext());
136   Profile* original_profile = profile->GetOriginalProfile();
137   PrefService* prefs = original_profile->GetPrefs();
138   scoped_ptr<TranslatePrefs> translate_prefs = CreateTranslatePrefs(prefs);
139   if (!web_contents->GetBrowserContext()->IsOffTheRecord()) {
140     std::string auto_translate_language =
141         TranslateManager::GetAutoTargetLanguage(*source, translate_prefs.get());
142     if (!auto_translate_language.empty()) {
143       *target = auto_translate_language;
144       return;
145     }
146   }
147 
148   std::string accept_languages_str = prefs->GetString(prefs::kAcceptLanguages);
149   std::vector<std::string> accept_languages_list;
150   base::SplitString(accept_languages_str, ',', &accept_languages_list);
151   *target = TranslateManager::GetTargetLanguage(accept_languages_list);
152 }
153 
GetTranslateManager()154 TranslateManager* ChromeTranslateClient::GetTranslateManager() {
155   return translate_manager_.get();
156 }
157 
GetWebContents()158 content::WebContents* ChromeTranslateClient::GetWebContents() {
159   return web_contents();
160 }
161 
ShowTranslateUI(translate::TranslateStep step,const std::string source_language,const std::string target_language,TranslateErrors::Type error_type,bool triggered_from_menu)162 void ChromeTranslateClient::ShowTranslateUI(translate::TranslateStep step,
163                                             const std::string source_language,
164                                             const std::string target_language,
165                                             TranslateErrors::Type error_type,
166                                             bool triggered_from_menu) {
167   DCHECK(web_contents());
168   if (error_type != TranslateErrors::NONE)
169     step = translate::TRANSLATE_STEP_TRANSLATE_ERROR;
170 
171   if (TranslateService::IsTranslateBubbleEnabled()) {
172     // Bubble UI.
173     if (step == translate::TRANSLATE_STEP_BEFORE_TRANSLATE) {
174       // TODO(droger): Move this logic out of UI code.
175       GetLanguageState().SetTranslateEnabled(true);
176       if (!GetLanguageState().HasLanguageChanged())
177         return;
178     }
179     ShowBubble(step, error_type);
180     return;
181   }
182 
183   // Infobar UI.
184   TranslateInfoBarDelegate::Create(
185       step != translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
186       translate_manager_->GetWeakPtr(),
187       InfoBarService::FromWebContents(web_contents()),
188       web_contents()->GetBrowserContext()->IsOffTheRecord(),
189       step,
190       source_language,
191       target_language,
192       error_type,
193       triggered_from_menu);
194 }
195 
GetTranslateDriver()196 TranslateDriver* ChromeTranslateClient::GetTranslateDriver() {
197   return &translate_driver_;
198 }
199 
GetPrefs()200 PrefService* ChromeTranslateClient::GetPrefs() {
201   DCHECK(web_contents());
202   Profile* profile =
203       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
204   return profile->GetOriginalProfile()->GetPrefs();
205 }
206 
GetTranslatePrefs()207 scoped_ptr<TranslatePrefs> ChromeTranslateClient::GetTranslatePrefs() {
208   DCHECK(web_contents());
209   Profile* profile =
210       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
211   return CreateTranslatePrefs(profile->GetPrefs());
212 }
213 
GetTranslateAcceptLanguages()214 TranslateAcceptLanguages* ChromeTranslateClient::GetTranslateAcceptLanguages() {
215   DCHECK(web_contents());
216   return GetTranslateAcceptLanguages(web_contents()->GetBrowserContext());
217 }
218 
GetInfobarIconID() const219 int ChromeTranslateClient::GetInfobarIconID() const {
220   return IDR_INFOBAR_TRANSLATE;
221 }
222 
223 // ChromeTranslateClient::CreateInfoBar() is implemented in platform-specific
224 // files, except the TOOLKIT_VIEWS implementation, which has been removed.
225 #if defined(TOOLKIT_VIEWS)
CreateInfoBar(scoped_ptr<TranslateInfoBarDelegate> delegate) const226 scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
227     scoped_ptr<TranslateInfoBarDelegate> delegate) const {
228   return scoped_ptr<infobars::InfoBar>();
229 }
230 #endif
231 
IsTranslatableURL(const GURL & url)232 bool ChromeTranslateClient::IsTranslatableURL(const GURL& url) {
233   return TranslateService::IsTranslatableURL(url);
234 }
235 
ShowReportLanguageDetectionErrorUI(const GURL & report_url)236 void ChromeTranslateClient::ShowReportLanguageDetectionErrorUI(
237     const GURL& report_url) {
238 #if defined(OS_ANDROID)
239   // Android does not support reporting language detection errors.
240   NOTREACHED();
241 #else
242   // We'll open the URL in a new tab so that the user can tell us more.
243   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
244   if (!browser) {
245     NOTREACHED();
246     return;
247   }
248 
249   chrome::AddSelectedTabWithURL(
250       browser, report_url, content::PAGE_TRANSITION_AUTO_BOOKMARK);
251 #endif  // defined(OS_ANDROID)
252 }
253 
OnMessageReceived(const IPC::Message & message)254 bool ChromeTranslateClient::OnMessageReceived(const IPC::Message& message) {
255   bool handled = true;
256   IPC_BEGIN_MESSAGE_MAP(ChromeTranslateClient, message)
257   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_TranslateLanguageDetermined,
258                       OnLanguageDetermined)
259   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PageTranslated, OnPageTranslated)
260 #if defined(CLD2_DYNAMIC_MODE)
261   IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NeedCLDData, OnCLDDataRequested)
262 #endif
263   IPC_MESSAGE_UNHANDLED(handled = false)
264   IPC_END_MESSAGE_MAP()
265 
266   return handled;
267 }
268 
NavigationEntryCommitted(const content::LoadCommittedDetails & load_details)269 void ChromeTranslateClient::NavigationEntryCommitted(
270     const content::LoadCommittedDetails& load_details) {
271   // Check whether this is a reload: When doing a page reload, the
272   // TranslateLanguageDetermined IPC is not sent so the translation needs to be
273   // explicitly initiated.
274 
275   content::NavigationEntry* entry =
276       web_contents()->GetController().GetActiveEntry();
277   if (!entry) {
278     NOTREACHED();
279     return;
280   }
281 
282   // If the navigation happened while offline don't show the translate
283   // bar since there will be nothing to translate.
284   if (load_details.http_status_code == 0 ||
285       load_details.http_status_code == net::HTTP_INTERNAL_SERVER_ERROR) {
286     return;
287   }
288 
289   if (!load_details.is_main_frame &&
290       GetLanguageState().translation_declined()) {
291     // Some sites (such as Google map) may trigger sub-frame navigations
292     // when the user interacts with the page.  We don't want to show a new
293     // infobar if the user already dismissed one in that case.
294     return;
295   }
296 
297   // If not a reload, return.
298   if (entry->GetTransitionType() != content::PAGE_TRANSITION_RELOAD &&
299       load_details.type != content::NAVIGATION_TYPE_SAME_PAGE) {
300     return;
301   }
302 
303   if (!GetLanguageState().page_needs_translation())
304     return;
305 
306   // Note that we delay it as the ordering of the processing of this callback
307   // by WebContentsObservers is undefined and might result in the current
308   // infobars being removed. Since the translation initiation process might add
309   // an infobar, it must be done after that.
310   base::MessageLoop::current()->PostTask(
311       FROM_HERE,
312       base::Bind(&ChromeTranslateClient::InitiateTranslation,
313                  weak_pointer_factory_.GetWeakPtr(),
314                  GetLanguageState().original_language(),
315                  0));
316 }
317 
DidNavigateAnyFrame(const content::LoadCommittedDetails & details,const content::FrameNavigateParams & params)318 void ChromeTranslateClient::DidNavigateAnyFrame(
319     const content::LoadCommittedDetails& details,
320     const content::FrameNavigateParams& params) {
321   // Let the LanguageState clear its state.
322   const bool reload =
323       details.entry->GetTransitionType() == content::PAGE_TRANSITION_RELOAD ||
324       details.type == content::NAVIGATION_TYPE_SAME_PAGE;
325   GetLanguageState().DidNavigate(
326       details.is_in_page, details.is_main_frame, reload);
327 }
328 
WebContentsDestroyed()329 void ChromeTranslateClient::WebContentsDestroyed() {
330   // Translation process can be interrupted.
331   // Destroying the TranslateManager now guarantees that it never has to deal
332   // with NULL WebContents.
333   translate_manager_.reset();
334 }
335 
336 #if defined(CLD2_DYNAMIC_MODE)
OnCLDDataRequested()337 void ChromeTranslateClient::OnCLDDataRequested() {
338   // Quickly try to read s_cached_file_. If valid, the file handle is
339   // cached and can be used immediately. Else, queue the caching task to the
340   // blocking pool.
341   base::File* handle = NULL;
342   uint64 data_offset = 0;
343   uint64 data_length = 0;
344   {
345     base::AutoLock lock(s_file_lock_.Get());
346     handle = s_cached_file_;
347     data_offset = s_cached_data_offset_;
348     data_length = s_cached_data_length_;
349   }
350 
351   if (handle && handle->IsValid()) {
352     // Cached data available. Respond to the request.
353     SendCLDDataAvailable(handle, data_offset, data_length);
354     return;
355   }
356 
357   // Else, we don't have the data file yet. Queue a caching attempt.
358   // The caching attempt happens in the blocking pool because it may involve
359   // arbitrary filesystem access.
360   // After the caching attempt is made, we call MaybeSendCLDDataAvailable
361   // to pass the file handle to the renderer. This only results in an IPC
362   // message if the caching attempt was successful.
363   content::BrowserThread::PostBlockingPoolTaskAndReply(
364       FROM_HERE,
365       base::Bind(&ChromeTranslateClient::HandleCLDDataRequest),
366       base::Bind(&ChromeTranslateClient::MaybeSendCLDDataAvailable,
367                  weak_pointer_factory_.GetWeakPtr()));
368 }
369 
MaybeSendCLDDataAvailable()370 void ChromeTranslateClient::MaybeSendCLDDataAvailable() {
371   base::File* handle = NULL;
372   uint64 data_offset = 0;
373   uint64 data_length = 0;
374   {
375     base::AutoLock lock(s_file_lock_.Get());
376     handle = s_cached_file_;
377     data_offset = s_cached_data_offset_;
378     data_length = s_cached_data_length_;
379   }
380 
381   if (handle && handle->IsValid())
382     SendCLDDataAvailable(handle, data_offset, data_length);
383 }
384 
SendCLDDataAvailable(const base::File * handle,const uint64 data_offset,const uint64 data_length)385 void ChromeTranslateClient::SendCLDDataAvailable(const base::File* handle,
386                                                  const uint64 data_offset,
387                                                  const uint64 data_length) {
388   // Data available, respond to the request.
389   IPC::PlatformFileForTransit ipc_platform_file = IPC::GetFileHandleForProcess(
390       handle->GetPlatformFile(),
391       GetWebContents()->GetRenderViewHost()->GetProcess()->GetHandle(),
392       false);
393   // In general, sending a response from within the code path that is processing
394   // a request is discouraged because there is potential for deadlock (if the
395   // methods are sent synchronously) or loops (if the response can trigger a
396   // new request). Neither of these concerns is relevant in this code, so
397   // sending the response from within the code path of the request handler is
398   // safe.
399   Send(new ChromeViewMsg_CLDDataAvailable(
400       GetWebContents()->GetRenderViewHost()->GetRoutingID(),
401       ipc_platform_file,
402       data_offset,
403       data_length));
404 }
405 
HandleCLDDataRequest()406 void ChromeTranslateClient::HandleCLDDataRequest() {
407   // Because this function involves arbitrary file system access, it must run
408   // on the blocking pool.
409   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
410   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
411 
412   {
413     base::AutoLock lock(s_file_lock_.Get());
414     if (s_cached_file_)
415       return;  // Already done, duplicate request
416   }
417 
418 #if defined(CLD2_IS_COMPONENT)
419   base::FilePath path = component_updater::GetLatestCldDataFile();
420   if (path.empty())
421     return;
422 #else  // CLD2 data is at a well-known file path
423   base::FilePath path;
424   if (!PathService::Get(chrome::DIR_USER_DATA, &path)) {
425     LOG(WARNING) << "Unable to locate user data directory";
426     return;  // Chrome isn't properly installed.
427   }
428   path = path.Append(chrome::kCLDDataFilename);
429 #endif
430 
431   // If the file exists, we can send an IPC-safe construct back to the
432   // renderer process immediately; otherwise, nothing to do here.
433   if (!base::PathExists(path))
434     return;
435 
436   // Attempt to open the file for reading.
437   scoped_ptr<base::File> file(
438       new base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
439   if (!file->IsValid()) {
440     LOG(WARNING) << "CLD data file exists but cannot be opened";
441     return;
442   }
443 
444   base::File::Info file_info;
445   if (!file->GetInfo(&file_info)) {
446     LOG(WARNING) << "CLD data file exists but cannot be inspected";
447     return;
448   }
449 
450   // For now, our offset and length are simply 0 and the length of the file,
451   // respectively. If we later decide to include the CLD2 data file inside of
452   // a larger binary context, these params can be twiddled appropriately.
453   const uint64 data_offset = 0;
454   const uint64 data_length = file_info.size;
455 
456   {
457     base::AutoLock lock(s_file_lock_.Get());
458     if (s_cached_file_) {
459       // Idempotence: Racing another request on the blocking pool, abort.
460     } else {
461       // Else, this request has taken care of it all. Cache all info.
462       s_cached_file_ = file.release();
463       s_cached_data_offset_ = data_offset;
464       s_cached_data_length_ = data_length;
465     }
466   }
467 }
468 
469 #endif  // defined(CLD2_DYNAMIC_MODE)
470 
InitiateTranslation(const std::string & page_lang,int attempt)471 void ChromeTranslateClient::InitiateTranslation(const std::string& page_lang,
472                                                 int attempt) {
473   if (GetLanguageState().translation_pending())
474     return;
475 
476   // During a reload we need web content to be available before the
477   // translate script is executed. Otherwise we will run the translate script on
478   // an empty DOM which will fail. Therefore we wait a bit to see if the page
479   // has finished.
480   if (web_contents()->IsLoading() && attempt < max_reload_check_attempts_) {
481     int backoff = attempt * kMaxTranslateLoadCheckAttempts;
482     base::MessageLoop::current()->PostDelayedTask(
483         FROM_HERE,
484         base::Bind(&ChromeTranslateClient::InitiateTranslation,
485                    weak_pointer_factory_.GetWeakPtr(),
486                    page_lang,
487                    ++attempt),
488         base::TimeDelta::FromMilliseconds(backoff));
489     return;
490   }
491 
492   translate_manager_->InitiateTranslation(
493       TranslateDownloadManager::GetLanguageCode(page_lang));
494 }
495 
OnLanguageDetermined(const LanguageDetectionDetails & details,bool page_needs_translation)496 void ChromeTranslateClient::OnLanguageDetermined(
497     const LanguageDetectionDetails& details,
498     bool page_needs_translation) {
499   GetLanguageState().LanguageDetermined(details.adopted_language,
500                                         page_needs_translation);
501 
502   if (web_contents())
503     translate_manager_->InitiateTranslation(details.adopted_language);
504 
505   content::NotificationService::current()->Notify(
506       chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED,
507       content::Source<content::WebContents>(web_contents()),
508       content::Details<const LanguageDetectionDetails>(&details));
509 }
510 
OnPageTranslated(int32 page_id,const std::string & original_lang,const std::string & translated_lang,TranslateErrors::Type error_type)511 void ChromeTranslateClient::OnPageTranslated(int32 page_id,
512                                              const std::string& original_lang,
513                                              const std::string& translated_lang,
514                                              TranslateErrors::Type error_type) {
515   DCHECK(web_contents());
516   translate_manager_->PageTranslated(
517       original_lang, translated_lang, error_type);
518 
519   PageTranslatedDetails details;
520   details.source_language = original_lang;
521   details.target_language = translated_lang;
522   details.error_type = error_type;
523   content::NotificationService::current()->Notify(
524       chrome::NOTIFICATION_PAGE_TRANSLATED,
525       content::Source<content::WebContents>(web_contents()),
526       content::Details<PageTranslatedDetails>(&details));
527 }
528 
ShowBubble(translate::TranslateStep step,TranslateErrors::Type error_type)529 void ChromeTranslateClient::ShowBubble(translate::TranslateStep step,
530                                        TranslateErrors::Type error_type) {
531 // The bubble is implemented only on the desktop platforms.
532 #if !defined(OS_ANDROID) && !defined(OS_IOS)
533   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
534 
535   // |browser| might be NULL when testing. In this case, Show(...) should be
536   // called because the implementation for testing is used.
537   if (!browser) {
538     TranslateBubbleFactory::Show(NULL, web_contents(), step, error_type);
539     return;
540   }
541 
542   if (web_contents() != browser->tab_strip_model()->GetActiveWebContents())
543     return;
544 
545   // This ShowBubble function is also used for upating the existing bubble.
546   // However, with the bubble shown, any browser windows are NOT activated
547   // because the bubble takes the focus from the other widgets including the
548   // browser windows. So it is checked that |browser| is the last activated
549   // browser, not is now activated.
550   if (browser !=
551       chrome::FindLastActiveWithHostDesktopType(browser->host_desktop_type())) {
552     return;
553   }
554 
555   // During auto-translating, the bubble should not be shown.
556   if (step == translate::TRANSLATE_STEP_TRANSLATING ||
557       step == translate::TRANSLATE_STEP_AFTER_TRANSLATE) {
558     if (GetLanguageState().InTranslateNavigation())
559       return;
560   }
561 
562   TranslateBubbleFactory::Show(
563       browser->window(), web_contents(), step, error_type);
564 #else
565   NOTREACHED();
566 #endif
567 }
568