accessible/atk/Platform.cpp
author Sylvestre Ledru <sledru@mozilla.com>
Fri, 24 Feb 2017 17:04:50 +0100
changeset 489671 05d9746016f47666c00390aacc9f9d62c8ffffb4
parent 489668 cbb8fdf1daf98a15f7d57f6b08d273bdf96aa1a0
permissions -rw-r--r--
Move to 99 chars instead of 80 MozReview-Commit-ID: 6NxbMuFVI7e

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */

#include "Platform.h"

#include "nsIAccessibleEvent.h"
#include "nsIGConfService.h"
#include "nsIServiceManager.h"
#include "nsMai.h"
#include "AtkSocketAccessible.h"
#include "prenv.h"
#include "prlink.h"

#ifdef MOZ_ENABLE_DBUS
#include <dbus/dbus.h>
#endif
#include <gtk/gtk.h>

#if (MOZ_WIDGET_GTK == 3)
extern "C" __attribute__((weak, visibility("default"))) int
atk_bridge_adaptor_init(int*, char** []);
#endif

using namespace mozilla;
using namespace mozilla::a11y;

int atkMajorVersion = 1, atkMinorVersion = 12;

GType (*gAtkTableCellGetTypeFunc)();

extern "C" {
typedef GType (*AtkGetTypeType)(void);
typedef void (*GnomeAccessibilityInit)(void);
typedef void (*GnomeAccessibilityShutdown)(void);
}

static PRLibrary* sATKLib = nullptr;
static const char sATKLibName[] = "libatk-1.0.so.0";
static const char sATKHyperlinkImplGetTypeSymbol[] = "atk_hyperlink_impl_get_type";

gboolean
toplevel_event_watcher(GSignalInvocationHint*, guint, const GValue*, gpointer);
static bool sToplevel_event_hook_added = false;
static gulong sToplevel_show_hook = 0;
static gulong sToplevel_hide_hook = 0;

GType g_atk_hyperlink_impl_type = G_TYPE_INVALID;

struct GnomeAccessibilityModule
{
  const char* libName;
  PRLibrary* lib;
  const char* initName;
  GnomeAccessibilityInit init;
  const char* shutdownName;
  GnomeAccessibilityShutdown shutdown;
};

static GnomeAccessibilityModule sAtkBridge = {
#ifdef AIX
  "libatk-bridge.a(libatk-bridge.so.0)", nullptr,
#else
    "libatk-bridge.so", nullptr,
#endif
  "gnome_accessibility_module_init",     nullptr, "gnome_accessibility_module_shutdown", nullptr
};

#if (MOZ_WIDGET_GTK == 2)
static GnomeAccessibilityModule sGail = { "libgail.so",
                                          nullptr,
                                          "gnome_accessibility_module_init",
                                          nullptr,
                                          "gnome_accessibility_module_shutdown",
                                          nullptr };
#endif

