Skip to content

Commit

Permalink
Bug 889335 - Implement navigator.languages and languagechange event. …
Browse files Browse the repository at this point in the history
…r=sicking,smaug
  • Loading branch information
mounirlamouri committed May 12, 2014
1 parent 364ad99 commit bd7cae1
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 38 deletions.
1 change: 1 addition & 0 deletions content/base/src/nsGkAtomList.h
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ GK_ATOM(oninvalid, "oninvalid")
GK_ATOM(onkeydown, "onkeydown")
GK_ATOM(onkeypress, "onkeypress")
GK_ATOM(onkeyup, "onkeyup")
GK_ATOM(onlanguagechange, "onlanguagechange")
GK_ATOM(onlevelchange, "onlevelchange")
GK_ATOM(onLoad, "onLoad")
GK_ATOM(onload, "onload")
Expand Down
102 changes: 65 additions & 37 deletions dom/base/Navigator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,59 +351,87 @@ Navigator::GetAppName(nsAString& aAppName)
}

/**
* JS property navigator.language, exposed to web content.
* Take first value from Accept-Languages (HTTP header), which is
* the "content language" freely set by the user in the Pref window.
* Returns the value of Accept-Languages (HTTP header) as a nsTArray of
* languages. The value is set in the preference by the user ("Content
* Languages").
*
* Do not use UI language (chosen app locale) here.
* See RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers"
* "en", "en-US" and "i-cherokee" and "" are valid languages tokens.
*
* "en", "en-US" and "i-cherokee" and "" are valid.
* Fallback in case of invalid pref should be "" (empty string), to
* let site do fallback, e.g. to site's local language.
* An empty array will be returned if there is no valid languages.
*/
NS_IMETHODIMP
Navigator::GetLanguage(nsAString& aLanguage)
void
Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages)
{
// E.g. "de-de, en-us,en".
const nsAdoptingString& acceptLang =
Preferences::GetLocalizedString("intl.accept_languages");

// Take everything before the first "," or ";", without trailing space.
// Split values on commas.
nsCharSeparatedTokenizer langTokenizer(acceptLang, ',');
const nsSubstring &firstLangPart = langTokenizer.nextToken();
nsCharSeparatedTokenizer qTokenizer(firstLangPart, ';');
aLanguage.Assign(qTokenizer.nextToken());
while (langTokenizer.hasMoreTokens()) {
nsDependentSubstring lang = langTokenizer.nextToken();

// Checks and fixups:
// replace "_" with "-" to avoid POSIX/Windows "en_US" notation.
if (aLanguage.Length() > 2 && aLanguage[2] == char16_t('_')) {
aLanguage.Replace(2, 1, char16_t('-')); // TODO replace all
}

// Use uppercase for country part, e.g. "en-US", not "en-us", see BCP47
// only uppercase 2-letter country codes, not "zh-Hant", "de-DE-x-goethe".
if (aLanguage.Length() <= 2) {
return NS_OK;
}
// Replace "_" with "-" to avoid POSIX/Windows "en_US" notation.
// NOTE: we should probably rely on the pref being set correctly.
if (lang.Length() > 2 && lang[2] == char16_t('_')) {
lang.Replace(2, 1, char16_t('-'));
}

nsCharSeparatedTokenizer localeTokenizer(aLanguage, '-');
int32_t pos = 0;
bool first = true;
while (localeTokenizer.hasMoreTokens()) {
const nsSubstring& code = localeTokenizer.nextToken();
// Use uppercase for country part, e.g. "en-US", not "en-us", see BCP47
// only uppercase 2-letter country codes, not "zh-Hant", "de-DE-x-goethe".
// NOTE: we should probably rely on the pref being set correctly.
if (lang.Length() > 2) {
nsCharSeparatedTokenizer localeTokenizer(lang, '-');
int32_t pos = 0;
bool first = true;
while (localeTokenizer.hasMoreTokens()) {
const nsSubstring& code = localeTokenizer.nextToken();

if (code.Length() == 2 && !first) {
nsAutoString upper(code);
ToUpperCase(upper);
lang.Replace(pos, code.Length(), upper);
}

if (code.Length() == 2 && !first) {
nsAutoString upper(code);
ToUpperCase(upper);
aLanguage.Replace(pos, code.Length(), upper);
pos += code.Length() + 1; // 1 is the separator
first = false;
}
}

pos += code.Length() + 1; // 1 is the separator
first = false;
aLanguages.AppendElement(lang);
}
}

return NS_OK;
/**
* Do not use UI language (chosen app locale) here but the first value set in
* the Accept Languages header, see ::GetAcceptLanguages().
*
* See RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers" for
* the reasons why.
*/
NS_IMETHODIMP
Navigator::GetLanguage(nsAString& aLanguage)
{
nsTArray<nsString> languages;
GetLanguages(languages);
if (languages.Length() >= 1) {
aLanguage.Assign(languages[0]);
} else {
aLanguage.Truncate();
}

return NS_OK;
}

void
Navigator::GetLanguages(nsTArray<nsString>& aLanguages)
{
GetAcceptLanguages(aLanguages);

// The returned value is cached by the binding code. The window listen to the
// accept languages change and will clear the cache when needed. It has to
// take care of dispatching the DOM event already and the invalidation and the
// event has to be timed correctly.
}

NS_IMETHODIMP
Expand Down
2 changes: 2 additions & 0 deletions dom/base/Navigator.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ class Navigator : public nsIDOMNavigator
JS::MutableHandle<JSPropertyDescriptor> aDesc);
void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
ErrorResult& aRv);
void GetLanguages(nsTArray<nsString>& aLanguages);
void GetAcceptLanguages(nsTArray<nsString>& aLanguages);

