diff --git a/widget/android/PrefsHelper.h b/widget/android/PrefsHelper.h index fa7a31cffb0..afe47ffb695 100644 --- a/widget/android/PrefsHelper.h +++ b/widget/android/PrefsHelper.h @@ -9,70 +9,308 @@ #include "GeneratedJNINatives.h" #include "MainThreadUtils.h" #include "nsAppShell.h" +#include "nsCOMPtr.h" +#include "nsVariant.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" #include "mozilla/UniquePtr.h" namespace mozilla { -struct PrefsHelper - : widget::PrefsHelper::Natives - , UsesGeckoThreadProxy +class PrefsHelper + : public widget::PrefsHelper::Natives + , public UsesGeckoThreadProxy { PrefsHelper() = delete; - static void GetPrefsById(const jni::ClassObject::LocalRef& cls, - int32_t requestId, - jni::ObjectArray::Param prefNames, - bool observe) + static bool GetVariantPref(nsIObserverService* aObsServ, + nsIWritableVariant* aVariant, + jni::Object::Param aPrefHandler, + jni::String::LocalRef& aPrefName) { - MOZ_ASSERT(NS_IsMainThread()); - - nsAppShell* const appShell = nsAppShell::Get(); - MOZ_ASSERT(appShell); - - nsTArray namesRefArray(prefNames.GetElements()); - const size_t len = namesRefArray.Length(); - - // Java strings are not null-terminated, but XPCOM expects - // null-terminated strings, so we copy the strings to nsString to - // ensure null-termination. - nsTArray namesStrArray(len); - nsTArray namesPtrArray(len); - - for (size_t i = 0; i < len; i++) { - if (!namesRefArray[i]) { - namesPtrArray.AppendElement(nullptr); - continue; - } - namesPtrArray.AppendElement(namesStrArray.AppendElement(nsString( - jni::String::Ref::From(namesRefArray[i])))->Data()); + if (NS_FAILED(aObsServ->NotifyObservers(aVariant, "android-get-pref", + nsString(aPrefName).get()))) { + return false; } - nsIAndroidBrowserApp* browserApp = nullptr; - appShell->GetBrowserApp(&browserApp); - MOZ_ASSERT(browserApp); + uint16_t varType = nsIDataType::VTYPE_EMPTY; + if (NS_FAILED(aVariant->GetDataType(&varType))) { + return false; + } - if (observe) { - browserApp->ObservePreferences( - requestId, len ? &namesPtrArray[0] : nullptr, len); - } else { - browserApp->GetPreferences( - requestId, len ? &namesPtrArray[0] : nullptr, len); + int32_t type = widget::PrefsHelper::PREF_INVALID; + bool boolVal = false; + int32_t intVal = 0; + nsAutoString strVal; + + switch (varType) { + case nsIDataType::VTYPE_BOOL: + type = widget::PrefsHelper::PREF_BOOL; + if (NS_FAILED(aVariant->GetAsBool(&boolVal))) { + return false; + } + break; + case nsIDataType::VTYPE_INT32: + type = widget::PrefsHelper::PREF_INT; + if (NS_FAILED(aVariant->GetAsInt32(&intVal))) { + return false; + } + break; + case nsIDataType::VTYPE_ASTRING: + type = widget::PrefsHelper::PREF_STRING; + if (NS_FAILED(aVariant->GetAsAString(strVal))) { + return false; + } + break; + default: + return false; + } + + const auto& jstrVal = type == widget::PrefsHelper::PREF_STRING ? + jni::Param(strVal, aPrefName.Env()) : + jni::Param(nullptr); + + widget::PrefsHelper::CallPrefHandler( + aPrefHandler, type, aPrefName, boolVal, intVal, jstrVal); + return true; + } + + static bool SetVariantPref(nsIObserverService* aObsServ, + nsIWritableVariant* aVariant, + jni::String::Param aPrefName, + bool aFlush, + int32_t aType, + bool aBoolVal, + int32_t aIntVal, + jni::String::Param aStrVal) + { + nsresult rv = NS_ERROR_FAILURE; + + switch (aType) { + case widget::PrefsHelper::PREF_BOOL: + rv = aVariant->SetAsBool(aBoolVal); + break; + case widget::PrefsHelper::PREF_INT: + rv = aVariant->SetAsInt32(aIntVal); + break; + case widget::PrefsHelper::PREF_STRING: + rv = aVariant->SetAsAString(nsString(aStrVal)); + break; + } + + if (NS_SUCCEEDED(rv)) { + rv = aObsServ->NotifyObservers(aVariant, "android-set-pref", + nsString(aPrefName).get()); + } + + uint16_t varType = nsIDataType::VTYPE_EMPTY; + if (NS_SUCCEEDED(rv)) { + rv = aVariant->GetDataType(&varType); + } + + // We use set-to-empty to signal the pref was handled. + const bool handled = varType == nsIDataType::VTYPE_EMPTY; + + if (NS_SUCCEEDED(rv) && handled && aFlush) { + rv = Preferences::GetService()->SavePrefFile(nullptr); + } + + if (NS_SUCCEEDED(rv)) { + return handled; + } + + NS_WARNING(nsPrintfCString("Failed to set pref %s", + nsCString(aPrefName).get()).get()); + // Pretend we handled the pref. + return true; + } + +public: + static void GetPrefs(const jni::ClassObject::LocalRef& aCls, + jni::ObjectArray::Param aPrefNames, + jni::Object::Param aPrefHandler) + { + nsTArray nameRefArray(aPrefNames.GetElements()); + nsCOMPtr obsServ; + nsCOMPtr value; + nsAdoptingString strVal; + + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(mozilla::Move(nameRef)); + const nsCString& name = nsCString(nameStr); + + int32_t type = widget::PrefsHelper::PREF_INVALID; + bool boolVal = false; + int32_t intVal = 0; + + switch (Preferences::GetType(name.get())) { + case nsIPrefBranch::PREF_BOOL: + type = widget::PrefsHelper::PREF_BOOL; + boolVal = Preferences::GetBool(name.get()); + break; + + case nsIPrefBranch::PREF_INT: + type = widget::PrefsHelper::PREF_INT; + intVal = Preferences::GetInt(name.get()); + break; + + case nsIPrefBranch::PREF_STRING: + type = widget::PrefsHelper::PREF_STRING; + strVal = Preferences::GetLocalizedString(name.get()); + if (!strVal) { + strVal = Preferences::GetString(name.get()); + } + break; + + default: + // Pref not found; try to find it. + if (!obsServ) { + obsServ = services::GetObserverService(); + if (!obsServ) { + continue; + } + } + if (value) { + value->SetAsEmpty(); + } else { + value = new nsVariant(); + } + if (!GetVariantPref(obsServ, value, + aPrefHandler, nameStr)) { + NS_WARNING(nsPrintfCString("Failed to get pref %s", + name.get()).get()); + } + continue; + } + + const auto& jstrVal = type == widget::PrefsHelper::PREF_STRING ? + jni::Param(strVal, aCls.Env()) : + jni::Param(nullptr); + + widget::PrefsHelper::CallPrefHandler( + aPrefHandler, type, nameStr, boolVal, intVal, jstrVal); + } + + widget::PrefsHelper::CallPrefHandler( + aPrefHandler, widget::PrefsHelper::PREF_FINISH, + nullptr, false, 0, nullptr); + } + + static void SetPref(jni::String::Param aPrefName, + bool aFlush, + int32_t aType, + bool aBoolVal, + int32_t aIntVal, + jni::String::Param aStrVal) + { + const nsCString& name = nsCString(aPrefName); + + if (Preferences::GetType(name.get()) == nsIPrefBranch::PREF_INVALID) { + // No pref; try asking first. + nsCOMPtr obsServ = + services::GetObserverService(); + nsCOMPtr value = new nsVariant(); + if (obsServ && SetVariantPref(obsServ, value, aPrefName, aFlush, + aType, aBoolVal, aIntVal, aStrVal)) { + return; + } + } + + switch (aType) { + case widget::PrefsHelper::PREF_BOOL: + Preferences::SetBool(name.get(), aBoolVal); + break; + case widget::PrefsHelper::PREF_INT: + Preferences::SetInt(name.get(), aIntVal); + break; + case widget::PrefsHelper::PREF_STRING: + Preferences::SetString(name.get(), nsString(aStrVal)); + break; + default: + MOZ_ASSERT(false, "Invalid pref type"); + } + + if (aFlush) { + Preferences::GetService()->SavePrefFile(nullptr); } } - static void RemovePrefsObserver(int32_t requestId) + static void AddObserver(const jni::ClassObject::LocalRef& aCls, + jni::ObjectArray::Param aPrefNames, + jni::Object::Param aPrefHandler, + jni::ObjectArray::Param aPrefsToObserve) { - MOZ_ASSERT(NS_IsMainThread()); + // Call observer immediately with existing pref values. + GetPrefs(aCls, aPrefNames, aPrefHandler); + if (!aPrefsToObserve) { + return; + } + + nsTArray nameRefArray( + aPrefsToObserve.GetElements()); nsAppShell* const appShell = nsAppShell::Get(); MOZ_ASSERT(appShell); - nsIAndroidBrowserApp* browserApp = nullptr; - appShell->GetBrowserApp(&browserApp); - MOZ_ASSERT(browserApp); + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(mozilla::Move(nameRef)); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(Preferences::AddStrongObserver( + appShell, nsCString(nameStr).get()))); + } + } - browserApp->RemovePreferenceObservers(requestId); + static void RemoveObserver(const jni::ClassObject::LocalRef& aCls, + jni::ObjectArray::Param aPrefsToUnobserve) + { + nsTArray nameRefArray( + aPrefsToUnobserve.GetElements()); + nsAppShell* const appShell = nsAppShell::Get(); + MOZ_ASSERT(appShell); + + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(mozilla::Move(nameRef)); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(Preferences::RemoveObserver( + appShell, nsCString(nameStr).get()))); + } + } + + static void OnPrefChange(const char16_t* aData) + { + const nsCString& name = NS_LossyConvertUTF16toASCII(aData); + + int32_t type = -1; + bool boolVal = false; + int32_t intVal = false; + nsAdoptingString strVal; + + switch (Preferences::GetType(name.get())) { + case nsIPrefBranch::PREF_BOOL: + type = widget::PrefsHelper::PREF_BOOL; + boolVal = Preferences::GetBool(name.get()); + break; + case nsIPrefBranch::PREF_INT: + type = widget::PrefsHelper::PREF_INT; + intVal = Preferences::GetInt(name.get()); + break; + case nsIPrefBranch::PREF_STRING: + type = widget::PrefsHelper::PREF_STRING; + strVal = Preferences::GetLocalizedString(name.get()); + if (!strVal) { + strVal = Preferences::GetString(name.get()); + } + break; + default: + NS_WARNING(nsPrintfCString("Invalid pref %s", + name.get()).get()); + return; + } + + const auto& jstrVal = type == widget::PrefsHelper::PREF_STRING ? + jni::Param(strVal) : + jni::Param(nullptr); + + widget::PrefsHelper::OnPrefChange(name, type, boolVal, intVal, jstrVal); } }; diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h index 52469c4f8ce..a954016641b 100644 --- a/widget/android/jni/Refs.h +++ b/widget/android/jni/Refs.h @@ -776,6 +776,11 @@ private: } public: + MOZ_IMPLICIT Type(decltype(nullptr)) + : Ref(nullptr) + , mEnv(nullptr) + {} + MOZ_IMPLICIT Type(const String::Ref& ref) : Ref(ref.Get()) , mEnv(nullptr) diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp index 4e085a87d6e..609bfed714d 100644 --- a/widget/android/nsAppShell.cpp +++ b/widget/android/nsAppShell.cpp @@ -341,6 +341,11 @@ nsAppShell::Observe(nsISupports* aSubject, } } removeObserver = true; + + } else if (!strcmp(aTopic, "nsPref:changed")) { + if (jni::IsAvailable()) { + mozilla::PrefsHelper::OnPrefChange(aData); + } } if (removeObserver) {