widget/android/PrefsHelper.h
author Doug Thayer <dothayer@mozilla.com>
Fri, 01 Mar 2019 18:29:09 +0000
changeset 520116 8b3fe0426ffc1b3a2ad044ef6cdde6c4f736f8e2
parent 505471 66eb1f485c1a3ea81372758bc92292c9428b17cd
permissions -rw-r--r--
Bug 1442694 - Fix failures due to removing selected tab r=Gijs This adds test which reproduce the failure as well as the fix. Essentially, if we hit the edited case in SessionStore with `tab` equal to `tabbrowser.tabs[t]`, we remove the tab and then try to pin it, which obviously blows up. Note: the additional method in SessionStore.jsm was largely to get around complexity requirements inside restoreWindow. Cleaner solutions welcome. Differential Revision: https://phabricator.services.mozilla.com/D21383

/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef PrefsHelper_h
#define PrefsHelper_h

#include "GeneratedJNINatives.h"
#include "MainThreadUtils.h"
#include "nsAppShell.h"
#include "nsCOMPtr.h"
#include "nsVariant.h"

#include "mozilla/Preferences.h"
#include "mozilla/Services.h"

namespace mozilla {

class PrefsHelper : public java::PrefsHelper::Natives<PrefsHelper> {
  PrefsHelper() = delete;

  static bool GetVariantPref(nsIObserverService* aObsServ,
                             nsIWritableVariant* aVariant,
                             jni::Object::Param aPrefHandler,
                             const jni::String::LocalRef& aPrefName) {
    if (NS_FAILED(aObsServ->NotifyObservers(aVariant, "android-get-pref",
                                            aPrefName->ToString().get()))) {
      return false;
    }

    uint16_t varType = aVariant->GetDataType();

    int32_t type = java::PrefsHelper::PREF_INVALID;
    bool boolVal = false;
    int32_t intVal = 0;
    nsAutoString strVal;

    switch (varType) {
      case nsIDataType::VTYPE_BOOL:
        type = java::PrefsHelper::PREF_BOOL;
        if (NS_FAILED(aVariant->GetAsBool(&boolVal))) {
          return false;
        }
        break;
      case nsIDataType::VTYPE_INT32:
        type = java::PrefsHelper::PREF_INT;
        if (NS_FAILED(aVariant->GetAsInt32(&intVal))) {
          return false;
        }
        break;
      case nsIDataType::VTYPE_ASTRING:
        type = java::PrefsHelper::PREF_STRING;
        if (NS_FAILED(aVariant->GetAsAString(strVal))) {
          return false;
        }
        break;
      default:
        return false;
    }

    jni::StringParam jstrVal(type == java::PrefsHelper::PREF_STRING
                                 ? jni::StringParam(strVal, aPrefName.Env())
                                 : jni::StringParam(nullptr));

    if (aPrefHandler) {
      java::PrefsHelper::CallPrefHandler(aPrefHandler, type, aPrefName, boolVal,
                                         intVal, jstrVal);
    } else {
      java::PrefsHelper::OnPrefChange(aPrefName, type, 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 java::PrefsHelper::PREF_BOOL:
        rv = aVariant->SetAsBool(aBoolVal);
        break;
      case java::PrefsHelper::PREF_INT:
        rv = aVariant->SetAsInt32(aIntVal);
        break;
      case java::PrefsHelper::PREF_STRING:
        rv = aVariant->SetAsAString(aStrVal->ToString());
        break;
    }

    if (NS_SUCCEEDED(rv)) {
      rv = aObsServ->NotifyObservers(aVariant, "android-set-pref",
                                     aPrefName->ToString().get());
    }

    uint16_t varType = nsIDataType::VTYPE_EMPTY;
    if (NS_SUCCEEDED(rv)) {
      varType = aVariant->GetDataType();
    }

    // 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", aPrefName->ToCString().get())
            .get());
    // Pretend we handled the pref.
    return true;
  }

