Various changes to make GCF nicer for the non-en-US world:

- Chrome Frame now uses MUI on Vista+ to detect IE's UX language.
- Chrome Frame now tells Chrome (via the --lang command-line option) what the current langauge is.
- Chrome Frame now respects Chrome's "ApplicationLocaleValue" group policy setting.

BUG=56435,59582
TEST=chrome_frame_unittests and chrome_frame_tests updated

Review URL: http://codereview.chromium.org/3757007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@63208 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome_frame/simple_resource_loader.cc b/chrome_frame/simple_resource_loader.cc
index e39087c..844afa7 100644
--- a/chrome_frame/simple_resource_loader.cc
+++ b/chrome_frame/simple_resource_loader.cc
@@ -4,40 +4,174 @@
 
 #include "chrome_frame/simple_resource_loader.h"
 
+#include <algorithm>
+
 #include <atlbase.h>
-#include <string>
 
 #include "base/base_paths.h"
 #include "base/file_path.h"
 #include "base/file_util.h"
 #include "base/path_service.h"
+#include "base/i18n/file_util_icu.h"
 #include "base/i18n/rtl.h"
+#include "base/singleton.h"
 #include "base/string_util.h"
 #include "base/utf_string_conversions.h"
 #include "base/win/windows_version.h"
 