static nsresult
LoadGtkModule(GnomeAccessibilityModule& aModule)
{
  NS_ENSURE_ARG(aModule.libName);

  if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) {
    //try to load the module with "gtk-2.0/modules" appended
    char* curLibPath = PR_GetLibraryPath();
    nsAutoCString libPath(curLibPath);
#if defined(LINUX) && defined(__x86_64__)
    libPath.AppendLiteral(":/usr/lib64:/usr/lib");
#else
    libPath.AppendLiteral(":/usr/lib");
#endif
    PR_FreeLibraryName(curLibPath);

    int16_t loc1 = 0, loc2 = 0;
    int16_t subLen = 0;
    while (loc2 >= 0) {
      loc2 = libPath.FindChar(':', loc1);
      if (loc2 < 0) {
        subLen = libPath.Length() - loc1;
      } else {
        subLen = loc2 - loc1;
      }
      nsAutoCString sub(Substring(libPath, loc1, subLen));
#if (MOZ_WIDGET_GTK == 2)
      sub.AppendLiteral("/gtk-2.0/modules/");
#else
      sub.AppendLiteral("/gtk-3.0/modules/");
#endif
      sub.Append(aModule.libName);
      aModule.lib = PR_LoadLibrary(sub.get());
      if (aModule.lib) {
        break;
      }

      loc1 = loc2 + 1;
    }
    if (!aModule.lib) {
      return NS_ERROR_FAILURE;
    }
  }

  //we have loaded the library, try to get the function ptrs
  if (!(aModule.init = PR_FindFunctionSymbol(aModule.lib, aModule.initName)) ||
      !(aModule.shutdown = PR_FindFunctionSymbol(aModule.lib, aModule.shutdownName))) {

    //fail, :(
    PR_UnloadLibrary(aModule.lib);
    aModule.lib = nullptr;
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

void
a11y::PlatformInit()
{
  if (!ShouldA11yBeEnabled()) {
    return;
  }

  sATKLib = PR_LoadLibrary(sATKLibName);
  if (!sATKLib) {
    return;
  }

  AtkGetTypeType pfn_atk_hyperlink_impl_get_type =
    (AtkGetTypeType)PR_FindFunctionSymbol(sATKLib, sATKHyperlinkImplGetTypeSymbol);
  if (pfn_atk_hyperlink_impl_get_type) {
    g_atk_hyperlink_impl_type = pfn_atk_hyperlink_impl_get_type();
  }

  AtkGetTypeType pfn_atk_socket_get_type =
    (AtkGetTypeType)PR_FindFunctionSymbol(sATKLib, AtkSocketAccessible::sATKSocketGetTypeSymbol);
  if (pfn_atk_socket_get_type) {
    AtkSocketAccessible::g_atk_socket_type = pfn_atk_socket_get_type();
    AtkSocketAccessible::g_atk_socket_embed = (AtkSocketEmbedType)PR_FindFunctionSymbol(
      sATKLib, AtkSocketAccessible ::sATKSocketEmbedSymbol);
    AtkSocketAccessible::gCanEmbed = AtkSocketAccessible::g_atk_socket_type != G_TYPE_INVALID &&
                                     AtkSocketAccessible::g_atk_socket_embed;
  }

  gAtkTableCellGetTypeFunc = (GType(*)())PR_FindFunctionSymbol(sATKLib, "atk_table_cell_get_type");

  const char* (*atkGetVersion)() =
    (const char* (*)())PR_FindFunctionSymbol(sATKLib, "atk_get_version");
  if (atkGetVersion) {
    const char* version = atkGetVersion();
    if (version) {
      char* endPtr = nullptr;
      atkMajorVersion = strtol(version, &endPtr, 10);
      if (*endPtr == '.') {
        atkMinorVersion = strtol(endPtr + 1, &endPtr, 10);
      }
    }
  }

#if (MOZ_WIDGET_GTK == 2)
  // Load and initialize gail library.
  nsresult rv = LoadGtkModule(sGail);
  if (NS_SUCCEEDED(rv))
    (*sGail.init)();
#endif

  // Initialize the MAI Utility class, it will overwrite gail_util.
  g_type_class_unref(g_type_class_ref(mai_util_get_type()));

  // Init atk-bridge now
  PR_SetEnv("NO_AT_BRIDGE=0");
#if (MOZ_WIDGET_GTK == 3)
  if (atk_bridge_adaptor_init) {
    atk_bridge_adaptor_init(nullptr, nullptr);
  } else
#endif
  {
    nsresult rv = LoadGtkModule(sAtkBridge);
    if (NS_SUCCEEDED(rv)) {
      (*sAtkBridge.init)();
    }
  }

  if (!sToplevel_event_hook_added) {
    sToplevel_event_hook_added = true;
    sToplevel_show_hook =
      g_signal_add_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW),
                                 0,
                                 toplevel_event_watcher,
                                 reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW),
                                 nullptr);
    sToplevel_hide_hook =
      g_signal_add_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW),
                                 0,
                                 toplevel_event_watcher,
                                 reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_HIDE),
                                 nullptr);
  }
}

void
a11y::PlatformShutdown()
{
  if (sToplevel_event_hook_added) {
    sToplevel_event_hook_added = false;
    g_signal_remove_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW), sToplevel_show_hook);
    g_signal_remove_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW), sToplevel_hide_hook);
  }

  if (sAtkBridge.lib) {
    // Do not shutdown/unload atk-bridge,
    // an exit function registered will take care of it
    // if (sAtkBridge.shutdown)
    //     (*sAtkBridge.shutdown)();
    // PR_UnloadLibrary(sAtkBridge.lib);
    sAtkBridge.lib = nullptr;
    sAtkBridge.init = nullptr;
    sAtkBridge.shutdown = nullptr;
  }
#if (MOZ_WIDGET_GTK == 2)
  if (sGail.lib) {
    // Do not shutdown gail because
    // 1) Maybe it's not init-ed by us. e.g. GtkEmbed
    // 2) We need it to avoid assert in spi_atk_tidy_windows
    // if (sGail.shutdown)
    //   (*sGail.shutdown)();
    // PR_UnloadLibrary(sGail.lib);
    sGail.lib = nullptr;
    sGail.init = nullptr;
    sGail.shutdown = nullptr;
  }