 public:
  static void GetPrefs(const jni::Class::LocalRef& aCls,
                       jni::ObjectArray::Param aPrefNames,
                       jni::Object::Param aPrefHandler) {
    nsTArray<jni::Object::LocalRef> nameRefArray(aPrefNames->GetElements());
    nsCOMPtr<nsIObserverService> obsServ;
    nsCOMPtr<nsIWritableVariant> value;
    nsAutoString strVal;

    for (jni::Object::LocalRef& nameRef : nameRefArray) {
      jni::String::LocalRef nameStr(std::move(nameRef));
      const nsCString& name = nameStr->ToCString();

      int32_t type = java::PrefsHelper::PREF_INVALID;
      bool boolVal = false;
      int32_t intVal = 0;
      strVal.Truncate();

      switch (Preferences::GetType(name.get())) {
        case nsIPrefBranch::PREF_BOOL:
          type = java::PrefsHelper::PREF_BOOL;
          boolVal = Preferences::GetBool(name.get());
          break;

        case nsIPrefBranch::PREF_INT:
          type = java::PrefsHelper::PREF_INT;
          intVal = Preferences::GetInt(name.get());
          break;

        case nsIPrefBranch::PREF_STRING: {
          type = java::PrefsHelper::PREF_STRING;
          nsresult rv = Preferences::GetLocalizedString(name.get(), strVal);
          if (NS_FAILED(rv)) {
            Preferences::GetString(name.get(), strVal);
          }
          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;
      }

      java::PrefsHelper::CallPrefHandler(
          aPrefHandler, type, nameStr, boolVal, intVal,
          jni::StringParam(type == java::PrefsHelper::PREF_STRING
                               ? jni::StringParam(strVal, aCls.Env())
                               : jni::StringParam(nullptr)));
    }

    java::PrefsHelper::CallPrefHandler(aPrefHandler,
                                       java::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 = aPrefName->ToCString();

    if (Preferences::GetType(name.get()) == nsIPrefBranch::PREF_INVALID) {
      // No pref; try asking first.
      nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService();
      nsCOMPtr<nsIWritableVariant> value = new nsVariant();
      if (obsServ && SetVariantPref(obsServ, value, aPrefName, aFlush, aType,
                                    aBoolVal, aIntVal, aStrVal)) {
        // The "pref" has changed; send a notification.
        GetVariantPref(obsServ, value, nullptr,
                       jni::String::LocalRef(aPrefName));
        return;
      }
    }

    switch (aType) {
      case java::PrefsHelper::PREF_BOOL:
        Preferences::SetBool(name.get(), aBoolVal);
        break;
      case java::PrefsHelper::PREF_INT:
        Preferences::SetInt(name.get(), aIntVal);
        break;
      case java::PrefsHelper::PREF_STRING:
        Preferences::SetString(name.get(), aStrVal->ToString());
        break;
      default:
        MOZ_ASSERT(false, "Invalid pref type");
    }

    if (aFlush) {
      Preferences::GetService()->SavePrefFile(nullptr);
    }
  }

  static void AddObserver(const jni::Class::LocalRef& aCls,
                          jni::ObjectArray::Param aPrefNames,
                          jni::Object::Param aPrefHandler,
                          jni::ObjectArray::Param aPrefsToObserve) {
    // Call observer immediately with existing pref values.
    GetPrefs(aCls, aPrefNames, aPrefHandler);

    if (!aPrefsToObserve) {
      return;
    }

    nsTArray<jni::Object::LocalRef> nameRefArray(
        aPrefsToObserve->GetElements());
    nsAppShell* const appShell = nsAppShell::Get();
    MOZ_ASSERT(appShell);

    for (jni::Object::LocalRef& nameRef : nameRefArray) {
      jni::String::LocalRef nameStr(std::move(nameRef));
      MOZ_ALWAYS_SUCCEEDS(
          Preferences::AddStrongObserver(appShell, nameStr->ToCString()));
    }
  }

  static void RemoveObserver(const jni::Class::LocalRef& aCls,
                             jni::ObjectArray::Param aPrefsToUnobserve) {
    nsTArray<jni::Object::LocalRef> nameRefArray(
        aPrefsToUnobserve->GetElements());
    nsAppShell* const appShell = nsAppShell::Get();
    MOZ_ASSERT(appShell);

    for (jni::Object::LocalRef& nameRef : nameRefArray) {
      jni::String::LocalRef nameStr(std::move(nameRef));
      MOZ_ALWAYS_SUCCEEDS(
          Preferences::RemoveObserver(appShell, nameStr->ToCString()));
    }
  }

  static void OnPrefChange(const char16_t* aData) {
    const nsCString& name = NS_LossyConvertUTF16toASCII(aData);

    int32_t type = -1;
    bool boolVal = false;
    int32_t intVal = false;
    nsAutoString strVal;

    switch (Preferences::GetType(name.get())) {
      case nsIPrefBranch::PREF_BOOL:
        type = java::PrefsHelper::PREF_BOOL;
        boolVal = Preferences::GetBool(name.get());
        break;
      case nsIPrefBranch::PREF_INT:
        type = java::PrefsHelper::PREF_INT;
        intVal = Preferences::GetInt(name.get());
        break;
      case nsIPrefBranch::PREF_STRING: {
        type = java::PrefsHelper::PREF_STRING;
        nsresult rv = Preferences::GetLocalizedString(name.get(), strVal);
        if (NS_FAILED(rv)) {
          Preferences::GetString(name.get(), strVal);
        }
        break;
      }
      default:
        NS_WARNING(nsPrintfCString("Invalid pref %s", name.get()).get());
        return;
    }

    java::PrefsHelper::OnPrefChange(
        name, type, boolVal, intVal,
        jni::StringParam(type == java::PrefsHelper::PREF_STRING
                             ? jni::StringParam(strVal)
                             : jni::StringParam(nullptr)));
  }
};

}  // namespace mozilla

#endif  // PrefsHelper_h