+#include "chrome_frame/policy_settings.h"
+
+namespace {
+
 const wchar_t kLocalesDirName[] = L"Locales";
 
-HINSTANCE SimpleResourceLoader::locale_dll_handle_;
+bool IsInvalidTagCharacter(wchar_t tag_character) {
+  return !(L'-' == tag_character ||
+           IsAsciiDigit(tag_character) ||
+           IsAsciiAlpha(tag_character));
+}
 
-SimpleResourceLoader::SimpleResourceLoader() {
+// A helper function object that performs a lower-case ASCII comparison between
+// two strings.
+class CompareInsensitiveASCII
+    : public std::unary_function<const std::wstring&, bool> {
+ public:
+  explicit CompareInsensitiveASCII(const std::wstring& value)
+      : value_lowered_(WideToASCII(value)) {
+    StringToLowerASCII(&value_lowered_);
+  }
+  bool operator()(const std::wstring& comparand) {
+    return LowerCaseEqualsASCII(comparand, value_lowered_.c_str());
+  }
+
+ private:
+  std::string value_lowered_;
+};
+
+// Returns true if the value was added.
+bool PushBackIfAbsent(
+    const std::wstring& value,
+    std::vector<std::wstring>* collection) {
+  if (collection->end() ==
+      std::find_if(collection->begin(), collection->end(),
+                   CompareInsensitiveASCII(value))) {
+    collection->push_back(value);
+    return true;
+  }
+  return false;
+}
+
+}  // namespace
+
+SimpleResourceLoader::SimpleResourceLoader()
+    : locale_dll_handle_(NULL) {
   // Find and load the resource DLL.
-  std::wstring language;
-  std::wstring region;
-  GetSystemLocale(&language, &region);
+  std::vector<std::wstring> language_tags;
+
+  // First, try the locale dictated by policy and its fallback.
+  std::wstring application_locale =
+      Singleton<PolicySettings>()->ApplicationLocale();
+  if (!application_locale.empty()) {
+    language_tags.push_back(application_locale);
+    std::wstring::size_type dash = application_locale.find(L'-');
+    if (std::wstring::npos != dash) {
+      if (0 != dash) {
+        language_tags.push_back(application_locale.substr(0, dash));
+      } else {
+        NOTREACHED() << "Group Policy application locale begins with a dash.";
+      }
+    }
+  }
+
+  // Next, try the thread, process, user, system languages.
+  GetPreferredLanguages(&language_tags);
+
+  // Finally, fall-back on "en-US" (which may already be present in the vector,
+  // but that's okay since we'll exit with success when the first is tried).
+  language_tags.push_back(L"en-US");
+
+  FilePath locales_path;
   FilePath locale_dll_path;
 
-  if (GetLocaleFilePath(language, region, &locale_dll_path)) {
-    DCHECK(locale_dll_handle_ == NULL) << "Locale DLL is already loaded!";
-    locale_dll_handle_ = LoadLocaleDll(locale_dll_path);
-    DCHECK(locale_dll_handle_ != NULL) << "Failed to load locale dll!";
+  DetermineLocalesDirectory(&locales_path);
+  if (LoadLocaleDll(language_tags, locales_path, &locale_dll_handle_,
+                    &locale_dll_path)) {
+    language_ = locale_dll_path.BaseName().RemoveExtension().value();
+  } else {
+    NOTREACHED() << "Failed loading any resource dll (even \"en-US\").";
   }
 }
 
-SimpleResourceLoader::~SimpleResourceLoader() {}
+SimpleResourceLoader::~SimpleResourceLoader() {
+  locale_dll_handle_ = NULL;
+}
 
-void SimpleResourceLoader::GetSystemLocale(std::wstring* language,
-                                           std::wstring* region) {
+// static
+void SimpleResourceLoader::GetPreferredLanguages(
+    std::vector<std::wstring>* language_tags) {
+  // The full set of preferred languages and their fallbacks are given priority.
+  GetThreadPreferredUILanguages(language_tags);
+
+  // The above gives us nothing pre-Vista, so use ICU to get the system
+  // language and add it and its fallback to the end of the list if not present.
+  std::wstring language;
+  std::wstring region;
+
+  GetICUSystemLanguage(&language, &region);
+  if (!region.empty()) {
+    std::wstring combined;
+    combined.reserve(language.size() + 1 + region.size());
+    combined.assign(language).append(L"-").append(region);
+    PushBackIfAbsent(combined, language_tags);
+  }
+  PushBackIfAbsent(language, language_tags);
+}
+
+// static
+bool SimpleResourceLoader::GetThreadPreferredUILanguages(
+    std::vector<std::wstring>* language_tags) {
+  typedef BOOL (WINAPI* GetThreadPreferredUILanguages_Fn)(
+      DWORD, PULONG, PZZWSTR, PULONG);
+  HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
+  DCHECK(kernel32) << "Failed finding kernel32.dll!";
+  GetThreadPreferredUILanguages_Fn get_thread_preferred_ui_languages =
+      reinterpret_cast<GetThreadPreferredUILanguages_Fn>(
+          GetProcAddress(kernel32, "GetThreadPreferredUILanguages"));
+  bool have_mui = (NULL != get_thread_preferred_ui_languages);
+  if (have_mui) {
+    const DWORD kNameAndFallbackFlags =
+        MUI_LANGUAGE_NAME | MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK;
+    ULONG language_count = 0;
+    ULONG buffer_length = 0;
+
+    if (get_thread_preferred_ui_languages(
+            kNameAndFallbackFlags,
+            &language_count,
+            NULL,
+            &buffer_length) && 0 != buffer_length) {
+      std::vector<wchar_t> language_names(buffer_length);
+
+      if (get_thread_preferred_ui_languages(
+              kNameAndFallbackFlags,
+              &language_count,
+              &language_names[0],
+              &buffer_length)) {
+        std::vector<wchar_t>::const_iterator scan = language_names.begin();
+        std::wstring language(&*scan);
+        while (!language.empty()) {
+          language_tags->push_back(language);
+          scan += language.size() + 1;
+          language.assign(&*scan);
+        }
+      }
+    }
+  }
+  return have_mui;
+}
+
+// static
+void SimpleResourceLoader::GetICUSystemLanguage(std::wstring* language,
+                                                std::wstring* region) {
   DCHECK(language);
   DCHECK(region);
 
@@ -45,89 +179,90 @@
   base::i18n::GetLanguageAndRegionFromOS(&icu_language, &icu_region);
   if (!icu_language.empty()) {
     *language = ASCIIToWide(icu_language);
+  } else {
+    language->clear();
   }
   if (!icu_region.empty()) {
     *region = ASCIIToWide(icu_region);
+  } else {
+    region->clear();
   }
 }
 
-bool SimpleResourceLoader::GetLocaleFilePath(const std::wstring& language,
-                                             const std::wstring& region,
-                                             FilePath* file_path) {
-  DCHECK(file_path);
+// static
+void SimpleResourceLoader::DetermineLocalesDirectory(FilePath* locales_path) {
+  DCHECK(locales_path);
 
   FilePath module_path;
   PathService::Get(base::DIR_MODULE, &module_path);
-  FilePath locales_path = module_path.Append(kLocalesDirName);
+  *locales_path = module_path.Append(kLocalesDirName);
 
   // We may be residing in the "locales" directory's parent, or we might be
   // in a sibling directory. Move up one and look for Locales again in the
   // latter case.
-  if (!file_util::DirectoryExists(locales_path)) {
-    locales_path = module_path.DirName();
-    locales_path = locales_path.Append(kLocalesDirName);
+  if (!file_util::DirectoryExists(*locales_path)) {
+    *locales_path = module_path.DirName();
+    *locales_path = locales_path->Append(kLocalesDirName);
   }
 
-  bool found_dll = false;
-  if (file_util::DirectoryExists(locales_path)) {
-    std::wstring dll_name(language);
-    FilePath look_path;
-
-    // First look for the [language]-[region].DLL.
-    if (!region.empty()) {
-      dll_name += L"-";
-      dll_name += region;
-      dll_name += L".dll";
-
-      look_path = locales_path.Append(dll_name);
-      if (file_util::PathExists(look_path)) {
-        *file_path = look_path;
-        found_dll = true;
-      }
-    }
-
-    // Next look for just [language].DLL.
-    if (!found_dll) {
-      dll_name = language;
-      dll_name += L".dll";
-      look_path = locales_path.Append(dll_name);
-      if (file_util::PathExists(look_path)) {
-        *file_path = look_path;
-        found_dll = true;
-      }
-    }
-
-    // Finally, try defaulting to en-US.dll.
-    if (!found_dll) {
-      look_path = locales_path.Append(L"en-US.dll");
-      if (file_util::PathExists(look_path)) {
-        *file_path = look_path;
-        found_dll = true;
-      }
-    }
-  } else {
-    NOTREACHED() << "Could not locate locales DLL directory.";
-  }
-
-  return found_dll;
+  // Don't make a second check to see if the dir is in the parent.  We'll notice
+  // and log that in LoadLocaleDll when we actually try loading DLLs.
 }
 
-HINSTANCE SimpleResourceLoader::LoadLocaleDll(const FilePath& dll_path) {
-  DWORD load_flags = 0;
-  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
-    load_flags = LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE |
-                 LOAD_LIBRARY_AS_IMAGE_RESOURCE;
-  } else {
-    load_flags = DONT_RESOLVE_DLL_REFERENCES;
-  }
+// static
+bool SimpleResourceLoader::IsValidLanguageTag(
+    const std::wstring& language_tag) {
+  // "[a-zA-Z]+(-[a-zA-Z0-9]+)*" is a simplification, but better than nothing.
+  // Rather than pick up the weight of a regex processor, just search for a
+  // character that isn't in the above set.  This will at least weed out
+  // attempts at "../../EvilBinary".
+  return language_tag.end() == std::find_if(language_tag.begin(),
+                                            language_tag.end(),
+                                            &IsInvalidTagCharacter);
+}
+
+// static
+bool SimpleResourceLoader::LoadLocaleDll(
+    const std::vector<std::wstring>& language_tags,
+    const FilePath& locales_path,
+    HMODULE* dll_handle,
+    FilePath* file_path) {
+  DCHECK(file_path);
 
   // The dll should only have resources, not executable code.
-  HINSTANCE locale_dll_handle = LoadLibraryEx(dll_path.value().c_str(), NULL,
-                                              load_flags);
-  DCHECK(locale_dll_handle != NULL) << "unable to load generated resources: "
-                                     << GetLastError();
+  const DWORD load_flags =
+      (base::win::GetVersion() >= base::win::VERSION_VISTA ?
+          LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE :
+          DONT_RESOLVE_DLL_REFERENCES);
+  const std::wstring dll_suffix(L".dll");
+  bool found_dll = false;
 
-  return locale_dll_handle;
+  for (std::vector<std::wstring>::const_iterator scan = language_tags.begin(),
+         end = language_tags.end();
+       scan != end;
+       ++scan) {
+    if (!IsValidLanguageTag(*scan)) {
+      LOG(WARNING) << "Invalid language tag supplied while locating resources:"
+                      " \"" << *scan << "\"";
+      continue;
+    }
+    FilePath look_path = locales_path.Append(*scan + dll_suffix);
+    HMODULE locale_dll_handle = LoadLibraryEx(look_path.value().c_str(), NULL,
+                                              load_flags);
+    if (NULL != locale_dll_handle) {
+      *dll_handle = locale_dll_handle;
+      *file_path = look_path;
+      found_dll = true;
+      break;
+    }
+    DPCHECK(ERROR_FILE_NOT_FOUND == GetLastError())
+        << "Unable to load generated resources from " << look_path.value();
+  }
+
+  DCHECK(found_dll || file_util::DirectoryExists(locales_path))
+      << "Could not locate locales DLL directory.";
+
+  return found_dll;
 }
 
 std::wstring SimpleResourceLoader::GetLocalizedResource(int message_id) {
@@ -154,11 +289,16 @@
 }
 
 // static
+std::wstring SimpleResourceLoader::GetLanguage() {
+  return SimpleResourceLoader::instance()->language_;
+}
+
+// static
 std::wstring SimpleResourceLoader::Get(int message_id) {
   SimpleResourceLoader* loader = SimpleResourceLoader::instance();
   return loader->GetLocalizedResource(message_id);
 }
 
-HINSTANCE SimpleResourceLoader::GetResourceModuleHandle() {
+HMODULE SimpleResourceLoader::GetResourceModuleHandle() {
   return locale_dll_handle_;
 }