DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (d03b538b6b41)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
/* 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/. */

"use strict";

const { Cc, Ci } = require("chrome");
const Services = require("Services");
const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";

/**
 * A helper class that does all the work related to accessibility service
 * lifecycle (initialization, shutdown, consumer changes, etc) in parent
 * parent process. It is not guaranteed that the AccessibilityActor starts in
 * parent process and thus triggering these lifecycle functions directly is
 * extremely unreliable.
 */
class AccessibilityParent {
  constructor(mm, prefix) {
    this._msgName = `debug:${prefix}accessibility`;
    this.onAccessibilityMessage = this.onAccessibilityMessage.bind(this);
    this.setMessageManager(mm);

    this.userPref = Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
    Services.obs.addObserver(this, "a11y-consumers-changed");
    Services.prefs.addObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);

    if (this.enabled && !this.accService) {
      // Set a local reference to an accessibility service if accessibility was
      // started elsewhere to ensure that parent process a11y service does not
      // get GC'ed away.
      this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
        Ci.nsIAccessibilityService);
    }

    this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
      topic: "initialized",
      data: {
        enabled: this.enabled,
        canBeDisabled: this.canBeDisabled,
        canBeEnabled: this.canBeEnabled
      }
    });
  }

  /**
   * Set up message manager listener to listen for messages coming from the
   * AccessibilityActor when it is instantiated in the child process.
   *
   * @param {Object} mm
   *        Message manager that corresponds to the current content tab.
   */
  setMessageManager(mm) {
    if (this.messageManager === mm) {
      return;
    }

    if (this.messageManager) {
      // If the browser was swapped we need to reset the message manager.
      const oldMM = this.messageManager;
      oldMM.removeMessageListener(this._msgName, this.onAccessibilityMessage);
    }

    this.messageManager = mm;
    if (mm) {
      mm.addMessageListener(this._msgName, this.onAccessibilityMessage);
    }
  }

  /**
   * Content AccessibilityActor message listener.
   *
   * @param  {String} msg
   *         Name of the action to perform.
   */
  onAccessibilityMessage(msg) {
    const { action } = msg.json;
    switch (action) {
      case "enable":
        this.enable();
        break;

      case "disable":
        this.disable();
        break;

      case "disconnect":
        this.destroy();
        break;

      default:
        break;
    }
  }

  observe(subject, topic, data) {
    if (topic === "a11y-consumers-changed") {
      // This event is fired when accessibility service consumers change. Since
      // this observer lives in parent process there are 2 possible consumers of
      // a11y service: XPCOM and PlatformAPI (e.g. screen readers). We only care
      // about PlatformAPI consumer changes because when set, we can no longer
      // disable accessibility service.
      const { PlatformAPI } = JSON.parse(data);
      this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
        topic: "can-be-disabled-change",
        data: !PlatformAPI
      });
    } else if (!this.disabling && topic === "nsPref:changed" &&
               data === PREF_ACCESSIBILITY_FORCE_DISABLED) {
      // PREF_ACCESSIBILITY_FORCE_DISABLED preference change event. When set to
      // >=1, it means that the user wants to disable accessibility service and
      // prevent it from starting in the future. Note: we also check
      // this.disabling state when handling this pref change because this is how
      // we disable the accessibility inspector itself.
      this.messageManager.sendAsyncMessage(`${this._msgName}:event`, {
        topic: "can-be-enabled-change",
        data: Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1
      });
    }
  }

  /**
   * A getter that indicates if accessibility service is enabled.
   *
   * @return {Boolean}
   *         True if accessibility service is on.
   */
  get enabled() {
    return Services.appinfo.accessibilityEnabled;
  }

  /**
   * A getter that indicates if the accessibility service can be disabled.
   *
   * @return {Boolean}
   *         True if accessibility service can be disabled.
   */
  get canBeDisabled() {
    if (this.enabled) {
      const a11yService = Cc["@mozilla.org/accessibilityService;1"].getService(
        Ci.nsIAccessibilityService);
      const { PlatformAPI } = JSON.parse(a11yService.getConsumers());
      return !PlatformAPI;
    }

    return true;
  }

  /**
   * A getter that indicates if the accessibility service can be enabled.
   *
   * @return {Boolean}
   *         True if accessibility service can be enabled.
   */
  get canBeEnabled() {
    return Services.prefs.getIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED) < 1;
  }

  /**
   * Enable accessibility service (via XPCOM service).
   */
  enable() {
    if (this.enabled || !this.canBeEnabled) {
      return;
    }

    this.accService = Cc["@mozilla.org/accessibilityService;1"].getService(
      Ci.nsIAccessibilityService);
  }

  /**
   * Force disable accessibility service. This method removes the reference to
   * the XPCOM a11y service object and flips the
   * PREF_ACCESSIBILITY_FORCE_DISABLED preference on and off to shutdown a11y
   * service.
   */
  disable() {
    if (!this.enabled || !this.canBeDisabled) {
      return;
    }

    this.disabling = true;
    this.accService = null;
    // Set PREF_ACCESSIBILITY_FORCE_DISABLED to 1 to force disable
    // accessibility service. This is the only way to guarantee an immediate
    // accessibility service shutdown in all processes. This also prevents
    // accessibility service from starting up in the future.
    Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
    // Set PREF_ACCESSIBILITY_FORCE_DISABLED back to previous default or user
    // set value. This will not start accessibility service until the user
    // activates it again. It simply ensures that accessibility service can
    // start again (when value is below 1).
    Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, this.userPref);
    delete this.disabling;
  }

  /**
   * Destroy thie helper class, remove all listeners and if possible disable
   * accessibility service in the parent process.
   */
  destroy() {
    Services.obs.removeObserver(this, "a11y-consumers-changed");
    Services.prefs.removeObserver(PREF_ACCESSIBILITY_FORCE_DISABLED, this);
    this.setMessageManager(null);
    this.accService = null;
  }
}

/**
 * Setup function that runs in parent process and setups AccessibleActor bits
 * that must always run in parent process.
 *
 * @param  {Object} options.mm
 *         Message manager that corresponds to the current content tab.
 * @param  {String} options.prefix
 *         Unique prefix for message manager messages.
 * @return {Object}
 *         Defines event listeners for when client disconnects or browser gets
 *         swapped.
 */
function setupParentProcess({ mm, prefix }) {
  let accessibility = new AccessibilityParent(mm, prefix);

  return {
    onBrowserSwap: newMM => accessibility.setMessageManager(newMM),
    onDisconnected: () => {
      accessibility.destroy();
      accessibility = null;
    }
  };
}

exports.setupParentProcess = setupParentProcess;