Backed out 2 changesets (bug 1449364, bug 1453278) for rc4 and 42 perma failures in layout/base/tests/test_bug332655-2.html and testInputConnection on a CLOSED TREE
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Thu, 12 Apr 2018 15:30:20 +0300
changeset 413002 1f351e5132ab4f21dc117dc737c8e24e19d2e21a
parent 413001 5ea0755f98afcf0b007b018fa79285089c01c789
child 413003 a453e9ae307e0fbab5f4d0885ecb9be9a0324327
push id33829
push userarchaeopteryx@coole-files.de
push dateThu, 12 Apr 2018 19:20:32 +0000
treeherdermozilla-central@17dda59473c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1449364, 1453278, 332655
milestone61.0a1
backs out7149139c60d98e3e5338795cb2be648136f2b9a4
bcfee006ebaa56c3f0e36871f29f121efdaa5692
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1449364, bug 1453278) for rc4 and 42 perma failures in layout/base/tests/test_bug332655-2.html and testInputConnection on a CLOSED TREE Backed out changeset 7149139c60d9 (bug 1449364) Backed out changeset bcfee006ebaa (bug 1453278)
accessible/jsat/AccessFu.jsm
accessible/jsat/Utils.jsm
accessible/tests/mochitest/jsat/jsatcommon.js
accessible/tests/mochitest/jsat/test_alive.html
accessible/tests/mochitest/jsat/test_live_regions.html
accessible/tests/mochitest/jsat/test_quicknav_modes.html
js/src/jit/mips32/Simulator-mips32.cpp
js/src/jit/mips32/Simulator-mips32.h
js/src/shell/js.cpp
js/src/shell/jsshell.h
js/src/wasm/WasmModule.h
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoAccessibility.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
mobile/android/chrome/geckoview/geckoview.js
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
mobile/android/modules/geckoview/GeckoViewAccessibility.jsm
mobile/android/modules/geckoview/moz.build
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -8,53 +8,56 @@ var EXPORTED_SYMBOLS = ["AccessFu"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
 
 if (Utils.MozBuildApp === "mobile/android") {
   ChromeUtils.import("resource://gre/modules/Messaging.jsm");
 }
 
+// const ACCESSFU_DISABLE = 0;
+const ACCESSFU_ENABLE = 1;
+const ACCESSFU_AUTO = 2;
+
+const SCREENREADER_SETTING = "accessibility.screenreader";
 const QUICKNAV_MODES_PREF = "accessibility.accessfu.quicknav_modes";
 const QUICKNAV_INDEX_PREF = "accessibility.accessfu.quicknav_index";
 
-const GECKOVIEW_MESSAGE = {
-  ACTIVATE: "GeckoView:AccessibilityActivate",
-  VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
-  LONG_PRESS: "GeckoView:AccessibilityLongPress",
-  BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
-  NEXT: "GeckoView:AccessibilityNext",
-  PREVIOUS: "GeckoView:AccessibilityPrevious",
-  SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
-  SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
-};
-
 var AccessFu = {
   /**
    * Initialize chrome-layer accessibility functionality.
    * If accessibility is enabled on the platform, then a special accessibility
    * mode is started.
    */
-  attach: function attach(aWindow, aInTest = false) {
+  attach: function attach(aWindow) {
     Utils.init(aWindow);
 
-    if (!aInTest) {
-      this._enable();
+    if (Utils.MozBuildApp === "mobile/android") {
+      EventDispatcher.instance.dispatch("Accessibility:Ready");
+      EventDispatcher.instance.registerListener(this, "Accessibility:Settings");
     }
+
+    this._activatePref = new PrefCache(
+      "accessibility.accessfu.activate", this._enableOrDisable.bind(this));
+
+    this._enableOrDisable();
   },
 
   /**
    * Shut down chrome-layer accessibility functionality from the outside.
    */
   detach: function detach() {
     // Avoid disabling twice.
     if (this._enabled) {
       this._disable();
     }
-
+    if (Utils.MozBuildApp === "mobile/android") {
+      EventDispatcher.instance.unregisterListener(this, "Accessibility:Settings");
+    }
+    delete this._activatePref;
     Utils.uninit();
   },
 
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
    */
   get handleEvent() {
     delete this.handleEvent;
@@ -109,18 +112,26 @@ var AccessFu = {
       new PrefCache("accessibility.accessfu.notify_output");
 
 
     this.Input.start();
     Output.start();
     PointerAdapter.start();
 
     if (Utils.MozBuildApp === "mobile/android") {
-      Utils.win.WindowEventDispatcher.registerListener(this,
-        Object.values(GECKOVIEW_MESSAGE));
+      EventDispatcher.instance.registerListener(this, [
+        "Accessibility:ActivateObject",
+        "Accessibility:Focus",
+        "Accessibility:LongPress",
+        "Accessibility:MoveByGranularity",
+        "Accessibility:NextObject",
+        "Accessibility:PreviousObject",
+        "Accessibility:ScrollBackward",
+        "Accessibility:ScrollForward",
+      ]);
     }
 
     Services.obs.addObserver(this, "remote-browser-shown");
     Services.obs.addObserver(this, "inprocess-browser-shown");
     Utils.win.addEventListener("TabOpen", this);
     Utils.win.addEventListener("TabClose", this);
     Utils.win.addEventListener("TabSelect", this);
 
@@ -156,31 +167,56 @@ var AccessFu = {
     Utils.win.removeEventListener("TabOpen", this);
     Utils.win.removeEventListener("TabClose", this);
     Utils.win.removeEventListener("TabSelect", this);
 
     Services.obs.removeObserver(this, "remote-browser-shown");
     Services.obs.removeObserver(this, "inprocess-browser-shown");
 
     if (Utils.MozBuildApp === "mobile/android") {
-      Utils.win.WindowEventDispatcher.unregisterListener(this,
-        Object.values(GECKOVIEW_MESSAGE));
+      EventDispatcher.instance.unregisterListener(this, [
+        "Accessibility:ActivateObject",
+        "Accessibility:Focus",
+        "Accessibility:LongPress",
+        "Accessibility:MoveByGranularity",
+        "Accessibility:NextObject",
+        "Accessibility:PreviousObject",
+        "Accessibility:ScrollBackward",
+        "Accessibility:ScrollForward",
+      ]);
     }
 
     delete this._quicknavModesPref;
     delete this._notifyOutputPref;
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
 
     Logger.info("AccessFu:Disabled");
   },
 
+  _enableOrDisable: function _enableOrDisable() {
+    try {
+      if (!this._activatePref) {
+        return;
+      }
+      let activatePref = this._activatePref.value;
+      if (activatePref == ACCESSFU_ENABLE ||
+          this._systemPref && activatePref == ACCESSFU_AUTO) {
+        this._enable();
+      } else {
+        this._disable();
+      }
+    } catch (x) {
+      dump("Error " + x.message + " " + x.fileName + ":" + x.lineNumber);
+    }
+  },
+
   receiveMessage: function receiveMessage(aMessage) {
     Logger.debug(() => {
       return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
     });
 
     switch (aMessage.name) {
       case "AccessFu:Ready":
         let mm = Utils.getMessageManager(aMessage.target);
@@ -255,53 +291,50 @@ var AccessFu = {
     if (this._enabled) {
       this._addMessageListeners(aMessageManager);
     }
     this._loadFrameScript(aMessageManager);
   },
 
   onEvent(event, data, callback) {
     switch (event) {
-      case GECKOVIEW_MESSAGE.SETTINGS:
-        if (data.enabled) {
-          this._enable();
-        } else {
-          this._disable();
-        }
+      case "Accessibility:Settings":
+        this._systemPref = data.enabled;
+        this._enableOrDisable();
         break;
-      case GECKOVIEW_MESSAGE.NEXT:
-      case GECKOVIEW_MESSAGE.PREVIOUS: {
+      case "Accessibility:NextObject":
+      case "Accessibility:PreviousObject": {
         let rule = "Simple";
         if (data && data.rule && data.rule.length) {
           rule = data.rule.substr(0, 1).toUpperCase() +
             data.rule.substr(1).toLowerCase();
         }
-        let method = event.replace(/GeckoView:Accessibility(\w+)/, "move$1");
+        let method = event.replace(/Accessibility:(\w+)Object/, "move$1");
         this.Input.moveCursor(method, rule, "gesture");
         break;
       }
-      case GECKOVIEW_MESSAGE.ACTIVATE:
+      case "Accessibility:ActivateObject":
         this.Input.activateCurrent(data);
         break;
-      case GECKOVIEW_MESSAGE.LONG_PRESS:
+      case "Accessibility:LongPress":
         this.Input.sendContextMenuMessage();
         break;
-      case GECKOVIEW_MESSAGE.SCROL_LFORWARD:
+      case "Accessibility:ScrollForward":
         this.Input.androidScroll("forward");
         break;
-      case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
+      case "Accessibility:ScrollBackward":
         this.Input.androidScroll("backward");
         break;
-      case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
+      case "Accessibility:Focus":
         this._focused = data.gainFocus;
         if (this._focused) {
           this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
         }
         break;
-      case GECKOVIEW_MESSAGE.BY_GRANULARITY:
+      case "Accessibility:MoveByGranularity":
         this.Input.moveByGranularity(data);
         break;
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "remote-browser-shown":
@@ -346,17 +379,24 @@ var AccessFu = {
             delay: 500,
             forcePresent: true,
             noOpIfOnScreen: true,
             moveMethod: "moveFirst" });
         }
         break;
       }
       default:
+      {
+        // A settings change, it does not have an event type
+        if (aEvent.settingName == SCREENREADER_SETTING) {
+          this._systemPref = aEvent.settingValue;
+          this._enableOrDisable();
+        }
         break;
+      }
     }
   },
 
   autoMove: function autoMove(aOptions) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     mm.sendAsyncMessage("AccessFu:AutoMove", aOptions);
   },
 
@@ -540,17 +580,17 @@ var Output = {
     }
   },
 
   Android: function Android(aDetails, aBrowser) {
     const ANDROID_VIEW_TEXT_CHANGED = 0x10;
     const ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
 
     for (let androidEvent of aDetails) {
-      androidEvent.type = "GeckoView:AccessibilityEvent";
+      androidEvent.type = "Accessibility:Event";
       if (androidEvent.bounds) {
         androidEvent.bounds = AccessFu.adjustContentBounds(
           androidEvent.bounds, aBrowser);
       }
 
       switch (androidEvent.eventType) {
         case ANDROID_VIEW_TEXT_CHANGED:
           androidEvent.brailleOutput = this.brailleState.adjustText(
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -136,18 +136,17 @@ var Utils = { // jshint ignore:line
         return this.win.shell;
       default:
         return null;
     }
   },
 
   get CurrentBrowser() {
     if (!this.BrowserApp) {
-      // Get the first content browser element when no 'BrowserApp' exists.
-      return this.win.document.querySelector("browser[type=content]");
+      return null;
     }
     if (this.MozBuildApp == "b2g") {
       return this.BrowserApp.contentBrowser;
     }
     return this.BrowserApp.selectedBrowser;
   },
 
   get CurrentContentDoc() {
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -136,30 +136,24 @@ var AccessFuTest = {
       for (var testFunc of gTestFuncs) {
         yield testFunc;
       }
     })();
 
     // Start AccessFu and put it in stand-by.
     ChromeUtils.import("resource://gre/modules/accessibility/AccessFu.jsm");
 
-    let chromeWin = getMainChromeWindow(window);
-    chromeWin.WindowEventDispatcher = {
-      dispatch: () => {},
-      sendRequest: () => {}
-    };
+    AccessFu.attach(getMainChromeWindow(window));
 
     AccessFu.readyCallback = function readyCallback() {
       // Enable logging to the console service.
       Logger.test = true;
       Logger.logLevel = Logger.DEBUG;
     };
 
-    AccessFu.attach(chromeWin, true);
-
     var prefs = [["accessibility.accessfu.notify_output", 1]];
     prefs.push.apply(prefs, aAdditionalPrefs);
 
     this.originalDwellThreshold = GestureSettings.dwellThreshold;
     this.originalSwipeMaxDuration = GestureSettings.swipeMaxDuration;
     this.originalMaxGestureResolveTimeout =
       GestureSettings.maxGestureResolveTimeout;
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes
--- a/accessible/tests/mochitest/jsat/test_alive.html
+++ b/accessible/tests/mochitest/jsat/test_alive.html
@@ -8,16 +8,24 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="./jsatcommon.js"></script>
   <script type="application/javascript">
 
+    function prefStart() {
+      AccessFuTest.once_log("AccessFu:Enabled", () =>
+        ok(AccessFu._enabled, "AccessFu was enabled again."));
+      AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
+      // Start AccessFu via pref.
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
+    }
+
     // Listen for 'EventManager.stop' and enable AccessFu again.
     function settingsStart() {
       isnot(AccessFu._enabled, true, "AccessFu was disabled.");
       // XXX: Bug 978076 - test start with SettingsManager.
       // navigator.mozSettings.createLock().set(
       //  {'accessibility.screenreader': false});
       AccessFuTest.once_log("EventManager.start", () => {
         ok(AccessFu._enabled, "AccessFu was enabled again.");
@@ -34,17 +42,29 @@
       //  {'accessibility.screenreader': false});
       AccessFuTest.once_log("EventManager.stop", () => {
         isnot(AccessFu._enabled, "AccessFu was disabled.");
         AccessFuTest.finish();
       });
       AccessFu._disable();
     }
 
+    // Listen for initial 'EventManager.start' and disable AccessFu.
+    function prefStop() {
+      ok(AccessFu._enabled, "AccessFu was started via preference.");
+      AccessFuTest.once_log("AccessFu:Disabled", () =>
+        isnot(AccessFu._enabled, true, "AccessFu was disabled."));
+      AccessFuTest.once_log("EventManager.stop", AccessFuTest.nextTest);
+
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
+    }
+
     function doTest() {
+      AccessFuTest.addFunc(prefStart);
+      AccessFuTest.addFunc(prefStop);
       AccessFuTest.addFunc(settingsStart);
       AccessFuTest.addFunc(settingsStop);
       AccessFuTest.waitForExplicitFinish();
       AccessFuTest.runTests(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
--- a/accessible/tests/mochitest/jsat/test_live_regions.html
+++ b/accessible/tests/mochitest/jsat/test_live_regions.html
@@ -9,23 +9,23 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="./jsatcommon.js"></script>
   <script type="application/javascript">
 
     function startAccessFu() {
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
       AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
-      AccessFu._enable();
     }
 
     function stopAccessFu() {
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
       AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
-      AccessFu._disable();
     }
 
     function hide(id) {
       var element = document.getElementById(id);
       element.style.display = "none";
     }
 
     function show(id) {
--- a/accessible/tests/mochitest/jsat/test_quicknav_modes.html
+++ b/accessible/tests/mochitest/jsat/test_quicknav_modes.html
@@ -8,19 +8,20 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="./jsatcommon.js"></script>
   <script type="application/javascript">
 
-    function startAccessFu() {
+    function prefStart() {
+      // Start AccessFu via pref.
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 1]]});
       AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
-      AccessFu._enable();
     }
 
     function nextMode(aCurrentMode, aNextMode) {
       return function() {
         is(AccessFu.Input.quickNavMode.current, aCurrentMode,
           "initial current mode is correct");
         AccessFu.Input.quickNavMode.next();
         _expectMode(aNextMode, AccessFuTest.nextTest);
@@ -63,33 +64,34 @@
       } else {
         AccessFuTest.once_log("Quicknav mode: " + aExpectedMode, function() {
           ok(true, "correct mode");
           aCallback();
         });
       }
     }
 
-    function stopAccessFu() {
-      ok(AccessFu._enabled, "AccessFu is enabled.");
+    // Listen for initial 'EventManager.start' and disable AccessFu.
+    function prefStop() {
+      ok(AccessFu._enabled, "AccessFu was started via preference.");
       AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
-      AccessFu._disable();
+      SpecialPowers.pushPrefEnv({"set": [["accessibility.accessfu.activate", 0]]});
     }
 
     function doTest() {
-      AccessFuTest.addFunc(startAccessFu);
+      AccessFuTest.addFunc(prefStart);
       AccessFuTest.addFunc(nextMode("Link", "Heading"));
       AccessFuTest.addFunc(nextMode("Heading", "FormElement"));
       AccessFuTest.addFunc(nextMode("FormElement", "Link"));
       AccessFuTest.addFunc(nextMode("Link", "Heading"));
       AccessFuTest.addFunc(prevMode("Heading", "Link"));
       AccessFuTest.addFunc(prevMode("Link", "FormElement"));
       AccessFuTest.addFunc(setMode(1, "Heading"));
       AccessFuTest.addFunc(reconfigureModes);
-      AccessFuTest.addFunc(stopAccessFu);
+      AccessFuTest.addFunc(prefStop);
       AccessFuTest.waitForExplicitFinish();
       AccessFuTest.runTests([   // Will call SimpleTest.finish();
         ["accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement"]]);
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
--- a/js/src/jit/mips32/Simulator-mips32.cpp
+++ b/js/src/jit/mips32/Simulator-mips32.cpp
@@ -1250,19 +1250,16 @@ Simulator::Simulator()
 
     stack_ = nullptr;
     stackLimit_ = 0;
     pc_modified_ = false;
     icount_ = 0;
     break_count_ = 0;
     break_pc_ = nullptr;
     break_instr_ = 0;
-    single_stepping_ = false;
-    single_step_callback_ = nullptr;
-    single_step_callback_arg_ = nullptr;
 
     // Set up architecture state.
     // All registers are initialized to zero to start with.
     for (int i = 0; i < Register::kNumSimuRegisters; i++) {
         registers_[i] = 0;
     }
     for (int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) {
         FPUregisters_[i] = 0;
@@ -2066,19 +2063,16 @@ Simulator::softwareInterrupt(SimInstruct
         intptr_t external = reinterpret_cast<intptr_t>(redirection->nativeFunction());
 
         bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0;
         if (!stack_aligned) {
             fprintf(stderr, "Runtime call with unaligned stack!\n");
             MOZ_CRASH();
         }
 
-        if (single_stepping_)
-            single_step_callback_(single_step_callback_arg_, this, nullptr);
-
         switch (redirection->type()) {
           case Args_General0: {
             Prototype_General0 target = reinterpret_cast<Prototype_General0>(external);
             int64_t result = target();
             setCallResult(result);
             break;
           }
           case Args_General1: {
@@ -2286,19 +2280,16 @@ Simulator::softwareInterrupt(SimInstruct
             double dresult = target(dval0, dval1, dval2, dval3);
             setCallResultDouble(dresult);
             break;
           }
           default:
             MOZ_CRASH("call");
         }
 
-        if (single_stepping_)
-            single_step_callback_(single_step_callback_arg_, this, nullptr);
-
         setRegister(ra, saved_ra);
         set_pc(getRegister(ra));
 #endif
     } else if (func == ff_break && code <= kMaxStopCode) {
         if (isWatchpoint(code)) {
             printWatchpoint(code);
         } else {
             increaseStopCounter(code);
@@ -3633,63 +3624,35 @@ Simulator::branchDelayInstructionDecode(
     }
 
     if (instr->isForbiddenInBranchDelay()) {
         MOZ_CRASH("Eror:Unexpected opcode in a branch delay slot.");
     }
     instructionDecode(instr);
 }
 
-void
-Simulator::enable_single_stepping(SingleStepCallback cb, void* arg)
-{
-    single_stepping_ = true;
-    single_step_callback_ = cb;
-    single_step_callback_arg_ = arg;
-    single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
-}
-
-void
-Simulator::disable_single_stepping()
-{
-    if (!single_stepping_)
-        return;
-    single_step_callback_(single_step_callback_arg_, this, (void*)get_pc());
-    single_stepping_ = false;
-    single_step_callback_ = nullptr;
-    single_step_callback_arg_ = nullptr;
-}
-
 template<bool enableStopSimAt>
 void
 Simulator::execute()
 {
-    if (single_stepping_)
-        single_step_callback_(single_step_callback_arg_, this, nullptr);
-
     // Get the PC to simulate. Cannot use the accessor here as we need the
     // raw PC value and not the one used as input to arithmetic instructions.
     int program_counter = get_pc();
 
     while (program_counter != end_sim_pc) {
         if (enableStopSimAt && (icount_ == Simulator::StopSimAt)) {
             MipsDebugger dbg(this);
             dbg.debug();
         } else {
-            if (single_stepping_)
-                single_step_callback_(single_step_callback_arg_, this, (void*)program_counter);
             SimInstruction* instr = reinterpret_cast<SimInstruction*>(program_counter);
             instructionDecode(instr);
             icount_++;
         }
         program_counter = get_pc();
     }
-
-    if (single_stepping_)
-        single_step_callback_(single_step_callback_arg_, this, nullptr);
 }
 
 void
 Simulator::callInternal(uint8_t* entry)
 {
     // Prepare to execute the code at entry.
     setRegister(pc, reinterpret_cast<int32_t>(entry));
     // Put down marker for end of simulation. The simulator will stop simulation
--- a/js/src/jit/mips32/Simulator-mips32.h
+++ b/js/src/jit/mips32/Simulator-mips32.h
@@ -45,21 +45,16 @@ namespace jit {
 
 class JitActivation;
 
 class Simulator;
 class Redirection;
 class CachePage;
 class AutoLockSimulator;
 
-// When the SingleStepCallback is called, the simulator is about to execute
-// sim->get_pc() and the current machine state represents the completed
-// execution of the previous pc.
-typedef void (*SingleStepCallback)(void* arg, Simulator* sim, void* pc);
-
 const intptr_t kPointerAlignment = 4;
 const intptr_t kPointerAlignmentMask = kPointerAlignment - 1;
 
 const intptr_t kDoubleAlignment = 8;
 const intptr_t kDoubleAlignmentMask = kDoubleAlignment - 1;
 
 
 // Number of general purpose registers.
@@ -202,19 +197,16 @@ class Simulator {
 
     // Special case of set_register and get_register to access the raw PC value.
     void set_pc(int32_t value);
     int32_t get_pc() const;
 
     template <typename T>
     T get_pc_as() const { return reinterpret_cast<T>(get_pc()); }
 
-    void enable_single_stepping(SingleStepCallback cb, void* arg);
-    void disable_single_stepping();
-
     // Accessor to the internal simulator stack area.
     uintptr_t stackLimit() const;
     bool overRecursed(uintptr_t newsp = 0) const;
     bool overRecursedWithExtra(uint32_t extra) const;
 
     // Executes MIPS instructions until the PC reaches end_sim_pc.
     template<bool enableStopSimAt>
     void execute();
@@ -366,21 +358,16 @@ class Simulator {
 
     // Debugger input.
     char* lastDebuggerInput_;
 
     // Registered breakpoints.
     SimInstruction* break_pc_;
     Instr break_instr_;
 
-    // Single-stepping support
-    bool single_stepping_;
-    SingleStepCallback single_step_callback_;
-    void* single_step_callback_arg_;
-
     // A stop is watched if its code is less than kNumOfWatchedStops.
     // Only watched stops support enabling/disabling and the counter feature.
     static const uint32_t kNumOfWatchedStops = 256;
 
 
     // Stop is disabled if bit 31 is set.
     static const uint32_t kStopDisabledBit = 1U << 31;
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5322,17 +5322,17 @@ SingleStepCallback(void* arg, jit::Simul
         return;
 
     JS::ProfilingFrameIterator::RegisterState state;
     state.pc = pc;
 #if defined(JS_SIMULATOR_ARM)
     state.sp = (void*)sim->get_register(jit::Simulator::sp);
     state.lr = (void*)sim->get_register(jit::Simulator::lr);
     state.fp = (void*)sim->get_register(jit::Simulator::fp);
-#elif defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
+#elif defined(JS_SIMULATOR_MIPS64)
     state.sp = (void*)sim->getRegister(jit::Simulator::sp);
     state.lr = (void*)sim->getRegister(jit::Simulator::ra);
     state.fp = (void*)sim->getRegister(jit::Simulator::fp);
 #else
 #  error "NYI: Single-step profiling support"
 #endif
 
     mozilla::DebugOnly<void*> lastStackAddress = nullptr;
--- a/js/src/shell/jsshell.h
+++ b/js/src/shell/jsshell.h
@@ -18,17 +18,17 @@
 #include "threading/ConditionVariable.h"
 #include "threading/LockGuard.h"
 #include "threading/Mutex.h"
 #include "threading/Thread.h"
 #include "vm/GeckoProfiler.h"
 #include "vm/Monitor.h"
 
 // Some platform hooks must be implemented for single-step profiling.
-#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
+#if defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS64)
 # define SINGLESTEP_PROFILING
 #endif
 
 namespace js {
 namespace shell {
 
 enum JSShellErrNum {
 #define MSG_DEF(name, count, exception, format) \
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -14,17 +14,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_module_h
 #define wasm_module_h
 
-#include "jit/shared/Assembler-shared.h"
 #include "js/TypeDecls.h"
 #include "threading/ConditionVariable.h"
 #include "threading/Mutex.h"
 #include "vm/MutexIDs.h"
 #include "wasm/WasmCode.h"
 #include "wasm/WasmTable.h"
 #include "wasm/WasmValidate.h"
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -878,17 +878,17 @@ public class BrowserApp extends GeckoApp
 
         doorhangerOverlay = findViewById(R.id.doorhanger_overlay);
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "Search:Keyword",
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
-            "GeckoView:AccessibilityEnabled",
+            "Accessibility:Enabled",
             "Menu:Open",
             "Menu:Update",
             "Menu:Add",
             "Menu:Remove",
             "Menu:AddBrowserAction",
             "Menu:RemoveBrowserAction",
             "Menu:UpdateBrowserAction",
             "LightweightTheme:Update",
@@ -1710,17 +1710,17 @@ public class BrowserApp extends GeckoApp
 
         mSearchEngineManager.unregisterListeners();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "Search:Keyword",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
-            "GeckoView:AccessibilityEnabled",
+            "Accessibility:Enabled",
             "Menu:Open",
             "Menu:Update",
             "Menu:Add",
             "Menu:Remove",
             "Menu:AddBrowserAction",
             "Menu:RemoveBrowserAction",
             "Menu:UpdateBrowserAction",
             "LightweightTheme:Update",
@@ -1963,17 +1963,17 @@ public class BrowserApp extends GeckoApp
                         !IntentUtils.getIsInAutomationFromEnvironment(new SafeIntent(getIntent()))) {
                     // TODO: Better scheduling of DLC actions (Bug 1257492)
                     DownloadContentService.startSync(this);
                     DownloadContentService.startVerification(this);
                 }
 
                 break;
 
-            case "GeckoView:AccessibilityEnabled":
+            case "Accessibility:Enabled":
                 mDynamicToolbar.setAccessibilityEnabled(message.getBoolean("enabled"));
                 break;
 
             case "Menu:Open":
                 if (mBrowserToolbar.isEditing()) {
                     mBrowserToolbar.cancelEdit();
                 }
                 openOptionsMenu();
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAccessibility.java
@@ -0,0 +1,401 @@
+/* -*- Mode: Java; c-basic-offset: 4; 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/. */
+
+package org.mozilla.gecko;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.geckoview.GeckoView;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+
+import com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient;
+import com.googlecode.eyesfree.braille.selfbraille.WriteData;
+
+public class GeckoAccessibility {
+    private static final String LOGTAG = "GeckoAccessibility";
+    private static final int VIRTUAL_ENTRY_POINT_BEFORE = 1;
+    private static final int VIRTUAL_CURSOR_POSITION = 2;
+    private static final int VIRTUAL_ENTRY_POINT_AFTER = 3;
+
+    private static boolean sEnabled;
+    // Used to store the JSON message and populate the event later in the code path.
+    private static GeckoBundle sHoverEnter;
+    private static AccessibilityNodeInfo sVirtualCursorNode;
+    private static int sCurrentNode;
+
+    // This is the number Brailleback uses to start indexing routing keys.
+    private static final int BRAILLE_CLICK_BASE_INDEX = -275000000;
+    private static SelfBrailleClient sSelfBrailleClient;
+
+    public static void updateAccessibilitySettings (final Context context) {
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                final AccessibilityManager accessibilityManager = (AccessibilityManager)
+                        context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+                sEnabled = accessibilityManager.isEnabled() &&
+                           accessibilityManager.isTouchExplorationEnabled();
+
+                if (Build.VERSION.SDK_INT >= 16 && sEnabled && sSelfBrailleClient == null) {
+                    sSelfBrailleClient = new SelfBrailleClient(context, false);
+                }
+
+                final GeckoBundle ret = new GeckoBundle(1);
+                ret.putBoolean("enabled", sEnabled);
+                // "Accessibility:Settings" is dispatched to the Gecko thread.
+                EventDispatcher.getInstance().dispatch("Accessibility:Settings", ret);
+                // "Accessibility:Enabled" is dispatched to the UI thread.
+                EventDispatcher.getInstance().dispatch("Accessibility:Enabled", ret);
+            }
+        });
+    }
+
+    private static void populateEventFromJSON (AccessibilityEvent event, GeckoBundle message) {
+        final String[] textArray = message.getStringArray("text");
+        if (textArray != null) {
+            for (int i = 0; i < textArray.length; i++)
+                event.getText().add(textArray[i] != null ? textArray[i] : "");
+        }
+
+        event.setContentDescription(message.getString("description", ""));
+        event.setEnabled(message.getBoolean("enabled", true));
+        event.setChecked(message.getBoolean("checked"));
+        event.setPassword(message.getBoolean("password"));
+        event.setAddedCount(message.getInt("addedCount", -1));
+        event.setRemovedCount(message.getInt("removedCount", -1));
+        event.setFromIndex(message.getInt("fromIndex", -1));
+        event.setItemCount(message.getInt("itemCount", -1));
+        event.setCurrentItemIndex(message.getInt("currentItemIndex", -1));
+        event.setBeforeText(message.getString("beforeText", ""));
+        event.setToIndex(message.getInt("toIndex", -1));
+        event.setScrollable(message.getBoolean("scrollable"));
+        event.setScrollX(message.getInt("scrollX", -1));
+        event.setScrollY(message.getInt("scrollY", -1));
+        event.setMaxScrollX(message.getInt("maxScrollX", -1));
+        event.setMaxScrollY(message.getInt("maxScrollY", -1));
+    }
+
+    private static void sendDirectAccessibilityEvent(int eventType, GeckoBundle message) {
+        final Context context = GeckoAppShell.getApplicationContext();
+        final AccessibilityEvent accEvent = AccessibilityEvent.obtain(eventType);
+        accEvent.setClassName(GeckoAccessibility.class.getName());
+        accEvent.setPackageName(context.getPackageName());
+        populateEventFromJSON(accEvent, message);
+        AccessibilityManager accessibilityManager =
+            (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        try {
+            accessibilityManager.sendAccessibilityEvent(accEvent);
+        } catch (IllegalStateException e) {
+            // Accessibility is off.
+        }
+    }
+
+    public static boolean isEnabled() {
+        return sEnabled;
+    }
+
+    public static void sendAccessibilityEvent(final GeckoView view,
+                                              final GeckoBundle message) {
+        if (!sEnabled)
+            return;
+
+        final int eventType = message.getInt("eventType", -1);
+        if (eventType < 0) {
+            Log.e(LOGTAG, "No accessibility event type provided");
+            return;
+        }
+
+        sendAccessibilityEvent(view, message, eventType);
+    }
+
+    public static void sendAccessibilityEvent(final GeckoView view, final GeckoBundle message,
+                                              final int eventType) {
+        if (!sEnabled)
+            return;
+
+        final String exitView = message.getString("exitView", "");
+        if (exitView.equals("moveNext")) {
+            sCurrentNode = VIRTUAL_ENTRY_POINT_AFTER;
+        } else if (exitView.equals("movePrevious")) {
+            sCurrentNode = VIRTUAL_ENTRY_POINT_BEFORE;
+        } else {
+            sCurrentNode = VIRTUAL_CURSOR_POSITION;
+        }
+
+        if (Build.VERSION.SDK_INT < 16) {
+            // Before Jelly Bean we send events directly from here while spoofing the source by setting
+            // the package and class name manually.
+            ThreadUtils.postToBackgroundThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        sendDirectAccessibilityEvent(eventType, message);
+                }
+            });
+        } else {
+            // In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
+            // it work with TalkBack.
+            if (sVirtualCursorNode == null) {
+                sVirtualCursorNode = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION);
+            }
+            sVirtualCursorNode.setEnabled(message.getBoolean("enabled", true));
+            sVirtualCursorNode.setClickable(message.getBoolean("clickable"));
+            sVirtualCursorNode.setCheckable(message.getBoolean("checkable"));
+            sVirtualCursorNode.setChecked(message.getBoolean("checked"));
+            sVirtualCursorNode.setPassword(message.getBoolean("password"));
+
+            final String[] textArray = message.getStringArray("text");
+            StringBuilder sb = new StringBuilder();
+            if (textArray != null && textArray.length > 0) {
+                sb.append(textArray[0] != null ? textArray[0] : "");
+                for (int i = 1; i < textArray.length; i++) {
+                    sb.append(' ').append(textArray[i] != null ? textArray[i] : "");
+                }
+                sVirtualCursorNode.setText(sb.toString());
+            }
+            sVirtualCursorNode.setContentDescription(message.getString("description", ""));
+
+            final GeckoBundle bounds = message.getBundle("bounds");
+            if (bounds != null) {
+                Rect relativeBounds = new Rect(bounds.getInt("left"), bounds.getInt("top"),
+                                               bounds.getInt("right"), bounds.getInt("bottom"));
+                sVirtualCursorNode.setBoundsInParent(relativeBounds);
+
+                final Matrix matrix = new Matrix();
+                final float[] origin = new float[2];
+                view.getSession().getClientToScreenMatrix(matrix);
+                matrix.mapPoints(origin);
+
+                relativeBounds.offset((int) origin[0], (int) origin[1]);
+                sVirtualCursorNode.setBoundsInScreen(relativeBounds);
+            }
+
+            final GeckoBundle braille = message.getBundle("brailleOutput");
+            if (braille != null) {
+                sendBrailleText(view, braille.getString("text", ""),
+                                braille.getInt("selectionStart"), braille.getInt("selectionEnd"));
+            }
+
+            if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
+                sHoverEnter = message;
+            }
+
+            final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+            event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+            event.setClassName(GeckoAccessibility.class.getName());
+            if (eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT ||
+                eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+                event.setSource(view, View.NO_ID);
+            } else {
+                event.setSource(view, VIRTUAL_CURSOR_POSITION);
+            }
+            populateEventFromJSON(event, message);
+            ((ViewParent) view).requestSendAccessibilityEvent(view, event);
+        }
+    }
+
+    private static void sendBrailleText(final View view, final String text, final int selectionStart, final int selectionEnd) {
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(view, VIRTUAL_CURSOR_POSITION);
+        WriteData data = WriteData.forInfo(info);
+        data.setText(text);
+        // Set either the focus blink or the current caret position/selection
+        data.setSelectionStart(selectionStart);
+        data.setSelectionEnd(selectionEnd);
+        sSelfBrailleClient.write(data);
+    }
+
+    public static void setDelegate(View view) {
+        // Only use this delegate in Jelly Bean.
+        if (Build.VERSION.SDK_INT >= 16) {
+            view.setAccessibilityDelegate(new GeckoAccessibilityDelegate());
+            view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+
+        view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(final View v, final boolean hasFocus) {
+                onLayerViewFocusChanged(hasFocus);
+            }
+        });
+    }
+
+    @TargetApi(19)
+    public static void setAccessibilityManagerListeners(final Context context) {
+        AccessibilityManager accessibilityManager =
+            (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() {
+            @Override
+            public void onAccessibilityStateChanged(boolean enabled) {
+                updateAccessibilitySettings(context);
+            }
+        });
+
+        if (Build.VERSION.SDK_INT >= 19) {
+            accessibilityManager.addTouchExplorationStateChangeListener(new AccessibilityManager.TouchExplorationStateChangeListener() {
+                @Override
+                public void onTouchExplorationStateChanged(boolean enabled) {
+                    updateAccessibilitySettings(context);
+                }
+            });
+        }
+    }
+
+    public static void onLayerViewFocusChanged(boolean gainFocus) {
+        if (sEnabled) {
+            final GeckoBundle data = new GeckoBundle(1);
+            data.putBoolean("gainFocus", gainFocus);
+            EventDispatcher.getInstance().dispatch("Accessibility:Focus", data);
+        }
+    }
+
+    public static class GeckoAccessibilityDelegate extends View.AccessibilityDelegate {
+        AccessibilityNodeProvider mAccessibilityNodeProvider;
+
+        @Override
+        public AccessibilityNodeProvider getAccessibilityNodeProvider(final View hostView) {
+            if (!(hostView instanceof GeckoView)) {
+                return super.getAccessibilityNodeProvider(hostView);
+            }
+            final GeckoView host = (GeckoView) hostView;
+
+            if (mAccessibilityNodeProvider == null)
+                // The accessibility node structure for web content consists of 3 LayerView child nodes:
+                // 1. VIRTUAL_ENTRY_POINT_BEFORE: Represents the entry point before the LayerView.
+                // 2. VIRTUAL_CURSOR_POSITION: Represents the current position of the virtual cursor.
+                // 3. VIRTUAL_ENTRY_POINT_AFTER: Represents the entry point after the LayerView.
+                mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+                        @Override
+                        public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
+                            AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CURSOR_POSITION && sVirtualCursorNode != null) ?
+                                AccessibilityNodeInfo.obtain(sVirtualCursorNode) :
+                                AccessibilityNodeInfo.obtain(host, virtualDescendantId);
+
+                            switch (virtualDescendantId) {
+                            case View.NO_ID:
+                                // This is the parent LayerView node, populate it with children.
+                                onInitializeAccessibilityNodeInfo(host, info);
+                                info.addChild(host, VIRTUAL_ENTRY_POINT_BEFORE);
+                                info.addChild(host, VIRTUAL_CURSOR_POSITION);
+                                info.addChild(host, VIRTUAL_ENTRY_POINT_AFTER);
+                                break;
+                            default:
+                                info.setParent(host);
+                                info.setSource(host, virtualDescendantId);
+                                info.setVisibleToUser(host.isShown());
+                                info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+                                info.setClassName(host.getClass().getName());
+                                info.setEnabled(true);
+                                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                                info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                                info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+                                info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+                                info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+                                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+                                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+                                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+                                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+                                info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+                                info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
+                                                              AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
+                                                              AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
+                                                              AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
+                                break;
+                            }
+                            return info;
+                        }
+
+                        @Override
+                        public boolean performAction (int virtualViewId, int action, Bundle arguments) {
+                            if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+                                // The accessibility focus is permanently on the middle node, VIRTUAL_CURSOR_POSITION.
+                                // When we enter the view forward or backward we just ask Gecko to get focus, keeping the current position.
+                                if (virtualViewId == VIRTUAL_CURSOR_POSITION && sHoverEnter != null) {
+                                    GeckoAccessibility.sendAccessibilityEvent(host, sHoverEnter, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+                                } else {
+                                    final GeckoBundle data = new GeckoBundle(1);
+                                    data.putBoolean("gainFocus", true);
+                                    EventDispatcher.getInstance().dispatch("Accessibility:Focus", data);
+                                }
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                EventDispatcher.getInstance().dispatch("Accessibility:ActivateObject", null);
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                EventDispatcher.getInstance().dispatch("Accessibility:LongPress", null);
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                EventDispatcher.getInstance().dispatch("Accessibility:ScrollForward", null);
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                EventDispatcher.getInstance().dispatch("Accessibility:ScrollBackward", null);
+                                return true;
+                            } else if ((action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ||
+                                        action == AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT) && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                final GeckoBundle data;
+                                if (arguments != null) {
+                                    data = new GeckoBundle(1);
+                                    data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
+                                } else {
+                                    data = null;
+                                }
+                                EventDispatcher.getInstance().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
+                                        "Accessibility:NextObject" : "Accessibility:PreviousObject", data);
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY &&
+                                       virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                // XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
+                                // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
+                                // Other negative values are used by ChromeVox, but we don't support them.
+                                // FAKE_GRANULARITY_READ_CURRENT = -1
+                                // FAKE_GRANULARITY_READ_TITLE = -2
+                                // FAKE_GRANULARITY_STOP_SPEECH = -3
+                                // FAKE_GRANULARITY_CHANGE_SHIFTER = -4
+                                int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                                if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
+                                    int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
+                                    final GeckoBundle data = new GeckoBundle(1);
+                                    data.putInt("keyIndex", keyIndex);
+                                    EventDispatcher.getInstance().dispatch("Accessibility:ActivateObject", data);
+                                } else if (granularity > 0) {
+                                    final GeckoBundle data = new GeckoBundle(2);
+                                    data.putString("direction", "Next");
+                                    data.putInt("granularity", granularity);
+                                    EventDispatcher.getInstance().dispatch("Accessibility:MoveByGranularity", data);
+                                }
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY &&
+                                       virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                                final GeckoBundle data = new GeckoBundle(2);
+                                data.putString("direction", "Previous");
+                                data.putInt("granularity", granularity);
+                                if (granularity > 0) {
+                                    EventDispatcher.getInstance().dispatch("Accessibility:MoveByGranularity", data);
+                                }
+                                return true;
+                            }
+                            return host.performAccessibilityAction(action, arguments);
+                        }
+                    };
+
+            return mAccessibilityNodeProvider;
+        }
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -682,16 +682,22 @@ public abstract class GeckoApp extends G
             }, STARTUP_PHASE_DURATION_MS);
 
         } else if (event.equals("Gecko:CorruptAPK")) {
             showCorruptAPKError();
             if (!isFinishing()) {
                 finish();
             }
 
+        } else if ("Accessibility:Ready".equals(event)) {
+            GeckoAccessibility.updateAccessibilitySettings(this);
+
+        } else if ("Accessibility:Event".equals(event)) {
+            GeckoAccessibility.sendAccessibilityEvent(mLayerView, message);
+
         } else if ("Contact:Add".equals(event)) {
             final String email = message.getString("email");
             final String phone = message.getString("phone");
             if (email != null) {
                 Uri contactUri = Uri.parse(email);
                 Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
                 startActivity(i);
             } else if (phone != null) {
@@ -1027,16 +1033,17 @@ public abstract class GeckoApp extends G
             if (!TextUtils.isEmpty(uri)) {
                 // Start a speculative connection as soon as Gecko loads.
                 GeckoThread.speculativeConnect(uri);
             }
         }
 
         // To prevent races, register startup events before launching the Gecko thread.
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Accessibility:Ready",
             "Gecko:Ready",
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
             "Gecko:CorruptAPK",
             "Update:Check",
             "Update:Download",
             "Update:Install",
@@ -1079,22 +1086,25 @@ public abstract class GeckoApp extends G
 
         // If the view already has a session, we need to ensure it is closed.
         if (mLayerView.getSession() != null) {
             mLayerView.getSession().close();
         }
         mLayerView.setSession(session, GeckoRuntime.getDefault(this));
         mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
 
+        GeckoAccessibility.setDelegate(mLayerView);
+
         getAppEventDispatcher().registerGeckoThreadListener(this,
             "Locale:Set",
             "PrivateBrowsing:Data",
             null);
 
         getAppEventDispatcher().registerUiThreadListener(this,
+            "Accessibility:Event",
             "Contact:Add",
             "DevToolsAuth:Scan",
             "DOMFullScreen:Start",
             "DOMFullScreen:Stop",
             "Mma:reader_available",
             "Mma:web_save_image",
             "Mma:web_save_media",
             "Permissions:Data",
@@ -2074,32 +2084,34 @@ public abstract class GeckoApp extends G
         }
 
         if (mTextSelection != null) {
             mTextSelection.destroy();
             mTextSelection = null;
         }
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Accessibility:Ready",
             "Gecko:Ready",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
             "Gecko:CorruptAPK",
             "Update:Check",
             "Update:Download",
             "Update:Install",
             null);
 
         getAppEventDispatcher().unregisterGeckoThreadListener(this,
             "Locale:Set",
             "PrivateBrowsing:Data",
             null);
 
         getAppEventDispatcher().unregisterUiThreadListener(this,
+            "Accessibility:Event",
             "Contact:Add",
             "DevToolsAuth:Scan",
             "DOMFullScreen:Start",
             "DOMFullScreen:Stop",
             "Mma:reader_available",
             "Mma:web_save_image",
             "Mma:web_save_media",
             "Permissions:Data",
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -362,16 +362,18 @@ public class GeckoApplication extends Ap
             ThreadUtils.postToBackgroundThread(new Runnable() {
                 @Override
                 public void run() {
                     initPushService();
                 }
             });
         }
 
+        GeckoAccessibility.setAccessibilityManagerListeners(this);
+
         AudioFocusAgent.getInstance().attachToContext(this);
     }
 
     private class EventListener implements BundleEventListener
     {
         private void onProfileCreate(final String name, final String path) {
             // Add everything when we're done loading the distribution.
             final Context context = GeckoApplication.this;
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -32,16 +32,17 @@ import android.view.View;
 import android.widget.ProgressBar;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.Clipboard;
 import org.mozilla.gecko.DoorHangerPopup;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.FormAssistPopup;
+import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
@@ -118,16 +119,18 @@ public class CustomTabsActivity extends 
 
         actionBarPresenter = new ActionBarPresenter(actionBar, getActionBarTextColor());
         actionBarPresenter.displayUrlOnly(intent.getDataString());
         actionBarPresenter.setBackgroundColor(IntentUtil.getToolbarColor(intent), getWindow());
         actionBarPresenter.setTextLongClickListener(new UrlCopyListener());
 
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
 
+        GeckoAccessibility.setDelegate(mGeckoView);
+
         final GeckoSessionSettings settings = new GeckoSessionSettings();
         settings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, false);
         settings.setBoolean(
             GeckoSessionSettings.USE_REMOTE_DEBUGGER,
             GeckoSharedPrefs.forApp(this).getBoolean(
                 GeckoPreferences.PREFS_DEVTOOLS_REMOTE_USB_ENABLED, false));
         mGeckoSession = new GeckoSession(settings);
         mGeckoSession.setNavigationDelegate(this);
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -21,16 +21,17 @@ import android.view.Window;
 import android.view.WindowManager;
 import android.widget.Toast;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.DoorHangerPopup;
 import org.mozilla.gecko.FormAssistPopup;
+import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoScreenOrientation;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.text.TextSelection;
@@ -137,16 +138,18 @@ public class WebAppActivity extends AppC
                         message = R.string.identity_connection_insecure;
                         fallbackToFennec(getString(message));
                     }
                 }
 
             }
         });
 
+        GeckoAccessibility.setDelegate(mGeckoView);
+
         mPromptService = new PromptService(this, mGeckoView.getEventDispatcher());
         mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
 
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.pwa_form_assist_popup);
         mFormAssistPopup.create(mGeckoView);
 
         mTextSelection = TextSelection.Factory.create(mGeckoView, this);
         mTextSelection.create();
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -81,15 +81,13 @@ function startup() {
   ModuleManager.add("resource://gre/modules/GeckoViewTab.jsm",
                     "GeckoViewTab");
   ModuleManager.add("resource://gre/modules/GeckoViewRemoteDebugger.jsm",
                     "GeckoViewRemoteDebugger");
   ModuleManager.add("resource://gre/modules/GeckoViewTrackingProtection.jsm",
                     "GeckoViewTrackingProtection");
   ModuleManager.add("resource://gre/modules/GeckoViewSelectionAction.jsm",
                     "GeckoViewSelectionAction");
-  ModuleManager.add("resource://gre/modules/GeckoViewAccessibility.jsm",
-                    "GeckoViewAccessibility");
 
   // Move focus to the content window at the end of startup,
   // so things like text selection can work properly.
   browser.focus();
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -87,18 +87,16 @@ public class GeckoSession extends LayerS
     private final NativeQueue mNativeQueue =
         new NativeQueue(State.INITIAL, State.READY);
 
     private final EventDispatcher mEventDispatcher =
         new EventDispatcher(mNativeQueue);
 
     private final SessionTextInput mTextInput = new SessionTextInput(this, mNativeQueue);
 
-    private SessionAccessibility mSessionAccessibility;
-
     private String mId = UUID.randomUUID().toString().replace("-", "");
     /* package */ String getId() { return mId; }
 
     private final GeckoSessionHandler<ContentDelegate> mContentHandler =
         new GeckoSessionHandler<ContentDelegate>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContextMenu",
@@ -822,28 +820,16 @@ public class GeckoSession extends LayerS
      *
      * @return SessionTextInput instance.
      */
     public @NonNull SessionTextInput getTextInput() {
         // May be called on any thread.
         return mTextInput;
     }
 
-    /**
-      * Get the SessionAccessibility instance for this session.
-      *
-      * @return SessionAccessibility instance.
-      */
-    public @NonNull SessionAccessibility getAccessibility() {
-        if (mSessionAccessibility == null) {
-            mSessionAccessibility = new SessionAccessibility(this);
-        }
-        return mSessionAccessibility;
-    }
-
     @IntDef(flag = true,
             value = { LOAD_FLAGS_NONE, LOAD_FLAGS_BYPASS_CACHE, LOAD_FLAGS_BYPASS_PROXY,
                       LOAD_FLAGS_EXTERNAL, LOAD_FLAGS_ALLOW_POPUPS })
     public @interface LoadFlags {}
 
     // These flags follow similarly named ones in Gecko's nsIWebNavigation.idl
     // https://searchfox.org/mozilla-central/source/docshell/base/nsIWebNavigation.idl
     //
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -29,27 +29,29 @@ import android.support.annotation.NonNul
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
-import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
 public class GeckoView extends FrameLayout {
     private static final String LOGTAG = "GeckoView";
     private static final boolean DEBUG = false;
 
+    private static AccessibilityManager sAccessibilityManager;
+
     protected final Display mDisplay = new Display();
     protected GeckoSession mSession;
     protected GeckoRuntime mRuntime;
     private boolean mStateSaved;
 
     protected SurfaceView mSurfaceView;
 
     private boolean mIsResettingFocus;
@@ -160,17 +162,16 @@ public class GeckoView extends FrameLayo
     public GeckoView(final Context context, final AttributeSet attrs) {
         super(context, attrs);
         init();
     }
 
     private void init() {
         setFocusable(true);
         setFocusableInTouchMode(true);
-        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         // We are adding descendants to this LayerView, but we don't want the
         // descendants to affect the way LayerView retains its focus.
         setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
 
         // This will stop PropertyAnimator from creating a drawing cache (i.e. a
         // bitmap) from a SurfaceView, which is just not possible (the bitmap will be
         // transparent).
@@ -319,28 +320,25 @@ public class GeckoView extends FrameLayo
         }
 
         if (!mSession.isOpen()) {
             mSession.open(mRuntime);
         }
 
         mSession.getTextInput().setView(this);
 
-        mSession.getAccessibility().setView(this);
-
         super.onAttachedToWindow();
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
         if (mSession != null) {
-            mSession.getTextInput().setView(null);
-            mSession.getAccessibility().setView(null);
+          mSession.getTextInput().setView(null);
         }
 
         if (mStateSaved) {
             // If we saved state earlier, we don't want to close the window.
             return;
         }
 
         if (mSession != null && mSession.isOpen()) {
@@ -504,22 +502,31 @@ public class GeckoView extends FrameLayo
         }
 
         // NOTE: Treat mouse events as "touch" rather than as "mouse", so mouse can be
         // used to pan/zoom. Call onMouseEvent() instead for behavior similar to desktop.
         return mSession != null &&
                mSession.getPanZoomController().onTouchEvent(event);
     }
 
+    protected static boolean isAccessibilityEnabled(final Context context) {
+        if (sAccessibilityManager == null) {
+            sAccessibilityManager = (AccessibilityManager)
+                    context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        }
+        return sAccessibilityManager.isEnabled() &&
+               sAccessibilityManager.isTouchExplorationEnabled();
+    }
+
     @Override
     public boolean onHoverEvent(final MotionEvent event) {
         // If we get a touchscreen hover event, and accessibility is not enabled, don't
         // send it to Gecko.
         if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN &&
-            !SessionAccessibility.Settings.isEnabled()) {
+            !isAccessibilityEnabled(getContext())) {
             return false;
         }
 
         return mSession != null &&
                mSession.getPanZoomController().onMotionEvent(event);
     }
 
     @Override
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; 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/. */
-
-package org.mozilla.geckoview;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-
-public class SessionAccessibility {
-    private static final String LOGTAG = "GeckoAccessibility";
-    // This is a special ID we use for nodes that are eent sources.
-    // We expose it as a fragment and not an actual child of the View node.
-    private static final int VIRTUAL_CONTENT_ID = -2;
-
-    // This is the number BrailleBack uses to start indexing routing keys.
-    private static final int BRAILLE_CLICK_BASE_INDEX = -275000000;
-
-    // Gecko session we are proxying
-    /* package */  final GeckoSession mSession;
-    // This is the view that delegates accessibility to us. We also sends event through it.
-    private View mView;
-    // Aave we reached the last item in content?
-    private boolean mLastItem;
-    // Used to store the JSON message and populate the event later in the code path.
-    private AccessibilityNodeInfo mVirtualContentNode;
-
-    /* package */ SessionAccessibility(final GeckoSession session) {
-        mSession = session;
-
-        Settings.getInstance().dispatch();
-
-        session.getEventDispatcher().registerUiThreadListener(new BundleEventListener() {
-            @Override
-            public void handleMessage(final String event, final GeckoBundle message,
-                                      final EventCallback callback) {
-                sendAccessibilityEvent(message);
-            }
-        }, "GeckoView:AccessibilityEvent", null);
-    }
-
-    /**
-      * Get the GeckoView instance that delegates accessibility to this session.
-      *
-      * @return GeckoView instance.
-      */
-    public View getView() {
-        return mView;
-    }
-
-    /**
-      * Set the GeckoView instance that should delegate accessibility to this session.
-      */
-    public void setView(final View view) {
-        if (mView != null) {
-            mView.setAccessibilityDelegate(null);
-        }
-
-        mView = view;
-        mLastItem = false;
-
-        if (mView == null) {
-            return;
-        }
-
-        mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
-            private AccessibilityNodeProvider mAccessibilityNodeProvider;
-
-            @Override
-            public AccessibilityNodeProvider getAccessibilityNodeProvider(final View hostView) {
-
-                if (mAccessibilityNodeProvider == null)
-                    mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
-                    @Override
-                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
-                        assertAttachedView(hostView);
-
-                        AccessibilityNodeInfo info = (virtualDescendantId == VIRTUAL_CONTENT_ID && mVirtualContentNode != null) ?
-                                                     AccessibilityNodeInfo.obtain(mVirtualContentNode) :
-                                                     AccessibilityNodeInfo.obtain(mView, virtualDescendantId);
-
-                        switch (virtualDescendantId) {
-                        case View.NO_ID:
-                            // This is the parent View node.
-                            // We intentionally don't add VIRTUAL_CONTENT_ID
-                            // as a child. It is a source for events,
-                            // but not a member of the tree you
-                            // can get to by traversing down.
-                            onInitializeAccessibilityNodeInfo(mView, info);
-                            info.setClassName("android.webkit.WebView"); // TODO: WTF
-                            Bundle bundle = info.getExtras();
-                            bundle.putCharSequence(
-                                "ACTION_ARGUMENT_HTML_ELEMENT_STRING_VALUES",
-                                "ARTICLE,BUTTON,CHECKBOX,COMBOBOX,CONTROL,FOCUSABLE,FRAME,GRAPHIC,H1,H2,H3,H4,H5,H6,HEADING,LANDMARK,LINK,LIST,LIST_ITEM,MAIN,MEDIA,RADIO,SECTION,TABLE,TEXT_FIELD,UNVISITED_LINK,VISITED_LINK");
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-                            info.addChild(hostView, VIRTUAL_CONTENT_ID);
-                            break;
-                        default:
-                            info.setParent(mView);
-                            info.setSource(mView, virtualDescendantId);
-                            info.setVisibleToUser(mView.isShown());
-                            info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
-                            info.setClassName(mView.getClass().getName());
-                            info.setEnabled(true);
-                            info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
-                            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
-                            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-                            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-                            info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-                            info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-                            info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
-                                                          AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
-                                                          AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
-                                                          AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
-                            break;
-                        }
-                        return info;
-                    }
-
-                    @Override
-                    public boolean performAction(int virtualViewId, int action, Bundle arguments) {
-                        assertAttachedView(hostView);
-
-                        if (virtualViewId == View.NO_ID) {
-                            return performRootAction(action, arguments);
-                        }
-                        return performContentAction(action, arguments);
-                    }
-
-                    private boolean performRootAction(int action, Bundle arguments) {
-                        switch (action) {
-                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
-                        case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
-                            final GeckoBundle data = new GeckoBundle(1);
-                            data.putBoolean("gainFocus", action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityViewFocused", data);
-                            return true;
-                        }
-
-                        return mView.performAccessibilityAction(action, arguments);
-                    }
-
-                    private boolean performContentAction(int action, Bundle arguments) {
-                        final GeckoBundle data;
-                        switch (action) {
-                        case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
-                            final AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, VIRTUAL_CONTENT_ID);
-                            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_CLICK:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_LONG_CLICK:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollForward", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityScrollBackward", null);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
-                            if (mLastItem) {
-                                return false;
-                            }
-                        case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-                            if (arguments != null) {
-                                data = new GeckoBundle(1);
-                                data.putString("rule", arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING));
-                            } else {
-                                data = null;
-                            }
-                            mSession.getEventDispatcher().dispatch(action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ?
-                                                                   "GeckoView:AccessibilityNext" : "GeckoView:AccessibilityPrevious", data);
-                            return true;
-                        case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-                        case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-                            // XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
-                            // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit.
-                            // Other negative values are used by ChromeVox, but we don't support them.
-                            // FAKE_GRANULARITY_READ_CURRENT = -1
-                            // FAKE_GRANULARITY_READ_TITLE = -2
-                            // FAKE_GRANULARITY_STOP_SPEECH = -3
-                            // FAKE_GRANULARITY_CHANGE_SHIFTER = -4
-                            int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
-                            if (granularity <= BRAILLE_CLICK_BASE_INDEX) {
-                                int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
-                                data = new GeckoBundle(1);
-                                data.putInt("keyIndex", keyIndex);
-                                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", data);
-                            } else if (granularity > 0) {
-                                data = new GeckoBundle(2);
-                                data.putString("direction", action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ? "Next" : "Previous");
-                                data.putInt("granularity", granularity);
-                                mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityByGranularity", data);
-                            }
-                            return true;
-                        }
-
-                        return mView.performAccessibilityAction(action, arguments);
-                    }
-
-                    private void assertAttachedView(final View view) {
-                        if (view != mView) {
-                            throw new AssertionError("delegate used with wrong view.");
-                        }
-                    }
-                };
-
-                return mAccessibilityNodeProvider;
-            }
-
-        });
-    }
-
-    public static class Settings {
-        private static final Settings INSTANCE = new Settings();
-        private boolean mEnabled;
-
-        public Settings() {
-            EventDispatcher.getInstance().registerUiThreadListener(new BundleEventListener() {
-                @Override
-                public void handleMessage(final String event, final GeckoBundle message,
-                                          final EventCallback callback) {
-                    updateAccessibilitySettings();
-                }
-            }, "GeckoView:AccessibilityReady", null);
-
-            final Context context = GeckoAppShell.getApplicationContext();
-            AccessibilityManager accessibilityManager =
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-
-            mEnabled = accessibilityManager.isEnabled() &&
-                       accessibilityManager.isTouchExplorationEnabled();
-
-            accessibilityManager.addAccessibilityStateChangeListener(
-            new AccessibilityManager.AccessibilityStateChangeListener() {
-                @Override
-                public void onAccessibilityStateChanged(boolean enabled) {
-                    updateAccessibilitySettings();
-                }
-            }
-            );
-
-            if (Build.VERSION.SDK_INT >= 19) {
-                accessibilityManager.addTouchExplorationStateChangeListener(
-                new AccessibilityManager.TouchExplorationStateChangeListener() {
-                    @Override
-                    public void onTouchExplorationStateChanged(boolean enabled) {
-                        updateAccessibilitySettings();
-                    }
-                }
-                );
-            }
-        }
-
-        public static Settings getInstance() {
-            return INSTANCE;
-        }
-
-        public static boolean isEnabled() {
-            return INSTANCE.mEnabled;
-        }
-
-        private void updateAccessibilitySettings() {
-            ThreadUtils.postToBackgroundThread(new Runnable() {
-                @Override
-                public void run() {
-                    final AccessibilityManager accessibilityManager = (AccessibilityManager)
-                            GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-                    mEnabled = accessibilityManager.isEnabled() &&
-                               accessibilityManager.isTouchExplorationEnabled();
-
-                    dispatch();
-                }
-            });
-        }
-
-        private void dispatch() {
-            final GeckoBundle ret = new GeckoBundle(1);
-            ret.putBoolean("enabled", mEnabled);
-            // "GeckoView:AccessibilitySettings" is dispatched to the Gecko thread.
-            EventDispatcher.getInstance().dispatch("GeckoView:AccessibilitySettings", ret);
-            // "GeckoView:AccessibilityEnabled" is dispatched to the UI thread.
-            EventDispatcher.getInstance().dispatch("GeckoView:AccessibilityEnabled", ret);
-        }
-    }
-
-    private AccessibilityEvent obtainEvent(final int eventType, final int sourceId) {
-        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
-        event.setClassName(SessionAccessibility.class.getName());
-        event.setSource(mView, sourceId);
-
-        return event;
-    }
-
-    private static void populateEventFromJSON(AccessibilityEvent event, final GeckoBundle message) {
-        final String[] textArray = message.getStringArray("text");
-        if (textArray != null) {
-            for (int i = 0; i < textArray.length; i++)
-                event.getText().add(textArray[i] != null ? textArray[i] : "");
-        }
-
-        event.setContentDescription(message.getString("description", ""));
-        event.setEnabled(message.getBoolean("enabled", true));
-        event.setChecked(message.getBoolean("checked"));
-        event.setPassword(message.getBoolean("password"));
-        event.setAddedCount(message.getInt("addedCount", -1));
-        event.setRemovedCount(message.getInt("removedCount", -1));
-        event.setFromIndex(message.getInt("fromIndex", -1));
-        event.setItemCount(message.getInt("itemCount", -1));
-        event.setCurrentItemIndex(message.getInt("currentItemIndex", -1));
-        event.setBeforeText(message.getString("beforeText", ""));
-        event.setToIndex(message.getInt("toIndex", -1));
-        event.setScrollable(message.getBoolean("scrollable"));
-        event.setScrollX(message.getInt("scrollX", -1));
-        event.setScrollY(message.getInt("scrollY", -1));
-        event.setMaxScrollX(message.getInt("maxScrollX", -1));
-        event.setMaxScrollY(message.getInt("maxScrollY", -1));
-    }
-
-    private void populateNodeInfoFromJSON(AccessibilityNodeInfo node, final GeckoBundle message) {
-        node.setEnabled(message.getBoolean("enabled", true));
-        node.setClickable(message.getBoolean("clickable"));
-        node.setCheckable(message.getBoolean("checkable"));
-        node.setChecked(message.getBoolean("checked"));
-        node.setPassword(message.getBoolean("password"));
-
-        final String[] textArray = message.getStringArray("text");
-        StringBuilder sb = new StringBuilder();
-        if (textArray != null && textArray.length > 0) {
-            sb.append(textArray[0] != null ? textArray[0] : "");
-            for (int i = 1; i < textArray.length; i++) {
-                sb.append(' ').append(textArray[i] != null ? textArray[i] : "");
-            }
-            node.setText(sb.toString());
-        }
-        node.setContentDescription(message.getString("description", ""));
-
-        final GeckoBundle bounds = message.getBundle("bounds");
-        if (bounds != null) {
-            Rect relativeBounds = new Rect(bounds.getInt("left"), bounds.getInt("top"),
-                                           bounds.getInt("right"), bounds.getInt("bottom"));
-            node.setBoundsInParent(relativeBounds);
-
-            final Matrix matrix = new Matrix();
-            final float[] origin = new float[2];
-            mSession.getClientToScreenMatrix(matrix);
-            matrix.mapPoints(origin);
-
-            relativeBounds.offset((int) origin[0], (int) origin[1]);
-            node.setBoundsInScreen(relativeBounds);
-        }
-
-    }
-
-    private void sendAccessibilityEvent(final GeckoBundle message) {
-        if (mView == null || !Settings.isEnabled())
-            return;
-
-        final int eventType = message.getInt("eventType", -1);
-        if (eventType < 0) {
-            Log.e(LOGTAG, "No accessibility event type provided");
-            return;
-        }
-
-        int eventSource = VIRTUAL_CONTENT_ID;
-
-        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
-            final String exitView = message.getString("exitView", "");
-
-            mLastItem = exitView.equals("moveNext");
-            if (mLastItem) {
-                return;
-            }
-
-            if (exitView.equals("movePrevious")) {
-                eventSource = View.NO_ID;
-            }
-        }
-
-        if (eventSource != View.NO_ID) {
-            // In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
-            // it work with TalkBack.
-            if (mVirtualContentNode == null) {
-                mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
-            }
-            populateNodeInfoFromJSON(mVirtualContentNode, message);
-        }
-
-        final AccessibilityEvent accessibilityEvent = obtainEvent(eventType, eventSource);
-        populateEventFromJSON(accessibilityEvent, message);
-        ((ViewParent) mView).requestSendAccessibilityEvent(mView, accessibilityEvent);
-    }
-}
deleted file mode 100644
--- a/mobile/android/modules/geckoview/GeckoViewAccessibility.jsm
+++ /dev/null
@@ -1,32 +0,0 @@
-/* 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";
-
-var EXPORTED_SYMBOLS = ["GeckoViewAccessibility"];
-
-ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetters(this, {
-  EventDispatcher: "resource://gre/modules/Messaging.jsm",
-  AccessFu: "resource://gre/modules/accessibility/AccessFu.jsm"
-});
-
-XPCOMUtils.defineLazyGetter(this, "dump", () =>
-    ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
-                       {}).AndroidLog.d.bind(null, "GeckoAccessibility"));
-
-class GeckoViewAccessibility extends GeckoViewModule {
-  init() {
-    EventDispatcher.instance.dispatch("GeckoView:AccessibilityReady");
-    EventDispatcher.instance.registerListener((aEvent, aData, aCallback) => {
-      if (aData.enabled) {
-        AccessFu.attach(this.window);
-      } else {
-        AccessFu.detach();
-      }
-    }, "GeckoView:AccessibilitySettings");
-  }
-}
--- a/mobile/android/modules/geckoview/moz.build
+++ b/mobile/android/modules/geckoview/moz.build
@@ -1,17 +1,16 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_JS_MODULES += [
     'AndroidLog.jsm',
-    'GeckoViewAccessibility.jsm',
     'GeckoViewContent.jsm',
     'GeckoViewContentModule.jsm',
     'GeckoViewModule.jsm',
     'GeckoViewNavigation.jsm',
     'GeckoViewProgress.jsm',
     'GeckoViewRemoteDebugger.jsm',
     'GeckoViewScroll.jsm',
     'GeckoViewSelectionAction.jsm',
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java
@@ -0,0 +1,147 @@
+/* -*- Mode: Java; c-basic-offset: 4; 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/. */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+/**
+ * Interface for a client to control braille output for a part of the
+ * accessibility node tree.
+ */
+public interface ISelfBrailleService extends android.os.IInterface {
+    /** Local-side IPC implementation stub class. */
+    public static abstract class Stub extends android.os.Binder implements
+            com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService {
+        private static final java.lang.String DESCRIPTOR = "com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService";
+
+        /** Construct the stub at attach it to the interface. */
+        public Stub() {
+            this.attachInterface(this, DESCRIPTOR);
+        }
+
+        /**
+         * Cast an IBinder object into an
+         * com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService
+         * interface, generating a proxy if needed.
+         */
+        public static com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService asInterface(
+                android.os.IBinder obj) {
+            if ((obj == null)) {
+                return null;
+            }
+            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+            if (((iin != null) && (iin instanceof com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService))) {
+                return ((com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService) iin);
+            }
+            return new com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService.Stub.Proxy(
+                    obj);
+        }
+
+        @Override
+        public android.os.IBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public boolean onTransact(int code, android.os.Parcel data,
+                android.os.Parcel reply, int flags)
+                throws android.os.RemoteException {
+            switch (code) {
+            case INTERFACE_TRANSACTION: {
+                reply.writeString(DESCRIPTOR);
+                return true;
+            }
+            case TRANSACTION_write: {
+                data.enforceInterface(DESCRIPTOR);
+                android.os.IBinder _arg0;
+                _arg0 = data.readStrongBinder();
+                com.googlecode.eyesfree.braille.selfbraille.WriteData _arg1;
+                if ((0 != data.readInt())) {
+                    _arg1 = com.googlecode.eyesfree.braille.selfbraille.WriteData.CREATOR
+                            .createFromParcel(data);
+                } else {
+                    _arg1 = null;
+                }
+                this.write(_arg0, _arg1);
+                reply.writeNoException();
+                return true;
+            }
+            case TRANSACTION_disconnect: {
+                data.enforceInterface(DESCRIPTOR);
+                android.os.IBinder _arg0;
+                _arg0 = data.readStrongBinder();
+                this.disconnect(_arg0);
+                return true;
+            }
+            }
+            return super.onTransact(code, data, reply, flags);
+        }
+
+        private static class Proxy implements
+                com.googlecode.eyesfree.braille.selfbraille.ISelfBrailleService {
+            private android.os.IBinder mRemote;
+
+            Proxy(android.os.IBinder remote) {
+                mRemote = remote;
+            }
+
+            @Override
+            public android.os.IBinder asBinder() {
+                return mRemote;
+            }
+
+            public java.lang.String getInterfaceDescriptor() {
+                return DESCRIPTOR;
+            }
+
+            @Override
+            public void write(
+                    android.os.IBinder clientToken,
+                    com.googlecode.eyesfree.braille.selfbraille.WriteData writeData)
+                    throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                android.os.Parcel _reply = android.os.Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeStrongBinder(clientToken);
+                    if ((writeData != null)) {
+                        _data.writeInt(1);
+                        writeData.writeToParcel(_data, 0);
+                    } else {
+                        _data.writeInt(0);
+                    }
+                    mRemote.transact(Stub.TRANSACTION_write, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void disconnect(android.os.IBinder clientToken)
+                    throws android.os.RemoteException {
+                android.os.Parcel _data = android.os.Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeStrongBinder(clientToken);
+                    mRemote.transact(Stub.TRANSACTION_disconnect, _data, null,
+                            android.os.IBinder.FLAG_ONEWAY);
+                } finally {
+                    _data.recycle();
+                }
+            }
+        }
+
+        static final int TRANSACTION_write = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+        static final int TRANSACTION_disconnect = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+    }
+
+    public void write(android.os.IBinder clientToken,
+            com.googlecode.eyesfree.braille.selfbraille.WriteData writeData)
+            throws android.os.RemoteException;
+
+    public void disconnect(android.os.IBinder clientToken)
+            throws android.os.RemoteException;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Client-side interface to the self brailling interface.
+ *
+ * Threading: Instances of this object should be created and shut down
+ * in a thread with a {@link Looper} associated with it.  Other methods may
+ * be called on any thread.
+ */
+public class SelfBrailleClient {
+    private static final String LOG_TAG =
+            SelfBrailleClient.class.getSimpleName();
+    private static final String ACTION_SELF_BRAILLE_SERVICE =
+            "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE";
+    private static final String BRAILLE_BACK_PACKAGE =
+            "com.googlecode.eyesfree.brailleback";
+    private static final Intent mServiceIntent =
+            new Intent(ACTION_SELF_BRAILLE_SERVICE)
+            .setPackage(BRAILLE_BACK_PACKAGE);
+    /**
+     * SHA-1 hash value of the Eyes-Free release key certificate, used to sign
+     * BrailleBack.  It was generated from the keystore with:
+     * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \
+     *   > cert
+     * $ keytool -printcert -file cert
+     */
+    // The typecasts are to silence a compiler warning about loss of precision
+    private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] {
+        (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D,
+        (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4,
+        (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B,
+        (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76,
+        (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61
+    };
+    /**
+     * Delay before the first rebind attempt on bind error or service
+     * disconnect.
+     */
+    private static final int REBIND_DELAY_MILLIS = 500;
+    private static final int MAX_REBIND_ATTEMPTS = 5;
+
+    private final Binder mIdentity = new Binder();
+    private final Context mContext;
+    private final boolean mAllowDebugService;
+    private final SelfBrailleHandler mHandler = new SelfBrailleHandler();
+    private boolean mShutdown = false;
+
+    /**
+     * Written in handler thread, read in any thread calling methods on the
+     * object.
+     */
+    private volatile Connection mConnection;
+    /** Protected by synchronizing on mHandler. */
+    private int mNumFailedBinds = 0;
+
+    /**
+     * Constructs an instance of this class.  {@code context} is used to bind
+     * to the self braille service.  The current thread must have a Looper
+     * associated with it.  If {@code allowDebugService} is true, this instance
+     * will connect to a BrailleBack service without requiring it to be signed
+     * by the release key used to sign BrailleBack.
+     */
+    public SelfBrailleClient(Context context, boolean allowDebugService) {
+        mContext = context;
+        mAllowDebugService = allowDebugService;
+        doBindService();
+    }
+
+    /**
+     * Shuts this instance down, deallocating any global resources it is using.
+     * This method must be called on the same thread that created this object.
+     */
+    public void shutdown() {
+        mShutdown = true;
+        doUnbindService();
+    }
+
+    public void write(WriteData writeData) {
+        writeData.validate();
+        ISelfBrailleService localService = getSelfBrailleService();
+        if (localService != null) {
+            try {
+                localService.write(mIdentity, writeData);
+            } catch (RemoteException ex) {
+                Log.e(LOG_TAG, "Self braille write failed", ex);
+            }
+        }
+    }
+
+    private void doBindService() {
+        Connection localConnection = new Connection();
+        if (!mContext.bindService(mServiceIntent, localConnection,
+                Context.BIND_AUTO_CREATE)) {
+            Log.e(LOG_TAG, "Failed to bind to service");
+            mHandler.scheduleRebind();
+            return;
+        }
+        mConnection = localConnection;
+        Log.i(LOG_TAG, "Bound to self braille service");
+    }
+
+    private void doUnbindService() {
+        if (mConnection != null) {
+            ISelfBrailleService localService = getSelfBrailleService();
+            if (localService != null) {
+                try {
+                    localService.disconnect(mIdentity);
+                } catch (RemoteException ex) {
+                    // Nothing to do.
+                }
+            }
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    private ISelfBrailleService getSelfBrailleService() {
+        Connection localConnection = mConnection;
+        if (localConnection != null) {
+            return localConnection.mService;
+        }
+        return null;
+    }
+
+    private boolean verifyPackage() {
+        PackageManager pm = mContext.getPackageManager();
+        PackageInfo pi;
+        try {
+            pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE,
+                    PackageManager.GET_SIGNATURES);
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE,
+                    ex);
+            return false;
+        }
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException ex) {
+            Log.e(LOG_TAG, "SHA-1 not supported", ex);
+            return false;
+        }
+        // Check if any of the certificates match our hash.
+        for (Signature signature : pi.signatures) {
+            digest.update(signature.toByteArray());
+            if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) {
+                return true;
+            }
+            digest.reset();
+        }
+        if (mAllowDebugService) {
+            Log.w(LOG_TAG, String.format(
+                "*** %s connected to BrailleBack with invalid (debug?) "
+                + "signature ***",
+                mContext.getPackageName()));
+            return true;
+        }
+        return false;
+    }
+    private class Connection implements ServiceConnection {
+        // Read in application threads, written in main thread.
+        private volatile ISelfBrailleService mService;
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder binder) {
+            if (!verifyPackage()) {
+                Log.w(LOG_TAG, String.format("Service certificate mismatch "
+                                + "for %s, dropping connection",
+                                BRAILLE_BACK_PACKAGE));
+                mHandler.unbindService();
+                return;
+            }
+            Log.i(LOG_TAG, "Connected to self braille service");
+            mService = ISelfBrailleService.Stub.asInterface(binder);
+            synchronized (mHandler) {
+                mNumFailedBinds = 0;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            Log.e(LOG_TAG, "Disconnected from self braille service");
+            mService = null;
+            // Retry by rebinding.
+            mHandler.scheduleRebind();
+        }
+    }
+
+    private class SelfBrailleHandler extends Handler {
+        private static final int MSG_REBIND_SERVICE = 1;
+        private static final int MSG_UNBIND_SERVICE = 2;
+
+        public void scheduleRebind() {
+            synchronized (this) {
+                if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+                    int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+                    sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+                    ++mNumFailedBinds;
+                }
+            }
+        }
+
+        public void unbindService() {
+            sendEmptyMessage(MSG_UNBIND_SERVICE);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REBIND_SERVICE:
+                    handleRebindService();
+                    break;
+                case MSG_UNBIND_SERVICE:
+                    handleUnbindService();
+                    break;
+            }
+        }
+
+        private void handleRebindService() {
+            if (mShutdown) {
+                return;
+            }
+            if (mConnection != null) {
+                doUnbindService();
+            }
+            doBindService();
+        }
+
+        private void handleUnbindService() {
+            doUnbindService();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Represents what should be shown on the braille display for a
+ * part of the accessibility node tree.
+ */
+public class WriteData implements Parcelable {
+
+    private static final String PROP_SELECTION_START = "selectionStart";
+    private static final String PROP_SELECTION_END = "selectionEnd";
+
+    private AccessibilityNodeInfo mAccessibilityNodeInfo;
+    private CharSequence mText;
+    private Bundle mProperties = Bundle.EMPTY;
+
+    /**
+     * Returns a new {@link WriteData} instance for the given {@code view}.
+     */
+    public static WriteData forView(View view) {
+        AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
+        WriteData writeData = new WriteData();
+        writeData.mAccessibilityNodeInfo = node;
+        return writeData;
+    }
+
+    public static WriteData forInfo(AccessibilityNodeInfo info){
+        WriteData writeData = new WriteData();
+        writeData.mAccessibilityNodeInfo = info;
+        return writeData;
+    }
+
+
+    public AccessibilityNodeInfo getAccessibilityNodeInfo() {
+        return mAccessibilityNodeInfo;
+    }
+
+    /**
+     * Sets the text to be displayed when the accessibility node associated
+     * with this instance has focus.  If this method is not called (or
+     * {@code text} is {@code null}), this client relinquishes control over
+     * this node.
+     */
+    public WriteData setText(CharSequence text) {
+        mText = text;
+        return this;
+    }
+
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Sets the start position in the text of a text selection or cursor that
+     * should be marked on the display.  A negative value (the default) means
+     * no selection will be added.
+     */
+    public WriteData setSelectionStart(int v) {
+        writableProperties().putInt(PROP_SELECTION_START, v);
+        return this;
+    }
+
+    /**
+     * @see {@link #setSelectionStart}.
+     */
+    public int getSelectionStart() {
+        return mProperties.getInt(PROP_SELECTION_START, -1);
+    }
+
+    /**
+     * Sets the end of the text selection to be marked on the display.  This
+     * value should only be non-negative if the selection start is
+     * non-negative.  If this value is <= the selection start, the selection
+     * is a cursor.  Otherwise, the selection covers the range from
+     * start(inclusive) to end (exclusive).
+     *
+     * @see {@link android.text.Selection}.
+     */
+    public WriteData setSelectionEnd(int v) {
+        writableProperties().putInt(PROP_SELECTION_END, v);
+        return this;
+    }
+
+    /**
+     * @see {@link #setSelectionEnd}.
+     */
+    public int getSelectionEnd() {
+        return mProperties.getInt(PROP_SELECTION_END, -1);
+    }
+
+    private Bundle writableProperties() {
+        if (mProperties == Bundle.EMPTY) {
+            mProperties = new Bundle();
+        }
+        return mProperties;
+    }
+
+    /**
+     * Checks constraints on the fields that must be satisfied before sending
+     * this instance to the self braille service.
+     * @throws IllegalStateException
+     */
+    public void validate() throws IllegalStateException {
+        if (mAccessibilityNodeInfo == null) {
+            throw new IllegalStateException(
+                "Accessibility node info can't be null");
+        }
+        int selectionStart = getSelectionStart();
+        int selectionEnd = getSelectionEnd();
+        if (mText == null) {
+            if (selectionStart > 0 || selectionEnd > 0) {
+                throw new IllegalStateException(
+                    "Selection can't be set without text");
+            }
+        } else {
+            if (selectionStart < 0 && selectionEnd >= 0) {
+                throw new IllegalStateException(
+                    "Selection end without start");
+            }
+            int textLength = mText.length();
+            if (selectionStart > textLength || selectionEnd > textLength) {
+                throw new IllegalStateException("Selection out of bounds");
+            }
+        }
+    }
+
+    // For Parcelable support.
+
+    public static final Parcelable.Creator<WriteData> CREATOR =
+        new Parcelable.Creator<WriteData>() {
+            @Override
+            public WriteData createFromParcel(Parcel in) {
+                return new WriteData(in);
+            }
+
+            @Override
+            public WriteData[] newArray(int size) {
+                return new WriteData[size];
+            }
+        };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be
+     * recycled by this method, don't try to use this more than once.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        mAccessibilityNodeInfo.writeToParcel(out, flags);
+        // The above call recycles the node, so make sure we don't use it
+        // anymore.
+        mAccessibilityNodeInfo = null;
+        out.writeString(mText.toString());
+        out.writeBundle(mProperties);
+    }
+
+    private WriteData() {
+    }
+
+    private WriteData(Parcel in) {
+        mAccessibilityNodeInfo =
+                AccessibilityNodeInfo.CREATOR.createFromParcel(in);
+        mText = in.readString();
+        mProperties = in.readBundle();
+    }
+}