#endif
  // if (sATKLib) {
  //     PR_UnloadLibrary(sATKLib);
  //     sATKLib = nullptr;
  // }
}

static const char sAccEnv[] = "GNOME_ACCESSIBILITY";
#ifdef MOZ_ENABLE_DBUS
static DBusPendingCall* sPendingCall = nullptr;
#endif

void
a11y::PreInit()
{
#ifdef MOZ_ENABLE_DBUS
  static bool sChecked = FALSE;
  if (sChecked) {
    return;
  }

  sChecked = TRUE;

  // dbus is only checked if GNOME_ACCESSIBILITY is unset
  // also make sure that a session bus address is available to prevent dbus from
  // starting a new one.  Dbus confuses the test harness when it creates a new
  // process (see bug 693343)
  if (PR_GetEnv(sAccEnv) || !PR_GetEnv("DBUS_SESSION_BUS_ADDRESS")) {
    return;
  }

  DBusConnection* bus = dbus_bus_get(DBUS_BUS_SESSION, nullptr);
  if (!bus) {
    return;
  }

  dbus_connection_set_exit_on_disconnect(bus, FALSE);

  static const char* iface = "org.a11y.Status";
  static const char* member = "IsEnabled";
  DBusMessage* message;
  message = dbus_message_new_method_call(
    "org.a11y.Bus", "/org/a11y/bus", "org.freedesktop.DBus.Properties", "Get");
  if (!message) {
    goto dbus_done;
  }

  dbus_message_append_args(
    message, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID);
  dbus_connection_send_with_reply(bus, message, &sPendingCall, 1000);
  dbus_message_unref(message);

dbus_done:
  dbus_connection_unref(bus);
#endif
}

bool
a11y::ShouldA11yBeEnabled()
{
  static bool sChecked = false, sShouldEnable = false;
  if (sChecked) {
    return sShouldEnable;
  }

  sChecked = true;

  EPlatformDisabledState disabledState = PlatformDisabledState();
  if (disabledState == ePlatformIsDisabled) {
    return sShouldEnable = false;
  }

  // check if accessibility enabled/disabled by environment variable
  const char* envValue = PR_GetEnv(sAccEnv);
  if (envValue) {
    return sShouldEnable = !!atoi(envValue);
  }

#ifdef MOZ_ENABLE_DBUS
  PreInit();
  bool dbusSuccess = false;
  DBusMessage* reply = nullptr;
  if (!sPendingCall) {
    goto dbus_done;
  }

  dbus_pending_call_block(sPendingCall);
  reply = dbus_pending_call_steal_reply(sPendingCall);
  dbus_pending_call_unref(sPendingCall);
  sPendingCall = nullptr;
  if (!reply || dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN ||
      strcmp(dbus_message_get_signature(reply), DBUS_TYPE_VARIANT_AS_STRING)) {
    goto dbus_done;
  }

  DBusMessageIter iter, iter_variant, iter_struct;
  dbus_bool_t dResult;
  dbus_message_iter_init(reply, &iter);
  dbus_message_iter_recurse(&iter, &iter_variant);
  switch (dbus_message_iter_get_arg_type(&iter_variant)) {
    case DBUS_TYPE_STRUCT:
      // at-spi2-core 2.2.0-2.2.1 had a bug where it returned a struct
      dbus_message_iter_recurse(&iter_variant, &iter_struct);
      if (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_BOOLEAN) {
        dbus_message_iter_get_basic(&iter_struct, &dResult);
        sShouldEnable = dResult;
        dbusSuccess = true;
      }

      break;
    case DBUS_TYPE_BOOLEAN:
      dbus_message_iter_get_basic(&iter_variant, &dResult);
      sShouldEnable = dResult;
      dbusSuccess = true;
      break;
    default:
      break;
  }

dbus_done:
  if (reply) {
    dbus_message_unref(reply);
  }

  if (dbusSuccess) {
    return sShouldEnable;
  }
#endif

//check gconf-2 setting
#define GCONF_A11Y_KEY "/desktop/gnome/interface/accessibility"
  nsresult rv = NS_OK;
  nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID, &rv);
  if (NS_SUCCEEDED(rv) && gconf) {
    gconf->GetBool(NS_LITERAL_CSTRING(GCONF_A11Y_KEY), &sShouldEnable);
  }

  return sShouldEnable;
}