// WebIDL helper methods
static bool HasBatterySupport(JSContext* /* unused*/, JSObject* /*unused */);
Expand Down
31 changes: 31 additions & 0 deletions dom/base/nsGlobalWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
#include "nsITabChild.h"
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/NavigatorBinding.h"
#ifdef HAVE_SIDEBAR
#include "mozilla/dom/ExternalBinding.h"
#endif
Expand Down Expand Up @@ -1137,6 +1138,8 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
// events. Use a strong reference.
os->AddObserver(mObserver, "dom-storage2-changed", false);
}

Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
}
} else {
// |this| is an outer window. Outer windows start out frozen and
Expand Down Expand Up @@ -1419,6 +1422,8 @@ nsGlobalWindow::CleanUp()
mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
}

Preferences::RemoveObserver(mObserver, "intl.accept_languages");

// Drop its reference to this dying window, in case for some bogus reason
// the object stays around.
mObserver->Forget();
Expand Down Expand Up @@ -11219,6 +11224,32 @@ nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic,
}
#endif // MOZ_B2G

if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
MOZ_ASSERT(!nsCRT::strcmp(NS_ConvertUTF16toUTF8(aData).get(), "intl.accept_languages"));
MOZ_ASSERT(IsInnerWindow());

// The user preferred languages have changed, we need to fire an event on
// Window object and invalidate the cache for navigator.languages. It is
// done for every change which can be a waste of cycles but those should be
// fairly rare.
// We MUST invalidate navigator.languages before sending the event in the
// very likely situation where an event handler will try to read its value.

if (mNavigator) {
NavigatorBinding::ClearCachedLanguagesValue(mNavigator);
}

nsCOMPtr<nsIDOMEvent> event;
NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
nsresult rv = event->InitEvent(NS_LITERAL_STRING("languagechange"), false, false);
NS_ENSURE_SUCCESS(rv, rv);

event->SetTrusted(true);

bool dummy;
return DispatchEvent(event, &dummy);
}

NS_WARNING("unrecognized topic in nsGlobalWindow::Observe");
return NS_ERROR_FAILURE;
}
Expand Down
1 change: 1 addition & 0 deletions dom/base/test/mochitest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ support-files =
[test_messageChannel_unshipped.html]
[test_named_frames.html]
[test_navigator_resolve_identity.html]
[test_navigator_language.html]
[test_nondomexception.html]
[test_openDialogChromeOnly.html]
[test_postMessage_solidus.html]
Expand Down
Loading

0 comments on commit bd7cae1

Please sign in to comment.