Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 10 Oct 2018 12:31:21 -0400
changeset 499013 3c2fc3b5c03adc291583cb49e346e7580f283c0f
parent 499012 c2dd39bde2cc1b5bbbd48a43a7ba616152159777 (current diff)
parent 498965 1a9cbc785296806682eb165e0307b05b8f45b7e7 (diff)
child 499014 5899ae8c1e091aed4697af62b98a621118e1059d
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
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
Merge m-c to inbound. a=merge
Pipfile
Pipfile.lock
accessible/android/DocAccessibleWrap.cpp
accessible/android/ProxyAccessibleWrap.cpp
accessible/android/ProxyAccessibleWrap.h
accessible/android/RootAccessibleWrap.cpp
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/DESCRIPTION.rst
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/METADATA
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/RECORD
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/WHEEL
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/metadata.json
third_party/python/psutil-cp27-none-win_amd64/psutil-5.4.3.dist-info/top_level.txt
third_party/python/psutil-cp27-none-win_amd64/psutil/__init__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_common.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_compat.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_exceptions.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psaix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psbsd.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_pslinux.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psosx.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psposix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_pssunos.py
third_party/python/psutil-cp27-none-win_amd64/psutil/_psutil_windows.pyd
third_party/python/psutil-cp27-none-win_amd64/psutil/_pswindows.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/__init__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/__main__.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_aix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_bsd.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_connections.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_contracts.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_linux.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_memory_leaks.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_misc.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_osx.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_posix.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_process.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_sunos.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_system.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_unicode.py
third_party/python/psutil-cp27-none-win_amd64/psutil/tests/test_windows.py
third_party/python/psutil/.coveragerc
third_party/python/psutil/.git-pre-commit
third_party/python/psutil/.gitignore
third_party/python/psutil/psutil/_psutil_aix.c
third_party/python/psutil/psutil/tests/__main__.py
third_party/python/psutil/psutil/tests/test_aix.py
third_party/python/psutil/psutil/tests/test_bsd.py
third_party/python/psutil/psutil/tests/test_connections.py
third_party/python/psutil/psutil/tests/test_contracts.py
third_party/python/psutil/psutil/tests/test_linux.py
third_party/python/psutil/psutil/tests/test_memory_leaks.py
third_party/python/psutil/psutil/tests/test_misc.py
third_party/python/psutil/psutil/tests/test_osx.py
third_party/python/psutil/psutil/tests/test_posix.py
third_party/python/psutil/psutil/tests/test_process.py
third_party/python/psutil/psutil/tests/test_sunos.py
third_party/python/psutil/psutil/tests/test_system.py
third_party/python/psutil/psutil/tests/test_unicode.py
third_party/python/psutil/psutil/tests/test_windows.py
third_party/python/psutil/scripts/battery.py
third_party/python/psutil/scripts/cpu_distribution.py
third_party/python/psutil/scripts/disk_usage.py
third_party/python/psutil/scripts/fans.py
third_party/python/psutil/scripts/free.py
third_party/python/psutil/scripts/ifconfig.py
third_party/python/psutil/scripts/internal/bench_oneshot.py
third_party/python/psutil/scripts/internal/check_broken_links.py
third_party/python/psutil/scripts/internal/download_exes.py
third_party/python/psutil/scripts/internal/generate_manifest.py
third_party/python/psutil/scripts/internal/print_announce.py
third_party/python/psutil/scripts/internal/winmake.py
third_party/python/psutil/scripts/iotop.py
third_party/python/psutil/scripts/killall.py
third_party/python/psutil/scripts/meminfo.py
third_party/python/psutil/scripts/netstat.py
third_party/python/psutil/scripts/nettop.py
third_party/python/psutil/scripts/pidof.py
third_party/python/psutil/scripts/pmap.py
third_party/python/psutil/scripts/procinfo.py
third_party/python/psutil/scripts/procsmem.py
third_party/python/psutil/scripts/ps.py
third_party/python/psutil/scripts/pstree.py
third_party/python/psutil/scripts/sensors.py
third_party/python/psutil/scripts/temperatures.py
third_party/python/psutil/scripts/top.py
third_party/python/psutil/scripts/who.py
third_party/python/psutil/scripts/winservices.py
widget/android/bindings/AccessibilityEvent-classes.txt
deleted file mode 100644
--- a/Pipfile
+++ /dev/null
@@ -1,21 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[dev-packages]
-
-[packages]
-attrs = "==18.1.0"
-blessings = "==1.7"
-jsmin = "==2.1.0"
-json-e = "==2.7.0"
-pip-tools = "==3.0.0"
-pipenv = "==2018.5.18"
-pytest = "==3.6.2"
-python-hglib = "==2.4"
-requests = "==2.9.1"
-six = "==1.10.0"
-virtualenv = "==15.2.0"
-voluptuous = "==0.11.5"
-psutil = "==5.4.3"
deleted file mode 100644
--- a/Pipfile.lock
+++ /dev/null
@@ -1,187 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "e756c316803705f9230eb8dd3b53fd9a9aa0a146c7387e3caffb668e0f7ea223"
-        },
-        "pipfile-spec": 6,
-        "requires": {},
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "atomicwrites": {
-            "hashes": [
-                "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585",
-                "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6"
-            ],
-            "version": "==1.1.5"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
-                "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
-            ],
-            "index": "pypi",
-            "version": "==18.1.0"
-        },
-        "blessings": {
-            "hashes": [
-                "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d",
-                "sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3",
-                "sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"
-            ],
-            "index": "pypi",
-            "version": "==1.7"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:4c1d68a1408dd090d2f3a869aa94c3947cc1d967821d1ed303208c9f41f0f2f4",
-                "sha256:b6e8b28b2b7e771a41ecdd12d4d43262ecab52adebbafa42c77d6b57fb6ad3a4"
-            ],
-            "version": "==2018.8.13"
-        },
-        "click": {
-            "hashes": [
-                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
-                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
-            ],
-            "version": "==7.0"
-        },
-        "funcsigs": {
-            "hashes": [
-                "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
-                "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
-            ],
-            "markers": "python_version < '3.0'",
-            "version": "==1.0.2"
-        },
-        "jsmin": {
-            "hashes": [
-                "sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56"
-            ],
-            "index": "pypi",
-            "version": "==2.1.0"
-        },
-        "json-e": {
-            "hashes": [
-                "sha256:f9114a25ed4b575395fbb2daa1183c5b781a647b387fdf28596220bb114673e8"
-            ],
-            "index": "pypi",
-            "version": "==2.5.0"
-        },
-        "more-itertools": {
-            "hashes": [
-                "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
-                "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
-                "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
-            ],
-            "version": "==4.3.0"
-        },
-        "pip-tools": {
-            "hashes": [
-                "sha256:4a94997602848f77ff02f660c0fcdfeaf316924ebb236c865f9742ce212aa6f9",
-                "sha256:e45e5198ce3799068642ebb0e7c9be5520bcff944c0186f79c1199a2759c970a"
-            ],
-            "index": "pypi",
-            "version": "==3.0.0"
-        },
-        "pipenv": {
-            "hashes": [
-                "sha256:04b9a8b02a3ff12a5502b335850cfdb192adcfd1d6bbdb7a7c47cae9ab9ddece",
-                "sha256:e96d5bfa6822a17b2200d455aa5f9002c14361c50df1b1e51921479d7c09e741"
-            ],
-            "index": "pypi",
-            "version": "==2018.5.18"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
-                "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
-                "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
-            ],
-            "version": "==0.6.0"
-        },
-        "psutil": {
-            "hashes": [
-                "sha256:230eeb3aeb077814f3a2cd036ddb6e0f571960d327298cc914c02385c3e02a63",
-                "sha256:4152ae231709e3e8b80e26b6da20dc965a1a589959c48af1ed024eca6473f60d",
-                "sha256:779ec7e7621758ca11a8d99a1064996454b3570154277cc21342a01148a49c28",
-                "sha256:82a06785db8eeb637b349006cc28a92e40cd190fefae9875246d18d0de7ccac8",
-                "sha256:8a15d773203a1277e57b1d11a7ccdf70804744ef4a9518a87ab8436995c31a4b",
-                "sha256:94d4e63189f2593960e73acaaf96be235dd8a455fe2bcb37d8ad6f0e87f61556",
-                "sha256:a3286556d4d2f341108db65d8e20d0cd3fcb9a91741cb5eb496832d7daf2a97c",
-                "sha256:c91eee73eea00df5e62c741b380b7e5b6fdd553891bee5669817a3a38d036f13",
-                "sha256:e2467e9312c2fa191687b89ff4bc2ad8843be4af6fb4dc95a7cc5f7d7a327b18"
-            ],
-            "index": "pypi",
-            "version": "==5.4.3"
-        },
-        "py": {
-            "hashes": [
-                "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
-                "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
-            ],
-            "version": "==1.5.4"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:8ea01fc4fcc8e1b1e305252b4bc80a1528019ab99fd3b88666c9dc38d754406c",
-                "sha256:90898786b3d0b880b47645bae7b51aa9bbf1e9d1e4510c2cfd15dd65c70ea0cd"
-            ],
-            "index": "pypi",
-            "version": "==3.6.2"
-        },
-        "python-hglib": {
-            "hashes": [
-                "sha256:693d6ed92a6566e78802c7a03c256cda33d08c63ad3f00fcfa11379b184b9462"
-            ],
-            "index": "pypi",
-            "version": "==2.4"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:113fbba5531a9e34945b7d36b33a084e8ba5d0664b703c81a7c572d91919a5b8",
-                "sha256:c577815dd00f1394203fc44eb979724b098f88264a9ef898ee45b8e5e9cf587f"
-            ],
-            "index": "pypi",
-            "version": "==2.9.1"
-        },
-        "six": {
-            "hashes": [
-                "sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1",
-                "sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a"
-            ],
-            "index": "pypi",
-            "version": "==1.10.0"
-        },
-        "virtualenv": {
-            "hashes": [
-                "sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54",
-                "sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f"
-            ],
-            "index": "pypi",
-            "version": "==15.2.0"
-        },
-        "virtualenv-clone": {
-            "hashes": [
-                "sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7",
-                "sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8"
-            ],
-            "version": "==0.3.0"
-        },
-        "voluptuous": {
-            "hashes": [
-                "sha256:303542b3fc07fb52ec3d7a1c614b329cdbee13a9d681935353d8ea56a7bfa9f1",
-                "sha256:567a56286ef82a9d7ae0628c5842f65f516abcb496e74f3f59f1d7b28df314ef"
-            ],
-            "index": "pypi",
-            "version": "==0.11.5"
-        }
-    },
-    "develop": {}
-}
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -1,509 +1,23 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AccessibleWrap.h"
 
-#include "Accessible-inl.h"
-#include "DocAccessibleWrap.h"
-#include "IDSet.h"
-#include "JavaBuiltins.h"
-#include "SessionAccessibility.h"
-#include "nsAccessibilityService.h"
-#include "nsIPersistentProperties2.h"
-#include "nsIStringBundle.h"
-#include "nsAccUtils.h"
-
-#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties"
-
 using namespace mozilla::a11y;
 
-// IDs should be a positive 32bit integer.
-IDSet sIDSet(31UL);
-
 //-----------------------------------------------------
 // construction
 //-----------------------------------------------------
 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
   : Accessible(aContent, aDoc)
 {
-  if (aDoc) {
-    mID = AcquireID();
-    DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
-    doc->AddID(mID, this);
-  }
 }
 
 //-----------------------------------------------------
 // destruction
 //-----------------------------------------------------
-AccessibleWrap::~AccessibleWrap() {}
-
-nsresult
-AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
-{
-  nsresult rv = Accessible::HandleAccEvent(aEvent);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (IPCAccessibilityActive()) {
-    return NS_OK;
-  }
-
-  auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
-  NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
-
-  // The accessible can become defunct if we have an xpcom event listener
-  // which decides it would be fun to change the DOM and flush layout.
-  if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
-    return NS_OK;
-  }
-
-  if (DocAccessible* doc = accessible->Document()) {
-    if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
-      return NS_OK;
-    }
-  }
-
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(accessible);
-  if (!sessionAcc) {
-    return NS_OK;
-  }
-
-  switch (aEvent->GetEventType()) {
-    case nsIAccessibleEvent::EVENT_FOCUS:
-      sessionAcc->SendFocusEvent(accessible);
-      break;
-    case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
-      AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
-      auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
-      auto oldPosition = static_cast<AccessibleWrap*>(vcEvent->OldAccessible());
-
-      if (sessionAcc && newPosition) {
-        if (oldPosition != newPosition) {
-          if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
-            sessionAcc->SendHoverEnterEvent(newPosition);
-          } else {
-            sessionAcc->SendAccessibilityFocusedEvent(newPosition);
-          }
-        }
-
-        if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
-          sessionAcc->SendTextTraversedEvent(
-            newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
-        }
-      }
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
-      AccCaretMoveEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendTextSelectionChangedEvent(accessible,
-                                                event->GetCaretOffset());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
-    case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
-      AccTextChangeEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendTextChangedEvent(accessible,
-                                       event->ModifiedText(),
-                                       event->GetStartOffset(),
-                                       event->GetLength(),
-                                       event->IsTextInserted(),
-                                       event->IsFromUserInput());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
-      AccStateChangeEvent* event = downcast_accEvent(aEvent);
-      auto state = event->GetState();
-      if (state & states::CHECKED) {
-        sessionAcc->SendClickedEvent(accessible);
-      }
-
-      if (state & states::SELECTED) {
-        sessionAcc->SendSelectedEvent(accessible);
-      }
-
-      if (state & states::BUSY) {
-        sessionAcc->SendWindowStateChangedEvent(accessible);
-      }
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_SCROLLING: {
-      AccScrollingEvent* event = downcast_accEvent(aEvent);
-      sessionAcc->SendScrollingEvent(accessible,
-                                     event->ScrollX(),
-                                     event->ScrollY(),
-                                     event->MaxScrollX(),
-                                     event->MaxScrollY());
-      break;
-    }
-    case nsIAccessibleEvent::EVENT_SHOW:
-    case nsIAccessibleEvent::EVENT_HIDE: {
-      AccMutationEvent* event = downcast_accEvent(aEvent);
-      auto parent = static_cast<AccessibleWrap*>(event->Parent());
-      sessionAcc->SendWindowContentChangedEvent(parent);
-      break;
-    }
-    default:
-      break;
-  }
-
-  return NS_OK;
-}
-
-void
-AccessibleWrap::Shutdown()
-{
-  if (mDoc) {
-    if (mID > 0) {
-      if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) {
-        doc->RemoveID(mID);
-      }
-      ReleaseID(mID);
-      mID = 0;
-    }
-  }
-
-  Accessible::Shutdown();
-}
-
-int32_t
-AccessibleWrap::AcquireID()
-{
-  return sIDSet.GetID();
-}
-
-void
-AccessibleWrap::ReleaseID(int32_t aID)
-{
-  sIDSet.ReleaseID(aID);
-}
-
-void
-AccessibleWrap::SetTextContents(const nsAString& aText) {
-  if (IsHyperText()) {
-    AsHyperText()->ReplaceText(aText);
-  }
-}
-
-void
-AccessibleWrap::GetTextContents(nsAString& aText) {
-  // For now it is a simple wrapper for getting entire range of TextSubstring.
-  // In the future this may be smarter and retrieve a flattened string.
-  if (IsHyperText()) {
-    AsHyperText()->TextSubstring(0, -1, aText);
-  }
-}
-
-bool
-AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) {
-  if (IsHyperText()) {
-    return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
-  }
-
-  return false;
-}
-
-mozilla::java::GeckoBundle::LocalRef
-AccessibleWrap::CreateBundle(int32_t aParentID,
-                             role aRole,
-                             uint64_t aState,
-                             const nsString& aName,
-                             const nsString& aTextValue,
-                             const nsString& aDOMNodeID,
-                             const nsIntRect& aBounds,
-                             double aCurVal,
-                             double aMinVal,
-                             double aMaxVal,
-                             double aStep,
-                             nsIPersistentProperties* aAttributes,
-                             const nsTArray<int32_t>& aChildren) const
+AccessibleWrap::~AccessibleWrap()
 {
-  GECKOBUNDLE_START(nodeInfo);
-  GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
-  GECKOBUNDLE_PUT(nodeInfo, "parentId", java::sdk::Integer::ValueOf(aParentID));
-  uint64_t flags = GetFlags(aRole, aState);
-  GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
-
-  nsAutoString geckoRole;
-  nsAutoString roleDescription;
-  nsAutoString className;
-  GetAndroidRoleAndClass(aRole, geckoRole, roleDescription, className);
-  if (VirtualViewID() == kNoID) {
-    className.AssignLiteral("android.webkit.WebView");
-    roleDescription.AssignLiteral("");
-  }
-  GECKOBUNDLE_PUT(
-    nodeInfo, "roleDescription", jni::StringParam(roleDescription));
-  GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
-  GECKOBUNDLE_PUT(nodeInfo, "className", jni::StringParam(className));
-
-  if (!aTextValue.IsEmpty() &&
-      (flags & java::SessionAccessibility::FLAG_EDITABLE)) {
-    GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(aName));
-    GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
-  } else {
-    GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
-  }
-
-  if (!aDOMNodeID.IsEmpty()) {
-    GECKOBUNDLE_PUT(
-      nodeInfo, "viewIdResourceName", jni::StringParam(aDOMNodeID));
-  }
-
-  const int32_t data[4] = {
-    aBounds.x, aBounds.y, aBounds.x + aBounds.width, aBounds.y + aBounds.height
-  };
-  GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
-
-  if (HasNumericValue()) {
-    GECKOBUNDLE_START(rangeInfo);
-    if (aMaxVal == 1 && aMinVal == 0) {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(2)); // percent
-    } else if (std::round(aStep) != aStep) {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(1)); // float
-    } else {
-      GECKOBUNDLE_PUT(
-        rangeInfo, "type", java::sdk::Integer::ValueOf(0)); // integer
-    }
-
-    if (!IsNaN(aCurVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal));
-    }
-    if (!IsNaN(aMinVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal));
-    }
-    if (!IsNaN(aMaxVal)) {
-      GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
-    }
-
-    GECKOBUNDLE_FINISH(rangeInfo);
-    GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
-  }
-
-  nsString inputType;
-  nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputType);
-  if (!inputType.IsEmpty()) {
-    GECKOBUNDLE_PUT(nodeInfo, "inputType", jni::StringParam(inputType));
-  }
-
-  nsString posinset;
-  nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"), posinset);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowIndex;
-    if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
-      GECKOBUNDLE_START(collectionItemInfo);
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "rowIndex", java::sdk::Integer::ValueOf(rowIndex));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "columnIndex", java::sdk::Integer::ValueOf(0));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "rowSpan", java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_PUT(
-        collectionItemInfo, "columnSpan", java::sdk::Integer::ValueOf(1));
-      GECKOBUNDLE_FINISH(collectionItemInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
-    }
-  }
-
-  nsString colSize;
-  rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
-                                      colSize);
-  if (NS_SUCCEEDED(rv)) {
-    int32_t rowCount;
-    if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
-      GECKOBUNDLE_START(collectionInfo);
-      GECKOBUNDLE_PUT(
-        collectionInfo, "rowCount", java::sdk::Integer::ValueOf(rowCount));
-      GECKOBUNDLE_PUT(
-        collectionInfo, "columnCount", java::sdk::Integer::ValueOf(1));
-
-      nsString unused;
-      rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
-                                          unused);
-      if (NS_SUCCEEDED(rv)) {
-        GECKOBUNDLE_PUT(
-          collectionInfo, "isHierarchical", java::sdk::Boolean::TRUE());
-      }
-
-      if (IsSelect()) {
-        int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
-        GECKOBUNDLE_PUT(collectionInfo,
-                        "selectionMode",
-                        java::sdk::Integer::ValueOf(selectionMode));
-      }
-      GECKOBUNDLE_FINISH(collectionInfo);
-
-      GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
-    }
-  }
-
-  GECKOBUNDLE_PUT(nodeInfo,
-                  "children",
-                  jni::IntArray::New(aChildren.Elements(), aChildren.Length()));
-  GECKOBUNDLE_FINISH(nodeInfo);
-
-  return nodeInfo;
 }
-
-uint64_t
-AccessibleWrap::GetFlags(role aRole, uint64_t aState)
-{
-  uint64_t flags = 0;
-  if (aState & states::CHECKABLE) {
-    flags |= java::SessionAccessibility::FLAG_CHECKABLE;
-  }
-
-  if (aState & states::CHECKED) {
-    flags |= java::SessionAccessibility::FLAG_CHECKED;
-  }
-
-  if (aState & states::INVALID) {
-    flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
-  }
-
-  if (aState & states::EDITABLE) {
-    flags |= java::SessionAccessibility::FLAG_EDITABLE;
-  }
-
-  if (aState & states::SENSITIVE) {
-    flags |= java::SessionAccessibility::FLAG_CLICKABLE;
-  }
-
-  if (aState & states::ENABLED) {
-    flags |= java::SessionAccessibility::FLAG_ENABLED;
-  }
-
-  if (aState & states::FOCUSABLE) {
-    flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
-  }
-
-  if (aState & states::FOCUSED) {
-    flags |= java::SessionAccessibility::FLAG_FOCUSED;
-  }
-
-  if (aState & states::MULTI_LINE) {
-    flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
-  }
-
-  if (aState & states::SELECTABLE) {
-    flags |= java::SessionAccessibility::FLAG_SELECTABLE;
-  }
-
-  if (aState & states::SELECTED) {
-    flags |= java::SessionAccessibility::FLAG_SELECTED;
-  }
-
-  if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
-    flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
-  }
-
-  if (aRole == roles::PASSWORD_TEXT) {
-    flags |= java::SessionAccessibility::FLAG_PASSWORD;
-  }
-
-  return flags;
-}
-
-void
-AccessibleWrap::GetAndroidRoleAndClass(role aRole,
-                                       nsAString& aGeckoRole,
-                                       nsAString& aRoleDescription,
-                                       nsAString& aClassStr)
-{
-  nsresult rv = NS_OK;
-
-  nsCOMPtr<nsIStringBundleService> sbs =
-    do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to get string bundle service");
-    return;
-  }
-
-  nsCOMPtr<nsIStringBundle> bundle;
-  rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to get string bundle");
-    return;
-  }
-
-#define ROLE(geckoRole,                                                        \
-             stringRole,                                                       \
-             atkRole,                                                          \
-             macRole,                                                          \
-             msaaRole,                                                         \
-             ia2Role,                                                          \
-             androidClass,                                                     \
-             nameRule)                                                         \
-  case roles::geckoRole:                                                       \
-    rv = bundle->GetStringFromName(stringRole, aRoleDescription);              \
-    if (NS_FAILED(rv))                                                         \
-      aRoleDescription.AssignLiteral("");                                      \
-    aGeckoRole.AssignLiteral(stringRole);                                      \
-    aClassStr.AssignLiteral(androidClass);                                     \
-    break;
-
-  switch (aRole) {
-#include "RoleMap.h"
-    default:
-      aRoleDescription.AssignLiteral("");
-      aGeckoRole.AssignLiteral("nothing");
-      aClassStr.AssignLiteral("android.view.View");
-      return;
-  }
-
-#undef ROLE
-}
-
-void
-AccessibleWrap::DOMNodeID(nsString& aDOMNodeID)
-{
-  if (mContent) {
-    nsAtom* id = mContent->GetID();
-    if (id) {
-      id->ToString(aDOMNodeID);
-    }
-  }
-}
-
-mozilla::java::GeckoBundle::LocalRef
-AccessibleWrap::ToBundle()
-{
-  AccessibleWrap* parent = static_cast<AccessibleWrap*>(Parent());
-
-  nsAutoString name;
-  Name(name);
-
-  nsAutoString value;
-  Value(value);
-
-  nsAutoString viewIdResourceName;
-  DOMNodeID(viewIdResourceName);
-
-  nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
-
-  auto childCount = ChildCount();
-  nsTArray<int32_t> children(childCount);
-  for (uint32_t i = 0; i < childCount; i++) {
-    auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
-    children.AppendElement(child->VirtualViewID());
-  }
-
-  return CreateBundle(parent ? parent->VirtualViewID() : 0,
-                      Role(),
-                      State(),
-                      name,
-                      value,
-                      viewIdResourceName,
-                      Bounds(),
-                      CurValue(),
-                      MinValue(),
-                      MaxValue(),
-                      Step(),
-                      attributes,
-                      children);
-}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -1,80 +1,25 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_AccessibleWrap_h_
 #define mozilla_a11y_AccessibleWrap_h_
 
+#include "nsCOMPtr.h"
 #include "Accessible.h"
-#include "GeneratedJNIWrappers.h"
-#include "mozilla/a11y/ProxyAccessible.h"
-#include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap : public Accessible
 {
-public:
+public: // construction, destruction
   AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
   virtual ~AccessibleWrap();
-
-  virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
-  virtual void Shutdown() override;
-
-  int32_t VirtualViewID() const { return mID; }
-
-  virtual void SetTextContents(const nsAString& aText);
-
-  virtual void GetTextContents(nsAString& aText);
-
-  virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
-
-  virtual mozilla::java::GeckoBundle::LocalRef ToBundle();
-
-  static const int32_t kNoID = -1;
-
-protected:
-  mozilla::java::GeckoBundle::LocalRef CreateBundle(
-    int32_t aParentID,
-    role aRole,
-    uint64_t aState,
-    const nsString& aName,
-    const nsString& aTextValue,
-    const nsString& aDOMNodeID,
-    const nsIntRect& aBounds,
-    double aCurVal,
-    double aMinVal,
-    double aMaxVal,
-    double aStep,
-    nsIPersistentProperties* aAttributes,
-    const nsTArray<int32_t>& aChildren) const;
-
-  // IDs should be a positive 32bit integer.
-  static int32_t AcquireID();
-  static void ReleaseID(int32_t aID);
-
-  int32_t mID;
-
-private:
-  void DOMNodeID(nsString& aDOMNodeID);
-
-  static void GetAndroidRoleAndClass(role aRole,
-                                     nsAString& aGeckoRole,
-                                     nsAString& aRoleDescription,
-                                     nsAString& aClassStr);
-
-  static uint64_t GetFlags(role aRole, uint64_t aState);
 };
 
-static inline AccessibleWrap*
-WrapperFor(const ProxyAccessible* aProxy)
-{
-  return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
-}
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
deleted file mode 100644
--- a/accessible/android/DocAccessibleWrap.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "DocAccessibleWrap.h"
-#include "nsIDocShell.h"
-
-using namespace mozilla::a11y;
-
-////////////////////////////////////////////////////////////////////////////////
-// DocAccessibleWrap
-////////////////////////////////////////////////////////////////////////////////
-
-DocAccessibleWrap::DocAccessibleWrap(nsIDocument* aDocument,
-                                     nsIPresShell* aPresShell)
-  : DocAccessible(aDocument, aPresShell)
-{
-  nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocument->GetDocShell());
-
-  nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
-  treeItem->GetParent(getter_AddRefs(parentTreeItem));
-
-  if (treeItem->ItemType() == nsIDocShellTreeItem::typeContent &&
-      (!parentTreeItem ||
-       parentTreeItem->ItemType() == nsIDocShellTreeItem::typeChrome)) {
-    // The top-level content document gets this special ID.
-    mID = kNoID;
-  } else {
-    mID = AcquireID();
-  }
-}
-
-DocAccessibleWrap::~DocAccessibleWrap() {}
-
-AccessibleWrap*
-DocAccessibleWrap::GetAccessibleByID(int32_t aID) const
-{
-  if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
-    return acc;
-  }
-
-  // If the ID is not in the hash table, check the IDs of the child docs.
-  for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
-    auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
-    if (childDoc->VirtualViewID() == aID) {
-      return childDoc;
-    }
-  }
-
-  return nullptr;
-}
--- a/accessible/android/DocAccessibleWrap.h
+++ b/accessible/android/DocAccessibleWrap.h
@@ -6,35 +6,14 @@
 #ifndef mozilla_a11y_DocAccessibleWrap_h__
 #define mozilla_a11y_DocAccessibleWrap_h__
 
 #include "DocAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
-class DocAccessibleWrap : public DocAccessible
-{
-public:
-  DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
-  virtual ~DocAccessibleWrap();
-
-  /**
-   * Manage the mapping from id to Accessible.
-   */
-  void AddID(uint32_t aID, AccessibleWrap* aAcc)
-  {
-    mIDToAccessibleMap.Put(aID, aAcc);
-  }
-  void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
-  AccessibleWrap* GetAccessibleByID(int32_t aID) const;
-
-protected:
-  /*
-   * This provides a mapping from 32 bit id to accessible objects.
-   */
-  nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-};
+typedef DocAccessible DocAccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/android/Platform.cpp
+++ b/accessible/android/Platform.cpp
@@ -1,200 +1,89 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Platform.h"
-#include "ProxyAccessibleWrap.h"
-#include "SessionAccessibility.h"
-#include "mozilla/a11y/ProxyAccessible.h"
-#include "nsIAccessibleEvent.h"
-#include "nsIAccessiblePivot.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 void
 a11y::PlatformInit()
 {
 }
 
 void
 a11y::PlatformShutdown()
 {
 }
 
 void
-a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
+a11y::ProxyCreated(ProxyAccessible*, uint32_t)
 {
-  AccessibleWrap* wrapper = nullptr;
-  if (aProxy->IsDoc()) {
-    wrapper = new DocProxyAccessibleWrap(aProxy->AsDoc());
-  } else {
-    wrapper = new ProxyAccessibleWrap(aProxy);
-  }
-
-  wrapper->AddRef();
-  aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper));
-}
-
-void
-a11y::ProxyDestroyed(ProxyAccessible* aProxy)
-{
-  AccessibleWrap* wrapper =
-    reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
-
-  // If aProxy is a document that was created, but
-  // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be
-  // null.
-  if (!wrapper) {
-    return;
-  }
-
-  wrapper->Shutdown();
-  aProxy->SetWrapper(0);
-  wrapper->Release();
 }
 
 void
-a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
+a11y::ProxyDestroyed(ProxyAccessible*)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-  if (!sessionAcc) {
-    return;
-  }
-
-  switch (aEventType) {
-    case nsIAccessibleEvent::EVENT_FOCUS:
-      sessionAcc->SendFocusEvent(WrapperFor(aTarget));
-      break;
-  }
 }
 
 void
-a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget,
-                            uint64_t aState,
-                            bool aEnabled)
+a11y::ProxyEvent(ProxyAccessible*, uint32_t)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (!sessionAcc) {
-    return;
-  }
+}
 
-  if (aState & states::CHECKED) {
-    sessionAcc->SendClickedEvent(WrapperFor(aTarget));
-  }
-
-  if (aState & states::SELECTED) {
-    sessionAcc->SendSelectedEvent(WrapperFor(aTarget));
-  }
-
-  if (aState & states::BUSY) {
-    sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget));
-  }
+void
+a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
+{
 }
 
 void
 a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (sessionAcc) {
-    sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset);
-  }
 }
 
 void
-a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget,
-                           const nsString& aStr,
-                           int32_t aStart,
-                           uint32_t aLen,
-                           bool aIsInsert,
-                           bool aFromUser)
+a11y::ProxyTextChangeEvent(ProxyAccessible*,
+                           const nsString&,
+                           int32_t,
+                           uint32_t,
+                           bool,
+                           bool)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (sessionAcc) {
-    sessionAcc->SendTextChangedEvent(
-      WrapperFor(aTarget), aStr, aStart, aLen, aIsInsert, aFromUser);
-  }
 }
 
 void
-a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
-                         ProxyAccessible* aParent,
-                         bool aInsert,
-                         bool aFromUser)
+a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
 {
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-  if (sessionAcc) {
-    sessionAcc->SendWindowContentChangedEvent(WrapperFor(aParent));
-  }
 }
 
 void
 a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
 {
 }
 
 void
-a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
-                                    ProxyAccessible* aOldPosition,
-                                    int32_t aOldStartOffset,
-                                    int32_t aOldEndOffset,
-                                    ProxyAccessible* aNewPosition,
-                                    int32_t aNewStartOffset,
-                                    int32_t aNewEndOffset,
-                                    int16_t aReason,
-                                    int16_t aBoundaryType,
-                                    bool aFromUser)
+a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*,
+                                    ProxyAccessible*,
+                                    int32_t,
+                                    int32_t,
+                                    ProxyAccessible*,
+                                    int32_t,
+                                    int32_t,
+                                    int16_t,
+                                    int16_t,
+                                    bool)
 {
-  if (!aNewPosition) {
-    return;
-  }
-
-  SessionAccessibility* sessionAcc =
-    SessionAccessibility::GetInstanceFor(aTarget);
-
-  if (!sessionAcc) {
-    return;
-  }
-
-  if (aOldPosition != aNewPosition) {
-    if (aReason == nsIAccessiblePivot::REASON_POINT) {
-      sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition));
-    } else {
-      sessionAcc->SendAccessibilityFocusedEvent(WrapperFor(aNewPosition));
-    }
-  }
-
-  if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) {
-    sessionAcc->SendTextTraversedEvent(
-      WrapperFor(aNewPosition), aNewStartOffset, aNewEndOffset);
-  }
 }
 
 void
-a11y::ProxyScrollingEvent(ProxyAccessible* aTarget,
-                          uint32_t aEventType,
-                          uint32_t aScrollX,
-                          uint32_t aScrollY,
-                          uint32_t aMaxScrollX,
-                          uint32_t aMaxScrollY)
+a11y::ProxyScrollingEvent(ProxyAccessible*,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t,
+                          uint32_t)
 {
-  if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
-    SessionAccessibility* sessionAcc =
-      SessionAccessibility::GetInstanceFor(aTarget);
-
-    if (sessionAcc) {
-      sessionAcc->SendScrollingEvent(
-        WrapperFor(aTarget), aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
-    }
-  }
 }
deleted file mode 100644
--- a/accessible/android/ProxyAccessibleWrap.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*- Mode: c++; 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/. */
-
-#include "ProxyAccessibleWrap.h"
-#include "nsPersistentProperties.h"
-
-using namespace mozilla::a11y;
-
-ProxyAccessibleWrap::ProxyAccessibleWrap(ProxyAccessible* aProxy)
-  : AccessibleWrap(nullptr, nullptr)
-{
-  mType = eProxyType;
-  mBits.proxy = aProxy;
-
-  if (aProxy->mHasValue) {
-    mStateFlags |= eHasNumericValue;
-  }
-
-  if (aProxy->mIsSelection) {
-    mGenericTypes |= eSelect;
-  }
-
-  if (aProxy->mIsHyperText) {
-    mGenericTypes |= eHyperText;
-  }
-
-  auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
-    Proxy()->Document()->GetWrapper());
-  if (doc) {
-    mID = AcquireID();
-    doc->AddID(mID, this);
-  }
-}
-
-void
-ProxyAccessibleWrap::Shutdown()
-{
-  auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
-    Proxy()->Document()->GetWrapper());
-  if (mID && doc) {
-    doc->RemoveID(mID);
-    ReleaseID(mID);
-    mID = 0;
-  }
-
-  mBits.proxy = nullptr;
-  mStateFlags |= eIsDefunct;
-}
-
-// Accessible
-
-already_AddRefed<nsIPersistentProperties>
-ProxyAccessibleWrap::Attributes()
-{
-  RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties();
-  nsAutoString unused;
-  AutoTArray<Attribute, 10> attrs;
-  Proxy()->Attributes(&attrs);
-  for (size_t i = 0; i < attrs.Length(); i++) {
-    attributes->SetStringProperty(
-      attrs.ElementAt(i).Name(), attrs.ElementAt(i).Value(), unused);
-  }
-
-  return attributes.forget();
-}
-
-uint32_t
-ProxyAccessibleWrap::ChildCount() const
-{
-  return Proxy()->ChildrenCount();
-}
-
-void
-ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const
-{
-  Proxy()->ScrollTo(aHow);
-}
-
-// Other
-
-void
-ProxyAccessibleWrap::SetTextContents(const nsAString& aText)
-{
-  Proxy()->ReplaceText(PromiseFlatString(aText));
-}
-
-void
-ProxyAccessibleWrap::GetTextContents(nsAString& aText)
-{
-  nsAutoString text;
-  Proxy()->TextSubstring(0, -1, text);
-  aText.Assign(text);
-}
-
-bool
-ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
-                                        int32_t* aEndOffset)
-{
-  nsAutoString unused;
-  return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset);
-}
-
-mozilla::java::GeckoBundle::LocalRef
-ProxyAccessibleWrap::ToBundle()
-{
-  ProxyAccessible* proxy = Proxy();
-  if (!proxy) {
-    return nullptr;
-  }
-
-  int32_t parentID = proxy->Parent() ?
-    WrapperFor(proxy->Parent())->VirtualViewID() : 0;
-
-  nsAutoString name;
-  proxy->Name(name);
-
-  nsAutoString value;
-  proxy->Value(value);
-
-  nsAutoString viewIdResourceName;
-  proxy->DOMNodeID(viewIdResourceName);
-
-  nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
-
-  auto childCount = proxy->ChildrenCount();
-  nsTArray<int32_t> children(childCount);
-  for (uint32_t i = 0; i < childCount; i++) {
-    auto child = WrapperFor(proxy->ChildAt(i));
-    children.AppendElement(child->VirtualViewID());
-  }
-
-  return CreateBundle(parentID,
-                      proxy->Role(),
-                      proxy->State(),
-                      name,
-                      value,
-                      viewIdResourceName,
-                      proxy->Bounds(),
-                      proxy->CurValue(),
-                      proxy->MinValue(),
-                      proxy->MaxValue(),
-                      proxy->Step(),
-                      attributes,
-                      children);
-}
deleted file mode 100644
--- a/accessible/android/ProxyAccessibleWrap.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. *
- */
-
-#ifndef MOZILLA_A11Y_ProxyAccessibleWrap_h
-#define MOZILLA_A11Y_ProxyAccessibleWrap_h
-
-#include "AccessibleWrap.h"
-#include "DocAccessibleParent.h"
-
-namespace mozilla {
-namespace a11y {
-
-/**
- * A wrapper for Accessible proxies. The public methods here should be overriden
- * from AccessibleWrap or its super classes.
- * This gives us an abstraction layer so SessionAccessibility doesn't have
- * to distinguish between a local or remote accessibles.
- * NOTE: This shouldn't be regarded as a full Accessible implementation.
- */
-class ProxyAccessibleWrap : public AccessibleWrap
-{
-public:
-  explicit ProxyAccessibleWrap(ProxyAccessible* aProxy);
-
-  virtual void Shutdown() override;
-
-  // Accessible
-
-  virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
-
-  virtual uint32_t ChildCount() const override;
-
-  virtual void ScrollTo(uint32_t aHow) const override;
-
-  // AccessibleWrap
-
-  virtual void SetTextContents(const nsAString& aText) override;
-
-  virtual void GetTextContents(nsAString& aText) override;
-
-  virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) override;
-
-  virtual mozilla::java::GeckoBundle::LocalRef ToBundle() override;
-};
-
-class DocProxyAccessibleWrap : public ProxyAccessibleWrap
-{
-public:
-  explicit DocProxyAccessibleWrap(DocAccessibleParent* aProxy)
-    : ProxyAccessibleWrap(aProxy)
-  {
-    mGenericTypes |= eDocument;
-
-    if (auto parent = ParentDocument()) {
-      mID = AcquireID();
-      parent->AddID(mID, this);
-    } else {
-      // top level
-      mID = kNoID;
-    }
-  }
-
-  virtual void Shutdown() override
-  {
-    if (mID) {
-      auto parent = ParentDocument();
-      if (parent) {
-        MOZ_ASSERT(mID != kNoID, "A non root accessible always has a parent");
-        parent->RemoveID(mID);
-        ReleaseID(mID);
-      }
-    }
-    mID = 0;
-    mBits.proxy = nullptr;
-    mStateFlags |= eIsDefunct;
-  }
-
-  DocProxyAccessibleWrap* ParentDocument()
-  {
-    DocAccessibleParent* proxy = static_cast<DocAccessibleParent*>(Proxy());
-    MOZ_ASSERT(proxy);
-    if (DocAccessibleParent* parent = proxy->ParentDoc()) {
-      return reinterpret_cast<DocProxyAccessibleWrap*>(parent->GetWrapper());
-    }
-
-    return nullptr;
-  }
-
-  DocProxyAccessibleWrap* GetChildDocumentAt(uint32_t aIndex)
-  {
-    auto doc = Proxy()->AsDoc();
-    if (doc && doc->ChildDocCount() > aIndex) {
-      return reinterpret_cast<DocProxyAccessibleWrap*>(
-        doc->ChildDocAt(aIndex)->GetWrapper());
-    }
-
-    return nullptr;
-  }
-
-  void AddID(uint32_t aID, AccessibleWrap* aAcc)
-  {
-    mIDToAccessibleMap.Put(aID, aAcc);
-  }
-  void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
-  AccessibleWrap* GetAccessibleByID(uint32_t aID) const
-  {
-    return mIDToAccessibleMap.Get(aID);
-  }
-
-private:
-  /*
-   * This provides a mapping from 32 bit id to accessible objects.
-   */
-  nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
-};
-}
-}
-
-#endif
deleted file mode 100644
--- a/accessible/android/RootAccessibleWrap.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: c++; 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/. */
-
-#include "RootAccessibleWrap.h"
-
-#include "AccessibleOrProxy.h"
-#include "DocAccessibleParent.h"
-#include "ProxyAccessibleWrap.h"
-#include "SessionAccessibility.h"
-
-using namespace mozilla::a11y;
-
-RootAccessibleWrap::RootAccessibleWrap(nsIDocument* aDoc,
-                                       nsIPresShell* aPresShell)
-  : RootAccessible(aDoc, aPresShell)
-{
-}
-
-RootAccessibleWrap::~RootAccessibleWrap() {}
-
-AccessibleWrap*
-RootAccessibleWrap::GetContentAccessible()
-{
-  if (ProxyAccessible* proxy = GetPrimaryRemoteTopLevelContentDoc()) {
-    return WrapperFor(proxy);
-  }
-
-  if (ChildDocumentCount()) {
-    return GetChildDocumentAt(0);
-  }
-
-  return nullptr;
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(int32_t aID)
-{
-  AccessibleWrap* contentAcc = GetContentAccessible();
-
-  if (!contentAcc) {
-    return nullptr;
-  }
-
-  if (aID == AccessibleWrap::kNoID) {
-    return contentAcc;
-  }
-
-  if (contentAcc->IsProxy()) {
-    return FindAccessibleById(static_cast<DocProxyAccessibleWrap*>(contentAcc),
-                              aID);
-  }
-
-  return FindAccessibleById(
-    static_cast<DocAccessibleWrap*>(contentAcc->AsDoc()), aID);
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(DocProxyAccessibleWrap* aDoc,
-                                       int32_t aID)
-{
-  AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
-  uint32_t index = 0;
-  while (!acc) {
-    auto child =
-      static_cast<DocProxyAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
-    if (!child) {
-      break;
-    }
-    acc = FindAccessibleById(child, aID);
-  }
-
-  return acc;
-}
-
-AccessibleWrap*
-RootAccessibleWrap::FindAccessibleById(DocAccessibleWrap* aDoc, int32_t aID)
-{
-  AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
-  uint32_t index = 0;
-  while (!acc) {
-    auto child =
-      static_cast<DocAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
-    if (!child) {
-      break;
-    }
-    acc = FindAccessibleById(child, aID);
-  }
-
-  return acc;
-}
--- a/accessible/android/RootAccessibleWrap.h
+++ b/accessible/android/RootAccessibleWrap.h
@@ -6,32 +6,14 @@
 #ifndef mozilla_a11y_RootAccessibleWrap_h__
 #define mozilla_a11y_RootAccessibleWrap_h__
 
 #include "RootAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
-class DocProxyAccessibleWrap;
-
-class RootAccessibleWrap : public RootAccessible
-{
-public:
-  RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
-  virtual ~RootAccessibleWrap();
-
-  AccessibleWrap* GetContentAccessible();
-
-  AccessibleWrap* FindAccessibleById(int32_t aID);
-
-  // Recursively searches for the accessible ID within the document tree.
-  AccessibleWrap* FindAccessibleById(DocAccessibleWrap* aDocument, int32_t aID);
-
-  // Recursively searches for the accessible ID within the proxy document tree.
-  AccessibleWrap* FindAccessibleById(DocProxyAccessibleWrap* aDocument,
-                                     int32_t aID);
-};
+typedef RootAccessible RootAccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/android/SessionAccessibility.cpp
+++ b/accessible/android/SessionAccessibility.cpp
@@ -1,22 +1,16 @@
 /* -*- Mode: c++; 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/. */
 
 #include "SessionAccessibility.h"
 #include "AndroidUiThread.h"
 #include "nsThreadUtils.h"
-#include "AccessibilityEvent.h"
-#include "HyperTextAccessible.h"
-#include "JavaBuiltins.h"
-#include "RootAccessibleWrap.h"
-#include "nsAccessibilityService.h"
-#include "nsViewManager.h"
 
 #ifdef DEBUG
 #include <android/log.h>
 #define AALOG(args...)                                                         \
   __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
 #else
 #define AALOG(args...)                                                         \
   do {                                                                         \
@@ -24,30 +18,16 @@
 #endif
 
 template<>
 const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
   "SessionAccessibility";
 
 using namespace mozilla::a11y;
 
-class Settings final
-  : public mozilla::java::SessionAccessibility::Settings::Natives<Settings>
-{
-public:
-  static void ToggleNativeAccessibility(bool aEnable)
-  {
-    if (aEnable) {
-      GetOrCreateAccService();
-    } else {
-      MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
-    }
-  }
-};
-
 void
 SessionAccessibility::SetAttached(bool aAttached,
                                   already_AddRefed<Runnable> aRunnable)
 {
   if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
     uiThread->Dispatch(NS_NewRunnableFunction(
       "SessionAccessibility::Attach",
       [aAttached,
@@ -56,264 +36,8 @@ SessionAccessibility::SetAttached(bool a
        runnable = RefPtr<Runnable>(aRunnable)] {
         sa->SetAttached(aAttached);
         if (runnable) {
           runnable->Run();
         }
       }));
   }
 }
-
-void
-SessionAccessibility::Init()
-{
-  java::SessionAccessibility::NativeProvider::Natives<
-    SessionAccessibility>::Init();
-  Settings::Init();
-}
-
-mozilla::jni::Object::LocalRef
-SessionAccessibility::GetNodeInfo(int32_t aID)
-{
-  java::GeckoBundle::GlobalRef ret = nullptr;
-  RefPtr<SessionAccessibility> self(this);
-  nsAppShell::SyncRunEvent([this, self, aID, &ret] {
-    if (RootAccessibleWrap* rootAcc = GetRoot()) {
-      AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
-      if (acc) {
-        ret = acc->ToBundle();
-      } else {
-        AALOG("oops, nothing for %d", aID);
-      }
-    }
-  });
-
-  return mozilla::jni::Object::Ref::From(ret);
-}
-
-RootAccessibleWrap*
-SessionAccessibility::GetRoot()
-{
-  if (!mWindow) {
-    return nullptr;
-  }
-
-  return static_cast<RootAccessibleWrap*>(mWindow->GetRootAccessible());
-}
-
-void
-SessionAccessibility::SetText(int32_t aID, jni::String::Param aText)
-{
-  if (RootAccessibleWrap* rootAcc = GetRoot()) {
-    AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
-    if (!acc) {
-      return;
-    }
-
-    acc->SetTextContents(aText->ToString());
-  }
-}
-
-SessionAccessibility*
-SessionAccessibility::GetInstanceFor(ProxyAccessible* aAccessible)
-{
-  Accessible* outerDoc = aAccessible->OuterDocOfRemoteBrowser();
-  if (!outerDoc) {
-    return nullptr;
-  }
-
-  return GetInstanceFor(outerDoc);
-}
-
-SessionAccessibility*
-SessionAccessibility::GetInstanceFor(Accessible* aAccessible)
-{
-  RootAccessible* rootAcc = aAccessible->RootAccessible();
-  nsIPresShell* shell = rootAcc->PresShell();
-  nsViewManager* vm = shell->GetViewManager();
-  if (!vm) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIWidget> rootWidget;
-  vm->GetRootWidget(getter_AddRefs(rootWidget));
-  // `rootWidget` can be one of several types. Here we make sure it is an
-  // android nsWindow that implemented NS_NATIVE_WIDGET to return itself.
-  if (rootWidget &&
-      rootWidget->WindowType() == nsWindowType::eWindowType_toplevel &&
-      rootWidget->GetNativeData(NS_NATIVE_WIDGET) == rootWidget) {
-    return static_cast<nsWindow*>(rootWidget.get())->GetSessionAccessibility();
-  }
-
-  return nullptr;
-}
-
-void
-SessionAccessibility::SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-  aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
-}
-
-void
-SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible)
-{
-  // Suppress focus events from about:blank pages.
-  // This is important for tests.
-  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
-    return;
-  }
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
-                                         int32_t aScrollX,
-                                         int32_t aScrollY,
-                                         int32_t aMaxScrollX,
-                                         int32_t aMaxScrollY)
-{
-  int32_t virtualViewId = aAccessible->VirtualViewID();
-
-  if (virtualViewId != AccessibleWrap::kNoID) {
-    // XXX: Support scrolling in subframes
-    return;
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
-  GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
-  GECKOBUNDLE_PUT(eventInfo, "maxScrollX", java::sdk::Integer::ValueOf(aMaxScrollX));
-  GECKOBUNDLE_PUT(eventInfo, "maxScrollY", java::sdk::Integer::ValueOf(aMaxScrollY));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
-    eventInfo, aAccessible->ToBundle());
-
-  SendWindowContentChangedEvent(aAccessible);
-}
-
-void
-SessionAccessibility::SendWindowContentChangedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendWindowStateChangedEvent(AccessibleWrap* aAccessible)
-{
-  // Suppress window state changed events from about:blank pages.
-  // This is important for tests.
-  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
-    return;
-  }
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
-                                                    int32_t aCaretOffset)
-{
-  int32_t fromIndex = aCaretOffset;
-  int32_t startSel = -1;
-  int32_t endSel = -1;
-  if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
-    fromIndex = startSel == aCaretOffset ? endSel : startSel;
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(fromIndex));
-  GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aCaretOffset));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
-                                           const nsString& aStr,
-                                           int32_t aStart,
-                                           uint32_t aLen,
-                                           bool aIsInsert,
-                                           bool aFromUser)
-{
-  if (!aFromUser) {
-    // Only dispatch text change events from users, for now.
-    return;
-  }
-
-  nsAutoString text;
-  aAccessible->GetTextContents(text);
-  nsAutoString beforeText(text);
-  if (aIsInsert) {
-    beforeText.Cut(aStart, aLen);
-  } else {
-    beforeText.Insert(aStr, aStart);
-  }
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
-  GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
-  GECKOBUNDLE_PUT(eventInfo, "addedCount", java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
-  GECKOBUNDLE_PUT(eventInfo, "removedCount", java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
-                                             int32_t aStartOffset,
-                                             int32_t aEndOffset)
-{
-  nsAutoString text;
-  aAccessible->GetTextContents(text);
-
-  GECKOBUNDLE_START(eventInfo);
-  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
-  GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStartOffset));
-  GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aEndOffset));
-  GECKOBUNDLE_FINISH(eventInfo);
-
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::
-      TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
-    aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
-
-void
-SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible)
-{
-  mSessionAccessibility->SendEvent(
-    java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
-    aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
-}
--- a/accessible/android/SessionAccessibility.h
+++ b/accessible/android/SessionAccessibility.h
@@ -2,51 +2,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_SessionAccessibility_h_
 #define mozilla_a11y_SessionAccessibility_h_
 
 #include "GeneratedJNINatives.h"
-#include "GeneratedJNIWrappers.h"
-#include "nsAppShell.h"
-#include "nsThreadUtils.h"
 #include "nsWindow.h"
 
-#define GECKOBUNDLE_START(name)                                                \
-  nsTArray<jni::String::LocalRef> _##name##_keys;                              \
-  nsTArray<jni::Object::LocalRef> _##name##_values;
-
-#define GECKOBUNDLE_PUT(name, key, value)                                      \
-  _##name##_keys.AppendElement(jni::StringParam(NS_LITERAL_STRING(key)));      \
-  _##name##_values.AppendElement(value);
-
-#define GECKOBUNDLE_FINISH(name)                                               \
-  MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length());            \
-  auto _##name##_jkeys =                                                       \
-    jni::ObjectArray::New<jni::String>(_##name##_keys.Length());               \
-  auto _##name##_jvalues =                                                     \
-    jni::ObjectArray::New<jni::Object>(_##name##_values.Length());             \
-  for (size_t i = 0;                                                           \
-       i < _##name##_keys.Length() && i < _##name##_values.Length();           \
-       i++) {                                                                  \
-    _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i));               \
-    _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i));           \
-  }                                                                            \
-  auto name =                                                                  \
-    mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
-
 namespace mozilla {
 namespace a11y {
 
-class AccessibleWrap;
-class ProxyAccessible;
-class RootAccessibleWrap;
-
 class SessionAccessibility final
   : public java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility>
 {
 public:
   typedef java::SessionAccessibility::NativeProvider::Natives<SessionAccessibility> Base;
 
   SessionAccessibility(
     nsWindow::NativePtr<SessionAccessibility>* aPtr,
@@ -63,59 +33,26 @@ public:
     SetAttached(false, std::move(aDisposer));
   }
 
   const java::SessionAccessibility::NativeProvider::Ref& GetJavaAccessibility()
   {
     return mSessionAccessibility;
   }
 
-  static void Init();
-  static SessionAccessibility* GetInstanceFor(ProxyAccessible* aAccessible);
-  static SessionAccessibility* GetInstanceFor(Accessible* aAccessible);
-
   // Native implementations
   using Base::AttachNative;
   using Base::DisposeNative;
-  jni::Object::LocalRef GetNodeInfo(int32_t aID);
-  void SetText(int32_t aID, jni::String::Param aText);
-  void StartNativeAccessibility();
 
-  // Event methods
-  void SendFocusEvent(AccessibleWrap* aAccessible);
-  void SendScrollingEvent(AccessibleWrap* aAccessible,
-                          int32_t aScrollX,
-                          int32_t aScrollY,
-                          int32_t aMaxScrollX,
-                          int32_t aMaxScrollY);
-  void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
-  void SendHoverEnterEvent(AccessibleWrap* aAccessible);
-  void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
-                                     int32_t aCaretOffset);
-  void SendTextTraversedEvent(AccessibleWrap* aAccessible,
-                              int32_t aStartOffset,
-                              int32_t aEndOffset);
-  void SendTextChangedEvent(AccessibleWrap* aAccessible,
-                            const nsString& aStr,
-                            int32_t aStart,
-                            uint32_t aLen,
-                            bool aIsInsert,
-                            bool aFromUser);
-  void SendSelectedEvent(AccessibleWrap* aAccessible);
-  void SendClickedEvent(AccessibleWrap* aAccessible);
-  void SendWindowContentChangedEvent(AccessibleWrap* aAccessible);
-  void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
-
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
+  NS_INLINE_DECL_REFCOUNTING(SessionAccessibility)
 
 private:
   ~SessionAccessibility() {}
 
   void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
-  RootAccessibleWrap* GetRoot();
 
   nsWindow::WindowPtr<SessionAccessibility> mWindow; // Parent only
   java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/android/moz.build
+++ b/accessible/android/moz.build
@@ -6,20 +6,17 @@
 
 EXPORTS.mozilla.a11y += ['AccessibleWrap.h',
     'HyperTextAccessibleWrap.h',
     'SessionAccessibility.h',
 ]
 
 SOURCES += [
     'AccessibleWrap.cpp',
-    'DocAccessibleWrap.cpp',
     'Platform.cpp',
-    'ProxyAccessibleWrap.cpp',
-    'RootAccessibleWrap.cpp',
     'SessionAccessibility.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/generic',
     '/accessible/html',
     '/accessible/ipc',
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -550,17 +550,17 @@ public:
   /**
    * Focus the accessible.
    */
   virtual void TakeFocus() const;
 
   /**
    * Scroll the accessible into view.
    */
-  virtual void ScrollTo(uint32_t aHow) const;
+  void ScrollTo(uint32_t aHow) const;
 
   /**
    * Scroll the accessible to the given point.
    */
   void ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY);
 
   /**
    * Get a pointer to accessibility interface for this node, which is specific
--- a/accessible/generic/HyperTextAccessible-inl.h
+++ b/accessible/generic/HyperTextAccessible-inl.h
@@ -6,17 +6,16 @@
 #ifndef mozilla_a11y_HyperTextAccessible_inl_h__
 #define mozilla_a11y_HyperTextAccessible_inl_h__
 
 #include "HyperTextAccessible.h"
 
 #include "nsAccUtils.h"
 
 #include "nsIClipboard.h"
-#include "nsIEditor.h"
 #include "nsIPersistentProperties2.h"
 #include "nsFrameSelection.h"
 
 #include "mozilla/TextEditor.h"
 
 namespace mozilla {
 namespace a11y {
 
@@ -117,17 +116,17 @@ HyperTextAccessible::DeleteText(int32_t 
 }
 
 inline void
 HyperTextAccessible::PasteText(int32_t aPosition)
 {
   RefPtr<TextEditor> textEditor = GetEditor();
   if (textEditor) {
     SetSelectionRange(aPosition, aPosition);
-    textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard);
+    textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard, true);
   }
 }
 
 inline index_t
 HyperTextAccessible::ConvertMagicOffset(int32_t aOffset) const
 {
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
     return CharacterCount();
--- a/accessible/ipc/ProxyAccessibleBase.h
+++ b/accessible/ipc/ProxyAccessibleBase.h
@@ -163,24 +163,23 @@ protected:
     , mWrapper(0)
     , mID(aID)
     , mRole(aRole)
     , mOuterDoc(false)
     , mIsDoc(false)
     , mHasValue(aInterfaces & Interfaces::VALUE)
     , mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
     , mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
-    , mIsSelection(aInterfaces & Interfaces::SELECTION)
   {
   }
 
   explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
     mParent(kNoParent), mDoc(aThisAsDoc), mWrapper(0), mID(0),
     mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
-    mIsHyperLink(false), mIsHyperText(false), mIsSelection(false)
+    mIsHyperLink(false), mIsHyperText(false)
   {}
 
 protected:
   void SetParent(Derived* aParent);
 
 private:
   uintptr_t mParent;
   static const uintptr_t kNoParent = UINTPTR_MAX;
@@ -200,17 +199,16 @@ protected:
 private:
   bool mOuterDoc : 1;
 
 public:
   const bool mIsDoc: 1;
   const bool mHasValue: 1;
   const bool mIsHyperLink: 1;
   const bool mIsHyperText: 1;
-  const bool mIsSelection: 1;
 };
 
 extern template class ProxyAccessibleBase<ProxyAccessible>;
 
 }
 }
 
 #endif
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -14,29 +14,29 @@ ChromeUtils.defineModuleGetter(this, "Re
 if (Utils.MozBuildApp === "mobile/android") {
   ChromeUtils.import("resource://gre/modules/Messaging.jsm");
 }
 
 const GECKOVIEW_MESSAGE = {
   ACTIVATE: "GeckoView:AccessibilityActivate",
   BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
   CLIPBOARD: "GeckoView:AccessibilityClipboard",
-  CURSOR_TO_FOCUSED: "GeckoView:AccessibilityCursorToFocused",
   EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
   LONG_PRESS: "GeckoView:AccessibilityLongPress",
   NEXT: "GeckoView:AccessibilityNext",
   PREVIOUS: "GeckoView:AccessibilityPrevious",
   SCROLL_BACKWARD: "GeckoView:AccessibilityScrollBackward",
   SCROLL_FORWARD: "GeckoView:AccessibilityScrollForward",
   SELECT: "GeckoView:AccessibilitySelect",
   SET_SELECTION: "GeckoView:AccessibilitySetSelection",
   VIEW_FOCUSED: "GeckoView:AccessibilityViewFocused",
 };
 
 const ACCESSFU_MESSAGE = {
+  PRESENT: "AccessFu:Present",
   DOSCROLL: "AccessFu:DoScroll",
 };
 
 const FRAME_SCRIPT = "chrome://global/content/accessibility/content-script.js";
 
 var AccessFu = {
   /**
    * A lazy getter for event handler that binds the scope to AccessFu object.
@@ -52,16 +52,21 @@ var AccessFu = {
    */
   enable: function enable() {
     if (this._enabled) {
       return;
     }
     this._enabled = true;
 
     ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
+    ChromeUtils.import("resource://gre/modules/accessibility/Presentation.jsm");
+
+    // Check for output notification
+    this._notifyOutputPref =
+      new PrefCache("accessibility.accessfu.notify_output");
 
     Services.obs.addObserver(this, "remote-browser-shown");
     Services.obs.addObserver(this, "inprocess-browser-shown");
     Services.ww.registerNotification(this);
 
     for (let win of Services.wm.getEnumerator(null)) {
       this._attachWindow(win);
     }
@@ -82,30 +87,35 @@ var AccessFu = {
     Services.obs.removeObserver(this, "remote-browser-shown");
     Services.obs.removeObserver(this, "inprocess-browser-shown");
     Services.ww.unregisterNotification(this);
 
     for (let win of Services.wm.getEnumerator(null)) {
       this._detachWindow(win);
     }
 
+    delete this._notifyOutputPref;
+
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
 
     Logger.info("AccessFu:Disabled");
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     Logger.debug(() => {
       return ["Recieved", aMessage.name, JSON.stringify(aMessage.json)];
     });
 
     switch (aMessage.name) {
+      case ACCESSFU_MESSAGE.PRESENT:
+        this._output(aMessage.json, aMessage.target);
+        break;
       case ACCESSFU_MESSAGE.DOSCROLL:
         this.Input.doScroll(aMessage.json, aMessage.target);
         break;
     }
   },
 
   _attachWindow: function _attachWindow(win) {
     let wtype = win.document.documentElement.getAttribute("windowtype");
@@ -140,16 +150,48 @@ var AccessFu = {
     win.removeEventListener("TabSelect", this);
     if (win.WindowEventDispatcher) {
       // desktop mochitests don't have this.
       win.WindowEventDispatcher.unregisterListener(this,
         Object.values(GECKOVIEW_MESSAGE));
     }
   },
 
+  _output: function _output(aPresentationData, aBrowser) {
+    if (!aPresentationData) {
+      // Either no android events to send or a string used for testing only.
+      return;
+    }
+
+    if (!Utils.isAliveAndVisible(Utils.AccService.getAccessibleFor(aBrowser))) {
+      return;
+    }
+
+    let win = aBrowser.ownerGlobal;
+
+    for (let evt of aPresentationData) {
+      if (typeof evt == "string") {
+        continue;
+      }
+
+      if (win.WindowEventDispatcher) {
+        // desktop mochitests don't have this.
+        win.WindowEventDispatcher.sendRequest({
+          ...evt,
+          type: "GeckoView:AccessibilityEvent"
+        });
+      }
+    }
+
+    if (this._notifyOutputPref.value) {
+      Services.obs.notifyObservers(null, "accessibility-output",
+                                   JSON.stringify(aPresentationData));
+    }
+  },
+
   onEvent(event, data, callback) {
     switch (event) {
       case GECKOVIEW_MESSAGE.SETTINGS:
         if (data.enabled) {
           this._enable();
         } else {
           this._disable();
         }
@@ -172,18 +214,21 @@ var AccessFu = {
         // XXX: Advertize long press on supported objects and implement action
         break;
       case GECKOVIEW_MESSAGE.SCROLL_FORWARD:
         this.Input.androidScroll("forward");
         break;
       case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
         this.Input.androidScroll("backward");
         break;
-      case GECKOVIEW_MESSAGE.CURSOR_TO_FOCUSED:
-        this.autoMove({ moveToFocused: true });
+      case GECKOVIEW_MESSAGE.VIEW_FOCUSED:
+        this._focused = data.gainFocus;
+        if (this._focused) {
+          this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
+        }
         break;
       case GECKOVIEW_MESSAGE.BY_GRANULARITY:
         this.Input.moveByGranularity(data);
         break;
       case GECKOVIEW_MESSAGE.EXPLORE_BY_TOUCH:
         this.Input.moveToPoint("Simple", ...data.coordinates);
         break;
       case GECKOVIEW_MESSAGE.SET_SELECTION:
@@ -231,16 +276,20 @@ var AccessFu = {
     }
   },
 
   autoMove: function autoMove(aOptions) {
     const mm = Utils.getMessageManager();
     mm.sendAsyncMessage("AccessFu:AutoMove", aOptions);
   },
 
+  announce: function announce(aAnnouncement) {
+    this._output(Presentation.announce(aAnnouncement), Utils.getCurrentBrowser());
+  },
+
   // So we don't enable/disable twice
   _enabled: false,
 
   // Layerview is focused
   _focused: false,
 
   /**
    * Adjusts the given bounds that are defined in device display pixels
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -9,16 +9,18 @@ ChromeUtils.defineModuleGetter(this, "Lo
 ChromeUtils.defineModuleGetter(this, "Roles",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "States",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "TraversalRules",
   "resource://gre/modules/accessibility/Traversal.jsm");
 ChromeUtils.defineModuleGetter(this, "TraversalHelper",
   "resource://gre/modules/accessibility/Traversal.jsm");
+ChromeUtils.defineModuleGetter(this, "Presentation",
+  "resource://gre/modules/accessibility/Presentation.jsm");
 
 var EXPORTED_SYMBOLS = ["ContentControl"];
 
 const MOVEMENT_GRANULARITY_CHARACTER = 1;
 const MOVEMENT_GRANULARITY_WORD = 2;
 const MOVEMENT_GRANULARITY_LINE = 4;
 
 const CLIPBOARD_COPY = 0x4000;
@@ -148,16 +150,19 @@ this.ContentControl.prototype = {
         // new position.
         this.sendToChild(vc, aMessage, { action: childAction }, true);
       }
     } else if (!this._childMessageSenders.has(aMessage.target) &&
                origin !== "top") {
       // We failed to move, and the message is not from a parent, so forward
       // to it.
       this.sendToParent(aMessage);
+    } else {
+      this._contentScope.get().sendAsyncMessage("AccessFu:Present",
+        Presentation.noMove(action));
     }
   },
 
   handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
     let [x, y] = [aMessage.json.x, aMessage.json.y];
     let rule = TraversalRules[aMessage.json.rule];
 
     this.vc.moveToPoint(rule, x, y, true);
@@ -218,28 +223,39 @@ this.ContentControl.prototype = {
 
         for (let eventType of ["mousedown", "mouseup"]) {
           let evt = this.document.createEvent("MouseEvents");
           evt.initMouseEvent(eventType, true, true, this.window,
             x, y, 0, 0, 0, false, false, false, false, 0, null);
           node.dispatchEvent(evt);
         }
       }
+
+      // Action invoked will be presented on checked/selected state change.
+      if (!Utils.getState(aAccessible).contains(States.CHECKABLE) &&
+          !Utils.getState(aAccessible).contains(States.SELECTABLE)) {
+        this._contentScope.get().sendAsyncMessage("AccessFu:Present",
+          Presentation.actionInvoked());
+      }
     };
 
     let focusedAcc = Utils.AccService.getAccessibleFor(
       this.document.activeElement);
     if (focusedAcc && this.vc.position === focusedAcc
         && focusedAcc.role === Roles.ENTRY) {
       let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
+      let oldOffset = accText.caretOffset;
       let newOffset = aMessage.json.offset;
+      let text = accText.getText(0, accText.characterCount);
+
       if (newOffset >= 0 && newOffset <= accText.characterCount) {
         accText.caretOffset = newOffset;
       }
 
+      this.presentCaretChange(text, oldOffset, accText.caretOffset);
       return;
     }
 
     // recursively find a descendant that is activatable.
     let getActivatableDescendant = (aAccessible) => {
       if (aAccessible.actionCount > 0) {
         return aAccessible;
       }
@@ -378,16 +394,25 @@ this.ContentControl.prototype = {
           if (startSel != endSel) {
             editText.cutText(startSel, endSel);
           }
           break;
       }
     }
   },
 
+  presentCaretChange: function cc_presentCaretChange(
+    aText, aOldOffset, aNewOffset) {
+    if (aOldOffset !== aNewOffset) {
+      let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
+        aOldOffset, aOldOffset, true);
+      this._contentScope.get().sendAsyncMessage("AccessFu:Present", msg);
+    }
+  },
+
   getChildCursor: function cc_getChildCursor(aAccessible) {
     let acc = aAccessible || this.vc.position;
     if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
       let domNode = acc.DOMNode;
       let mm = this._childMessageSenders.get(domNode, null);
       if (!mm) {
         mm = Utils.getMessageManager(domNode);
         mm.addWeakMessageListener("AccessFu:MoveCursor", this);
@@ -426,37 +451,48 @@ this.ContentControl.prototype = {
   sendToParent: function cc_sendToParent(aMessage) {
     // XXX: This is a silly way to make a deep copy
     let newJSON = JSON.parse(JSON.stringify(aMessage.json));
     newJSON.origin = "child";
     aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
   },
 
   /**
-   * Move cursor.
+   * Move cursor and/or present its location.
    * aOptions could have any of these fields:
    * - delay: in ms, before actual move is performed. Another autoMove call
    *    would cancel it. Useful if we want to wait for a possible trailing
    *    focus move. Default 0.
    * - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
+   * - forcePresent: present cursor location, whether we move or don't.
    * - moveToFocused: if there is a focused accessible move to that. This takes
    *    precedence over given anchor.
    * - moveMethod: pivot move method to use, default is 'moveNext',
    */
   autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
     this.cancelAutoMove();
 
     let moveFunc = () => {
       let vc = this.vc;
       let acc = aAnchor;
       let rule = aOptions.onScreenOnly ?
         TraversalRules.SimpleOnScreen : TraversalRules.Simple;
+      let forcePresentFunc = () => {
+        if (aOptions.forcePresent) {
+          this._contentScope.get().sendAsyncMessage(
+            "AccessFu:Present", Presentation.pivotChanged(
+              vc.position, null, vc.startOffset, vc.endOffset,
+              Ci.nsIAccessiblePivot.REASON_NONE,
+              Ci.nsIAccessiblePivot.NO_BOUNDARY));
+        }
+      };
 
       if (aOptions.noOpIfOnScreen &&
         Utils.isAliveAndVisible(vc.position, true)) {
+        forcePresentFunc();
         return;
       }
 
       if (aOptions.moveToFocused) {
         acc = Utils.AccService.getAccessibleFor(
           this.document.activeElement) || acc;
       }
 
@@ -468,24 +504,29 @@ this.ContentControl.prototype = {
         moved = vc[moveFirstOrLast ? "moveNext" : moveMethod](rule, acc, true,
                                                               true);
       }
       if (moveFirstOrLast && !moved) {
         // We move to first/last after no anchor move happened or succeeded.
         moved = vc[moveMethod](rule, true);
       }
 
-      this.sendToChild(vc, {
+      let sentToChild = this.sendToChild(vc, {
         name: "AccessFu:AutoMove",
         json: {
           moveMethod: aOptions.moveMethod,
           moveToFocused: aOptions.moveToFocused,
           noOpIfOnScreen: true,
+          forcePresent: true
         }
       }, null, true);
+
+      if (!moved && !sentToChild) {
+        forcePresentFunc();
+      }
     };
 
     if (aOptions.delay) {
       this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
     } else {
       moveFunc();
     }
   },
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -5,73 +5,125 @@
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/accessibility/Utils.jsm");
 ChromeUtils.defineModuleGetter(this, "Logger",
   "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Presentation",
+  "resource://gre/modules/accessibility/Presentation.jsm");
 ChromeUtils.defineModuleGetter(this, "Roles",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "Events",
   "resource://gre/modules/accessibility/Constants.jsm");
 ChromeUtils.defineModuleGetter(this, "States",
   "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "clearTimeout",
+  "resource://gre/modules/Timer.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout",
+  "resource://gre/modules/Timer.jsm");
 
 var EXPORTED_SYMBOLS = ["EventManager"];
 
 function EventManager(aContentScope) {
   this.contentScope = aContentScope;
   this.addEventListener = this.contentScope.addEventListener.bind(
     this.contentScope);
   this.removeEventListener = this.contentScope.removeEventListener.bind(
     this.contentScope);
   this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
     this.contentScope);
+  this.webProgress = this.contentScope.docShell.
+    QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIWebProgress);
 }
 
 this.EventManager.prototype = {
   start: function start() {
     try {
       if (!this._started) {
         Logger.debug("EventManager.start");
 
         this._started = true;
 
         AccessibilityEventObserver.addListener(this);
 
+        this.webProgress.addProgressListener(this,
+          (Ci.nsIWebProgress.NOTIFY_STATE_ALL |
+           Ci.nsIWebProgress.NOTIFY_LOCATION));
+        this.addEventListener("wheel", this, true);
+        this.addEventListener("scroll", this, true);
+        this.addEventListener("resize", this, true);
         this._preDialogPosition = new WeakMap();
       }
+      this.present(Presentation.tabStateChanged(null, "newtab"));
+
     } catch (x) {
       Logger.logException(x, "Failed to start EventManager");
     }
   },
 
   // XXX: Stop is not called when the tab is closed (|TabClose| event is too
   // late). It is only called when the AccessFu is disabled explicitly.
   stop: function stop() {
     if (!this._started) {
       return;
     }
     Logger.debug("EventManager.stop");
     AccessibilityEventObserver.removeListener(this);
     try {
       this._preDialogPosition = new WeakMap();
+      this.webProgress.removeProgressListener(this);
+      this.removeEventListener("wheel", this, true);
+      this.removeEventListener("scroll", this, true);
+      this.removeEventListener("resize", this, true);
     } catch (x) {
       // contentScope is dead.
     } finally {
       this._started = false;
     }
   },
 
   get contentControl() {
     return this.contentScope._jsat_contentControl;
   },
 
+  handleEvent: function handleEvent(aEvent) {
+    Logger.debug(() => {
+      return ["DOMEvent", aEvent.type];
+    });
+
+    // The target could be an element, document or window
+    const win = aEvent.target.ownerGlobal;
+    try {
+      switch (aEvent.type) {
+        case "wheel":
+        {
+          let delta = aEvent.deltaX || aEvent.deltaY;
+          this.contentControl.autoMove(
+           null,
+           { moveMethod: delta > 0 ? "moveNext" : "movePrevious",
+             onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
+          break;
+        }
+        case "scroll":
+          this.present(Presentation.viewportScrolled(win));
+        case "resize":
+        {
+          this.present(Presentation.viewportChanged(win));
+          break;
+        }
+      }
+    } catch (x) {
+      Logger.logException(x, "Error handling DOM event");
+    }
+  },
+
   handleAccEvent: function handleAccEvent(aEvent) {
     Logger.debug(() => {
       return ["A11yEvent", Logger.eventToString(aEvent),
               Logger.accessibleToString(aEvent.accessible)];
     });
 
     // Don't bother with non-content events in firefox.
     if (Utils.MozBuildApp == "browser" &&
@@ -99,47 +151,358 @@ this.EventManager.prototype = {
         if (position && position.role == Roles.INTERNAL_FRAME) {
           break;
         }
 
         // Blur to document if new position is not explicitly focused.
         if (!position || !Utils.getState(position).contains(States.FOCUSED)) {
           aEvent.accessibleDocument.takeFocus();
         }
+
+        this.present(
+          Presentation.pivotChanged(position, event.oldAccessible,
+                                    event.newStartOffset, event.newEndOffset,
+                                    event.reason, event.boundaryType));
+
+        break;
+      }
+      case Events.STATE_CHANGE:
+      {
+        const event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
+        const state = Utils.getState(event);
+        if (state.contains(States.CHECKED)) {
+          this.present(Presentation.checked(aEvent.accessible));
+        } else if (state.contains(States.SELECTED)) {
+          this.present(Presentation.selected(aEvent.accessible));
+        }
         break;
       }
       case Events.NAME_CHANGE:
       {
-        // XXX: Port to Android
+        let acc = aEvent.accessible;
+        if (acc === this.contentControl.vc.position) {
+          this.present(Presentation.nameChanged(acc));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ["text", "all"]);
+          if (liveRegion) {
+            this.present(Presentation.nameChanged(acc, isPolite));
+          }
+        }
         break;
       }
       case Events.SCROLLING_START:
       {
         this.contentControl.autoMove(aEvent.accessible);
         break;
       }
+      case Events.TEXT_CARET_MOVED:
+      {
+        let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+        let caretOffset = aEvent.
+          QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
+
+        // We could get a caret move in an accessible that is not focused,
+        // it doesn't mean we are not on any editable accessible. just not
+        // on this one..
+        let state = Utils.getState(acc);
+        if (state.contains(States.FOCUSED) && state.contains(States.EDITABLE)) {
+          let fromIndex = caretOffset;
+          if (acc.selectionCount) {
+            const [startSel, endSel] = Utils.getTextSelection(acc);
+            fromIndex = startSel == caretOffset ? endSel : startSel;
+          }
+          this.present(Presentation.textSelectionChanged(
+            acc.getText(0, -1), fromIndex, caretOffset, 0, 0,
+            aEvent.isFromUserInput));
+        }
+        break;
+      }
       case Events.SHOW:
       {
-        // XXX: Port to Android
+        this._handleShow(aEvent);
         break;
       }
       case Events.HIDE:
       {
-        // XXX: Port to Android
+        let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
+        this._handleHide(evt);
+        break;
+      }
+      case Events.TEXT_INSERTED:
+      case Events.TEXT_REMOVED:
+      {
+        let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+          ["text", "all"]);
+        if (aEvent.isFromUserInput || liveRegion) {
+          // Handle all text mutations coming from the user or if they happen
+          // on a live region.
+          this._handleText(aEvent, liveRegion, isPolite);
+        }
+        break;
+      }
+      case Events.FOCUS:
+      {
+        // Put vc where the focus is at
+        let acc = aEvent.accessible;
+        if (![Roles.CHROME_WINDOW,
+             Roles.DOCUMENT,
+             Roles.APPLICATION].includes(acc.role)) {
+          this.contentControl.autoMove(acc);
+        }
+
+        this.present(Presentation.focused(acc));
+
+       if (Utils.inTest) {
+        this.sendMsgFunc("AccessFu:Focused");
+       }
+       break;
+      }
+      case Events.DOCUMENT_LOAD_COMPLETE:
+      {
+        let position = this.contentControl.vc.position;
+        // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
+        // event's dialog accessible or accessible document
+        let subtreeRoot = aEvent.accessible.role === Roles.DIALOG ?
+          aEvent.accessible : aEvent.accessibleDocument;
+        if (aEvent.accessible === aEvent.accessibleDocument ||
+            (position && Utils.isInSubtree(position, subtreeRoot))) {
+          // Do not automove into the document if the virtual cursor is already
+          // positioned inside it.
+          break;
+        }
+        this._preDialogPosition.set(aEvent.accessible.DOMNode, position);
+        this.contentControl.autoMove(aEvent.accessible, { delay: 500 });
         break;
       }
+      case Events.TEXT_VALUE_CHANGE:
+        // We handle this events in TEXT_INSERTED/TEXT_REMOVED.
+        break;
       case Events.VALUE_CHANGE:
       {
-        // XXX: Port to Android
-        break;
+        let position = this.contentControl.vc.position;
+        let target = aEvent.accessible;
+        if (position === target ||
+            Utils.getEmbeddedControl(position) === target) {
+          this.present(Presentation.valueChanged(target));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ["text", "all"]);
+          if (liveRegion) {
+            this.present(Presentation.valueChanged(target, isPolite));
+          }
+        }
+      }
+    }
+  },
+
+  _handleShow: function _handleShow(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+      ["additions", "all"]);
+    // Only handle show if it is a relevant live region.
+    if (!liveRegion) {
+      return;
+    }
+    // Show for text is handled by the EVENT_TEXT_INSERTED handler.
+    if (aEvent.accessible.role === Roles.TEXT_LEAF) {
+      return;
+    }
+    this._dequeueLiveEvent(Events.HIDE, liveRegion);
+    this.present(Presentation.liveRegion(liveRegion, isPolite, false));
+  },
+
+  _handleHide: function _handleHide(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(
+      aEvent, ["removals", "all"]);
+    let acc = aEvent.accessible;
+    if (liveRegion) {
+      // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
+      if (acc.role === Roles.TEXT_LEAF) {
+        return;
+      }
+      this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
+    } else {
+      let vc = Utils.getVirtualCursor(this.contentScope.content.document);
+      if (vc.position &&
+        (Utils.getState(vc.position).contains(States.DEFUNCT) ||
+          Utils.isInSubtree(vc.position, acc))) {
+        let position = this._preDialogPosition.get(aEvent.accessible.DOMNode) ||
+          aEvent.targetPrevSibling || aEvent.targetParent;
+        if (!position) {
+          try {
+            position = acc.previousSibling;
+          } catch (x) {
+            // Accessible is unattached from the accessible tree.
+            position = acc.parent;
+          }
+        }
+        this.contentControl.autoMove(position,
+          { moveToFocused: true, delay: 500 });
       }
     }
   },
 
-  QueryInterface: ChromeUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
+  _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
+    let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
+    let isInserted = event.isInserted;
+    let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+
+    let text = "";
+    try {
+      text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+    } catch (x) {
+      // XXX we might have gotten an exception with of a
+      // zero-length text. If we did, ignore it (bug #749810).
+      if (txtIface.characterCount) {
+        throw x;
+      }
+    }
+    // If there are embedded objects in the text, ignore them.
+    // Assuming changes to the descendants would already be handled by the
+    // show/hide event.
+    let modifiedText = event.modifiedText.replace(/\uFFFC/g, "");
+    if (modifiedText != event.modifiedText && !modifiedText.trim()) {
+      return;
+    }
+
+    if (aLiveRegion) {
+      if (aEvent.eventType === Events.TEXT_REMOVED) {
+        this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
+          modifiedText);
+      } else {
+        this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion);
+        this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false,
+          modifiedText));
+      }
+    } else {
+      this.present(Presentation.textChanged(aEvent.accessible, isInserted,
+        event.start, event.length, text, modifiedText));
+    }
+  },
+
+  _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) {
+    if (aEvent.isFromUserInput) {
+      return {};
+    }
+    let parseLiveAttrs = function parseLiveAttrs(aAccessible) {
+      let attrs = Utils.getAttributes(aAccessible);
+      if (attrs["container-live"]) {
+        return {
+          live: attrs["container-live"],
+          relevant: attrs["container-relevant"] || "additions text",
+          busy: attrs["container-busy"],
+          atomic: attrs["container-atomic"],
+          memberOf: attrs["member-of"]
+        };
+      }
+      return null;
+    };
+    // XXX live attributes are not set for hidden accessibles yet. Need to
+    // climb up the tree to check for them.
+    let getLiveAttributes = function getLiveAttributes(aEvent) {
+      let liveAttrs = parseLiveAttrs(aEvent.accessible);
+      if (liveAttrs) {
+        return liveAttrs;
+      }
+      let parent = aEvent.targetParent;
+      while (parent) {
+        liveAttrs = parseLiveAttrs(parent);
+        if (liveAttrs) {
+          return liveAttrs;
+        }
+        parent = parent.parent;
+      }
+      return {};
+    };
+    let {live, relevant, /* busy, atomic, memberOf */ } = getLiveAttributes(aEvent);
+    // If container-live is not present or is set to |off| ignore the event.
+    if (!live || live === "off") {
+      return {};
+    }
+    // XXX: support busy and atomic.
+
+    // Determine if the type of the mutation is relevant. Default is additions
+    // and text.
+    let isRelevant = Utils.matchAttributeValue(relevant, aRelevant);
+    if (!isRelevant) {
+      return {};
+    }
+    return {
+      liveRegion: aEvent.accessible,
+      isPolite: live === "polite"
+    };
+  },
+
+  _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) {
+    let domNode = aLiveRegion.DOMNode;
+    if (this._liveEventQueue && this._liveEventQueue.has(domNode)) {
+      let queue = this._liveEventQueue.get(domNode);
+      let nextEvent = queue[0];
+      if (nextEvent.eventType === aEventType) {
+        clearTimeout(nextEvent.timeoutID);
+        queue.shift();
+        if (queue.length === 0) {
+          this._liveEventQueue.delete(domNode);
+        }
+      }
+    }
+  },
+
+  _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) {
+    if (!this._liveEventQueue) {
+      this._liveEventQueue = new WeakMap();
+    }
+    let eventHandler = {
+      eventType: aEventType,
+      timeoutID: setTimeout(this.present.bind(this),
+        20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
+        Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
+    };
+
+    let domNode = aLiveRegion.DOMNode;
+    if (this._liveEventQueue.has(domNode)) {
+      this._liveEventQueue.get(domNode).push(eventHandler);
+    } else {
+      this._liveEventQueue.set(domNode, [eventHandler]);
+    }
+  },
+
+  present: function present(aPresentationData) {
+    if (aPresentationData && aPresentationData.length > 0) {
+      this.sendMsgFunc("AccessFu:Present", aPresentationData);
+    }
+  },
+
+  onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+    let tabstate = "";
+
+    let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
+      Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+    let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
+      Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+    if ((aStateFlags & loadingState) == loadingState) {
+      tabstate = "loading";
+    } else if ((aStateFlags & loadedState) == loadedState &&
+               !aWebProgress.isLoadingDocument) {
+      tabstate = "loaded";
+    }
+
+    if (tabstate) {
+      let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+      this.present(Presentation.tabStateChanged(docAcc, tabstate));
+    }
+  },
+
+  onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+    this.present(Presentation.tabStateChanged(docAcc, "newdoc"));
+  },
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference, Ci.nsIObserver])
 };
 
 const AccessibilityEventObserver = {
 
   /**
    * A WeakMap containing [content, EventManager] pairs.
    */
   eventManagers: new WeakMap(),
new file mode 100644
--- /dev/null
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -0,0 +1,824 @@
+/* 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/. */
+
+/* exported UtteranceGenerator */
+
+"use strict";
+
+const INCLUDE_DESC = 0x01;
+const INCLUDE_NAME = 0x02;
+const INCLUDE_VALUE = 0x04;
+const NAME_FROM_SUBTREE_RULE = 0x10;
+const IGNORE_EXPLICIT_NAME = 0x20;
+
+const OUTPUT_DESC_FIRST = 0;
+const OUTPUT_DESC_LAST = 1;
+
+ChromeUtils.defineModuleGetter(this, "Utils", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "PrefCache", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Logger", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+
+var EXPORTED_SYMBOLS = ["UtteranceGenerator"]; // jshint ignore:line
+
+var OutputGenerator = {
+
+  defaultOutputOrder: OUTPUT_DESC_LAST,
+
+  /**
+   * Generates output for a PivotContext.
+   * @param {PivotContext} aContext object that generates and caches
+   *    context information for a given accessible and its relationship with
+   *    another accessible.
+   * @return {Object} An array of speech data. Depending on the utterance order,
+   *    the data describes the context for an accessible object either
+   *    starting from the accessible's ancestry or accessible's subtree.
+   */
+  genForContext: function genForContext(aContext) {
+    let output = [];
+    let self = this;
+    let addOutput = function addOutput(aAccessible) {
+      output.push.apply(output, self.genForObject(aAccessible, aContext));
+    };
+    let ignoreSubtree = function ignoreSubtree(aAccessible) {
+      let roleString = Utils.AccService.getStringRole(aAccessible.role);
+      let nameRule = self.roleRuleMap[roleString] || 0;
+      // Ignore subtree if the name is explicit and the role's name rule is the
+      // NAME_FROM_SUBTREE_RULE.
+      return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
+              ((nameRule & NAME_FROM_SUBTREE_RULE) &&
+               (Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
+               !(nameRule & IGNORE_EXPLICIT_NAME))));
+    };
+
+    let contextStart = this._getContextStart(aContext);
+
+    if (this.outputOrder === OUTPUT_DESC_FIRST) {
+      contextStart.forEach(addOutput);
+      addOutput(aContext.accessible);
+      for (let node of aContext.subtreeGenerator(true, ignoreSubtree)) {
+        addOutput(node);
+      }
+    } else {
+      for (let node of aContext.subtreeGenerator(false, ignoreSubtree)) {
+        addOutput(node);
+      }
+      addOutput(aContext.accessible);
+
+      // If there are any documents in new ancestry, find a first one and place
+      // it in the beginning of the utterance.
+      let doc, docIndex = contextStart.findIndex(
+        ancestor => ancestor.role === Roles.DOCUMENT);
+
+      if (docIndex > -1) {
+        doc = contextStart.splice(docIndex, 1)[0];
+      }
+
+      contextStart.reverse().forEach(addOutput);
+      if (doc) {
+        output.unshift.apply(output, self.genForObject(doc, aContext));
+      }
+    }
+
+    return output;
+  },
+
+
+  /**
+   * Generates output for an object.
+   * @param {nsIAccessible} aAccessible accessible object to generate output
+   *    for.
+   * @param {PivotContext} aContext object that generates and caches
+   *    context information for a given accessible and its relationship with
+   *    another accessible.
+   * @return {Array} A 2 element array of speech data. The first element
+   *    describes the object and its state. The second element is the object's
+   *    name. Whether the object's description or it's role is included is
+   *    determined by {@link roleRuleMap}.
+   */
+  genForObject: function genForObject(aAccessible, aContext) {
+    let roleString = Utils.AccService.getStringRole(aAccessible.role);
+    let func = this.objectOutputFunctions[
+      OutputGenerator._getOutputName(roleString)] ||
+      this.objectOutputFunctions.defaultFunc;
+
+    let flags = this.roleRuleMap[roleString] || 0;
+
+    if (aAccessible.childCount === 0) {
+      flags |= INCLUDE_NAME;
+    }
+
+    return func.apply(this, [aAccessible, roleString,
+                             Utils.getState(aAccessible), flags, aContext]);
+  },
+
+  /**
+   * Generates output for an action performed.
+   * @param {nsIAccessible} aAccessible accessible object that the action was
+   *    invoked in.
+   * @param {string} aActionName the name of the action, one of the keys in
+   *    {@link gActionMap}.
+   * @return {Array} A one element array with action data.
+   */
+  genForAction: function genForAction(aObject, aActionName) {}, // jshint ignore:line
+
+  /**
+   * Generates output for an announcement.
+   * @param {string} aAnnouncement unlocalized announcement.
+   * @return {Array} An announcement speech data to be localized.
+   */
+  genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, // jshint ignore:line
+
+  /**
+   * Generates output for a tab state change.
+   * @param {nsIAccessible} aAccessible accessible object of the tab's attached
+   *    document.
+   * @param {string} aTabState the tab state name, see
+   *    {@link Presenter.tabStateChanged}.
+   * @return {Array} The tab state utterace.
+   */
+  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, // jshint ignore:line
+
+  /**
+   * Generates output for announcing entering and leaving editing mode.
+   * @param {aIsEditing} boolean true if we are in editing mode
+   * @return {Array} The mode utterance
+   */
+  genForEditingMode: function genForEditingMode(aIsEditing) {}, // jshint ignore:line
+
+  _getContextStart: function getContextStart(aContext) {}, // jshint ignore:line
+
+  /**
+   * Adds an accessible name and description to the output if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {Number} aFlags output flags.
+   */
+  _addName: function _addName(aOutput, aAccessible, aFlags) {
+    let name;
+    if ((Utils.getAttributes(aAccessible)["explicit-name"] === "true" &&
+         !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
+      name = aAccessible.name;
+    }
+
+    let description = aAccessible.description;
+    if (description) {
+      // Compare against the calculated name unconditionally, regardless of name rule,
+      // so we can make sure we don't speak duplicated descriptions
+      let tmpName = name || aAccessible.name;
+      if (tmpName && (description !== tmpName)) {
+        if (name) {
+          name = this.outputOrder === OUTPUT_DESC_FIRST ?
+            description + " - " + name :
+            name + " - " + description;
+        } else {
+          name = description;
+        }
+      }
+    }
+
+    if (!name || !name.trim()) {
+      return;
+    }
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](name);
+  },
+
+  /**
+   * Adds a landmark role to the output if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   */
+  _addLandmark: function _addLandmark(aOutput, aAccessible) {
+    let landmarkName = Utils.getLandmarkName(aAccessible);
+    if (!landmarkName) {
+      return;
+    }
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "unshift" : "push"]({
+      string: landmarkName
+    });
+  },
+
+  /**
+   * Adds math roles to the output, for a MathML accessible.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {String} aRoleStr aAccessible's role string.
+   */
+  _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
+    // First, determine the actual role to use (e.g. mathmlfraction).
+    let roleStr = aRoleStr;
+    switch (aAccessible.role) {
+      case Roles.MATHML_CELL:
+      case Roles.MATHML_ENCLOSED:
+      case Roles.MATHML_LABELED_ROW:
+      case Roles.MATHML_ROOT:
+      case Roles.MATHML_SQUARE_ROOT:
+      case Roles.MATHML_TABLE:
+      case Roles.MATHML_TABLE_ROW:
+        // Use the default role string.
+        break;
+      case Roles.MATHML_MULTISCRIPTS:
+      case Roles.MATHML_OVER:
+      case Roles.MATHML_SUB:
+      case Roles.MATHML_SUB_SUP:
+      case Roles.MATHML_SUP:
+      case Roles.MATHML_UNDER:
+      case Roles.MATHML_UNDER_OVER:
+        // For scripted accessibles, use the string 'mathmlscripted'.
+        roleStr = "mathmlscripted";
+        break;
+      case Roles.MATHML_FRACTION:
+        // From a semantic point of view, the only important point is to
+        // distinguish between fractions that have a bar and those that do not.
+        // Per the MathML 3 spec, the latter happens iff the linethickness
+        // attribute is of the form [zero-float][optional-unit]. In that case,
+        // we use the string 'mathmlfractionwithoutbar'.
+        let linethickness = Utils.getAttributes(aAccessible).linethickness;
+        if (linethickness) {
+            let numberMatch = linethickness.match(/^(?:\d|\.)+/);
+            if (numberMatch && !parseFloat(numberMatch[0])) {
+                roleStr += "withoutbar";
+            }
+        }
+        break;
+      default:
+        // Otherwise, do not output the actual role.
+        roleStr = null;
+        break;
+    }
+
+    // Get the math role based on the position in the parent accessible
+    // (e.g. numerator for the first child of a mathmlfraction).
+    let mathRole = Utils.getMathRole(aAccessible);
+    if (mathRole) {
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
+        string: this._getOutputName(mathRole)});
+    }
+    if (roleStr) {
+      aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"]({
+        string: this._getOutputName(roleStr)});
+    }
+  },
+
+  /**
+   * Adds MathML menclose notations to the output.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   */
+  _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
+    let notations = Utils.getAttributes(aAccessible).notation || "longdiv";
+    aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"].apply(
+      aOutput, notations.split(" ").map(notation => {
+        return { string: this._getOutputName("notation-" + notation) };
+      }));
+  },
+
+  /**
+   * Adds an entry type attribute to the description if available.
+   * @param {Array} aOutput Output array.
+   * @param {nsIAccessible} aAccessible current accessible object.
+   * @param {String} aRoleStr aAccessible's role string.
+   */
+  _addType: function _addType(aOutput, aAccessible, aRoleStr) {
+    if (aRoleStr !== "entry") {
+      return;
+    }
+
+    let typeName = Utils.getAttributes(aAccessible)["text-input-type"];
+    // Ignore the the input type="text" case.
+    if (!typeName || typeName === "text") {
+      return;
+    }
+    aOutput.push({string: "textInputType_" + typeName});
+  },
+
+  _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line
+
+  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {}, // jshint ignore:line
+
+  get outputOrder() {
+    if (!this._utteranceOrder) {
+      this._utteranceOrder = new PrefCache("accessibility.accessfu.utterance");
+    }
+    return typeof this._utteranceOrder.value === "number" ?
+      this._utteranceOrder.value : this.defaultOutputOrder;
+  },
+
+  _getOutputName: function _getOutputName(aName) {
+    return aName.replace(/\s/g, "");
+  },
+
+  roleRuleMap: {
+    "menubar": INCLUDE_DESC,
+    "scrollbar": INCLUDE_DESC,
+    "grip": INCLUDE_DESC,
+    "alert": INCLUDE_DESC | INCLUDE_NAME,
+    "menupopup": INCLUDE_DESC,
+    "menuitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "tooltip": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "columnheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "rowheader": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "column": NAME_FROM_SUBTREE_RULE,
+    "row": NAME_FROM_SUBTREE_RULE,
+    "cell": INCLUDE_DESC | INCLUDE_NAME,
+    "application": INCLUDE_NAME,
+    "document": INCLUDE_NAME | NAME_FROM_SUBTREE_RULE, // don't use the subtree of entire document
+    "grouping": INCLUDE_DESC | INCLUDE_NAME,
+    "toolbar": INCLUDE_DESC,
+    "table": INCLUDE_DESC | INCLUDE_NAME,
+    "link": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "helpballoon": NAME_FROM_SUBTREE_RULE,
+    "list": INCLUDE_DESC | INCLUDE_NAME,
+    "listitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "outline": INCLUDE_DESC,
+    "outlineitem": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "pagetab": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "graphic": INCLUDE_DESC,
+    "switch": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "pushbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "checkbutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "radiobutton": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "buttondropdown": NAME_FROM_SUBTREE_RULE,
+    "combobox": INCLUDE_DESC | INCLUDE_VALUE,
+    "droplist": INCLUDE_DESC,
+    "progressbar": INCLUDE_DESC | INCLUDE_VALUE,
+    "slider": INCLUDE_DESC | INCLUDE_VALUE,
+    "spinbutton": INCLUDE_DESC | INCLUDE_VALUE,
+    "diagram": INCLUDE_DESC,
+    "animation": INCLUDE_DESC,
+    "equation": INCLUDE_DESC,
+    "buttonmenu": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "buttondropdowngrid": NAME_FROM_SUBTREE_RULE,
+    "pagetablist": INCLUDE_DESC,
+    "canvas": INCLUDE_DESC,
+    "check menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "label": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "password text": INCLUDE_DESC,
+    "popup menu": INCLUDE_DESC,
+    "radio menu item": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "table column header": NAME_FROM_SUBTREE_RULE,
+    "table row header": NAME_FROM_SUBTREE_RULE,
+    "tear off menu item": NAME_FROM_SUBTREE_RULE,
+    "toggle button": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "parent menuitem": NAME_FROM_SUBTREE_RULE,
+    "header": INCLUDE_DESC,
+    "footer": INCLUDE_DESC,
+    "entry": INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
+    "caption": INCLUDE_DESC,
+    "document frame": INCLUDE_DESC,
+    "heading": INCLUDE_DESC,
+    "calendar": INCLUDE_DESC | INCLUDE_NAME,
+    "combobox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "listbox option": INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+    "listbox rich option": NAME_FROM_SUBTREE_RULE,
+    "gridcell": NAME_FROM_SUBTREE_RULE,
+    "check rich option": NAME_FROM_SUBTREE_RULE,
+    "term": NAME_FROM_SUBTREE_RULE,
+    "definition": NAME_FROM_SUBTREE_RULE,
+    "key": NAME_FROM_SUBTREE_RULE,
+    "image map": INCLUDE_DESC,
+    "option": INCLUDE_DESC,
+    "listbox": INCLUDE_DESC,
+    "definitionlist": INCLUDE_DESC | INCLUDE_NAME,
+    "dialog": INCLUDE_DESC | INCLUDE_NAME,
+    "chrome window": IGNORE_EXPLICIT_NAME,
+    "app root": IGNORE_EXPLICIT_NAME,
+    "statusbar": NAME_FROM_SUBTREE_RULE,
+    "mathml table": INCLUDE_DESC | INCLUDE_NAME,
+    "mathml labeled row": NAME_FROM_SUBTREE_RULE,
+    "mathml table row": NAME_FROM_SUBTREE_RULE,
+    "mathml cell": INCLUDE_DESC | INCLUDE_NAME,
+    "mathml fraction": INCLUDE_DESC,
+    "mathml square root": INCLUDE_DESC,
+    "mathml root": INCLUDE_DESC,
+    "mathml enclosed": INCLUDE_DESC,
+    "mathml sub": INCLUDE_DESC,
+    "mathml sup": INCLUDE_DESC,
+    "mathml sub sup": INCLUDE_DESC,
+    "mathml under": INCLUDE_DESC,
+    "mathml over": INCLUDE_DESC,
+    "mathml under over": INCLUDE_DESC,
+    "mathml multiscripts": INCLUDE_DESC,
+    "mathml identifier": INCLUDE_DESC,
+    "mathml number": INCLUDE_DESC,
+    "mathml operator": INCLUDE_DESC,
+    "mathml text": INCLUDE_DESC,
+    "mathml string literal": INCLUDE_DESC,
+    "mathml row": INCLUDE_DESC,
+    "mathml style": INCLUDE_DESC,
+    "mathml error": INCLUDE_DESC },
+
+  mathmlRolesSet: new Set([
+    Roles.MATHML_MATH,
+    Roles.MATHML_IDENTIFIER,
+    Roles.MATHML_NUMBER,
+    Roles.MATHML_OPERATOR,
+    Roles.MATHML_TEXT,
+    Roles.MATHML_STRING_LITERAL,
+    Roles.MATHML_GLYPH,
+    Roles.MATHML_ROW,
+    Roles.MATHML_FRACTION,
+    Roles.MATHML_SQUARE_ROOT,
+    Roles.MATHML_ROOT,
+    Roles.MATHML_FENCED,
+    Roles.MATHML_ENCLOSED,
+    Roles.MATHML_STYLE,
+    Roles.MATHML_SUB,
+    Roles.MATHML_SUP,
+    Roles.MATHML_SUB_SUP,
+    Roles.MATHML_UNDER,
+    Roles.MATHML_OVER,
+    Roles.MATHML_UNDER_OVER,
+    Roles.MATHML_MULTISCRIPTS,
+    Roles.MATHML_TABLE,
+    Roles.LABELED_ROW,
+    Roles.MATHML_TABLE_ROW,
+    Roles.MATHML_CELL,
+    Roles.MATHML_ACTION,
+    Roles.MATHML_ERROR,
+    Roles.MATHML_STACK,
+    Roles.MATHML_LONG_DIVISION,
+    Roles.MATHML_STACK_GROUP,
+    Roles.MATHML_STACK_ROW,
+    Roles.MATHML_STACK_CARRIES,
+    Roles.MATHML_STACK_CARRY,
+    Roles.MATHML_STACK_LINE
+  ]),
+
+  objectOutputFunctions: {
+    _generateBaseOutput:
+      function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
+        let output = [];
+
+        if (aFlags & INCLUDE_DESC) {
+          this._addState(output, aState, aRoleStr);
+          this._addType(output, aAccessible, aRoleStr);
+          this._addRole(output, aAccessible, aRoleStr);
+        }
+
+        if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
+          output[this.outputOrder === OUTPUT_DESC_FIRST ? "push" : "unshift"](
+            aAccessible.value);
+        }
+
+        this._addName(output, aAccessible, aFlags);
+        this._addLandmark(output, aAccessible);
+
+        return output;
+      },
+
+    label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
+      if (aContext.isNestedControl ||
+          aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
+        // If we are on a nested control, or a nesting label,
+        // we don't need the context.
+        return [];
+      }
+
+      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+    },
+
+    entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
+      let rolestr = aState.contains(States.MULTI_LINE) ? "textarea" : "entry";
+      return this.objectOutputFunctions.defaultFunc.apply(
+        this, [aAccessible, rolestr, aState, aFlags]);
+    },
+
+    pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
+      let itemno = {};
+      let itemof = {};
+      aAccessible.groupPosition({}, itemof, itemno);
+      let output = [];
+      this._addState(output, aState);
+      this._addRole(output, aAccessible, aRoleStr);
+      output.push({
+        string: "objItemOfN",
+        args: [itemno.value, itemof.value]
+      });
+
+      this._addName(output, aAccessible, aFlags);
+      this._addLandmark(output, aAccessible);
+
+      return output;
+    },
+
+    table: function table(aAccessible, aRoleStr, aState, aFlags) {
+      let output = [];
+      let table;
+      try {
+        table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
+      } catch (x) {
+        Logger.logException(x);
+        return output;
+      } finally {
+        // Check if it's a layout table, and bail out if true.
+        // We don't want to speak any table information for layout tables.
+        if (table.isProbablyForLayout()) {
+          return output;
+        }
+        this._addRole(output, aAccessible, aRoleStr);
+        output.push.call(output, {
+          string: this._getOutputName("tblColumnInfo"),
+          count: table.columnCount
+        }, {
+          string: this._getOutputName("tblRowInfo"),
+          count: table.rowCount
+        });
+        this._addName(output, aAccessible, aFlags);
+        this._addLandmark(output, aAccessible);
+        return output;
+      }
+    },
+
+    gridcell: function gridcell(aAccessible, aRoleStr, aState, aFlags) {
+      let output = [];
+      this._addState(output, aState);
+      this._addName(output, aAccessible, aFlags);
+      this._addLandmark(output, aAccessible);
+      return output;
+    },
+
+    // Use the table output functions for MathML tabular elements.
+    mathmltable: function mathmltable() {
+      return this.objectOutputFunctions.table.apply(this, arguments);
+    },
+
+    mathmlcell: function mathmlcell() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    mathmlenclosed: function mathmlenclosed(aAccessible, aRoleStr, aState,
+                                            aFlags, aContext) {
+      let output = this.objectOutputFunctions.defaultFunc.
+        apply(this, [aAccessible, aRoleStr, aState, aFlags, aContext]);
+      this._addMencloseNotations(output, aAccessible);
+      return output;
+    }
+  }
+};
+
+/**
+ * Generates speech utterances from objects, actions and state changes.
+ * An utterance is an array of speech data.
+ *
+ * It should not be assumed that flattening an utterance array would create a
+ * gramatically correct sentence. For example, {@link genForObject} might
+ * return: ['graphic', 'Welcome to my home page'].
+ * Each string element in an utterance should be gramatically correct in itself.
+ * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
+ *
+ * An utterance is ordered from the least to the most important. Speaking the
+ * last string usually makes sense, but speaking the first often won't.
+ * For example {@link genForAction} might return ['button', 'clicked'] for a
+ * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
+ * not.
+ */
+var UtteranceGenerator = {  // jshint ignore:line
+  __proto__: OutputGenerator, // jshint ignore:line
+
+  gActionMap: {
+    jump: "jumpAction",
+    press: "pressAction",
+    check: "checkAction",
+    uncheck: "uncheckAction",
+    on: "onAction",
+    off: "offAction",
+    select: "selectAction",
+    unselect: "unselectAction",
+    open: "openAction",
+    close: "closeAction",
+    switch: "switchAction",
+    click: "clickAction",
+    collapse: "collapseAction",
+    expand: "expandAction",
+    activate: "activateAction",
+    cycle: "cycleAction"
+  },
+
+  // TODO: May become more verbose in the future.
+  genForAction: function genForAction(aObject, aActionName) {
+    return [{string: this.gActionMap[aActionName]}];
+  },
+
+  genForLiveRegion:
+    function genForLiveRegion(aContext, aIsHide, aModifiedText) {
+      let utterance = [];
+      if (aIsHide) {
+        utterance.push({string: "hidden"});
+      }
+      return utterance.concat(aModifiedText || this.genForContext(aContext));
+    },
+
+  genForAnnouncement: function genForAnnouncement(aAnnouncement) {
+    return [{
+      string: aAnnouncement
+    }];
+  },
+
+  genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
+    switch (aTabState) {
+      case "newtab":
+        return [{string: "tabNew"}];
+      case "loading":
+        return [{string: "tabLoading"}];
+      case "loaded":
+        return [aObject.name, {string: "tabLoaded"}];
+      case "loadstopped":
+        return [{string: "tabLoadStopped"}];
+      case "reload":
+        return [{string: "tabReload"}];
+      default:
+        return [];
+    }
+  },
+
+  genForEditingMode: function genForEditingMode(aIsEditing) {
+    return [{string: aIsEditing ? "editingMode" : "navigationMode"}];
+  },
+
+  objectOutputFunctions: {
+
+    __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
+
+    defaultFunc: function defaultFunc() {
+      return this.objectOutputFunctions._generateBaseOutput.apply(
+        this, arguments);
+    },
+
+    heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
+      let level = {};
+      aAccessible.groupPosition(level, {}, {});
+      let utterance = [{string: "headingLevel", args: [level.value]}];
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
+      let itemno = {};
+      let itemof = {};
+      aAccessible.groupPosition({}, itemof, itemno);
+      let utterance = [];
+      if (itemno.value == 1) {
+        // Start of list
+        utterance.push({string: "listStart"});
+      } else if (itemno.value == itemof.value) {
+        // last item
+        utterance.push({string: "listEnd"});
+      }
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    list: function list(aAccessible, aRoleStr, aState, aFlags) {
+      return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+        aAccessible.childCount);
+    },
+
+    definitionlist:
+      function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
+        return this._getListUtterance(aAccessible, aRoleStr, aFlags,
+          aAccessible.childCount / 2);
+      },
+
+    application: function application(aAccessible, aRoleStr, aState, aFlags) {
+      // Don't utter location of applications, it gets tiring.
+      if (aAccessible.name != aAccessible.DOMNode.location) {
+        return this.objectOutputFunctions.defaultFunc.apply(this,
+          [aAccessible, aRoleStr, aState, aFlags]);
+      }
+
+      return [];
+    },
+
+    cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
+      let utterance = [];
+      let cell = aContext.getCellInfo(aAccessible);
+      if (cell) {
+        let addCellChanged =
+          function addCellChanged(aUtterance, aChanged, aString, aIndex) {
+            if (aChanged) {
+              aUtterance.push({string: aString, args: [aIndex + 1]});
+            }
+          };
+        let addExtent = function addExtent(aUtterance, aExtent, aString) {
+          if (aExtent > 1) {
+            aUtterance.push({string: aString, args: [aExtent]});
+          }
+        };
+        let addHeaders = function addHeaders(aUtterance, aHeaders) {
+          if (aHeaders.length > 0) {
+            aUtterance.push.apply(aUtterance, aHeaders);
+          }
+        };
+
+        addCellChanged(utterance, cell.columnChanged, "columnInfo",
+          cell.columnIndex);
+        addCellChanged(utterance, cell.rowChanged, "rowInfo", cell.rowIndex);
+
+        addExtent(utterance, cell.columnExtent, "spansColumns");
+        addExtent(utterance, cell.rowExtent, "spansRows");
+
+        addHeaders(utterance, cell.columnHeaders);
+        addHeaders(utterance, cell.rowHeaders);
+      }
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    },
+
+    columnheader: function columnheader() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    rowheader: function rowheader() {
+      return this.objectOutputFunctions.cell.apply(this, arguments);
+    },
+
+    statictext: function statictext(aAccessible) {
+      if (Utils.isListItemDecorator(aAccessible, true)) {
+        return [];
+      }
+
+      return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+    }
+  },
+
+  _getContextStart: function _getContextStart(aContext) {
+    return aContext.newAncestry;
+  },
+
+  _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {
+    if (this.mathmlRolesSet.has(aAccessible.role)) {
+      this._addMathRoles(aOutput, aAccessible, aRoleStr);
+    } else {
+      aOutput.push({string: this._getOutputName(aRoleStr)});
+    }
+  },
+
+  /**
+   * Add localized state information to output data.
+   * Note: We do not expose checked and selected states, we let TalkBack do it for us
+   * there. This is because we expose the checked information on the node info itself.
+   */
+  _addState: function _addState(aOutput, aState, aRoleStr) {
+    if (aState.contains(States.UNAVAILABLE)) {
+      aOutput.push({string: "stateUnavailable"});
+    }
+
+    if (aState.contains(States.READONLY)) {
+      aOutput.push({string: "stateReadonly"});
+    }
+
+    if (aState.contains(States.PRESSED)) {
+      aOutput.push({string: "statePressed"});
+    }
+
+    if (aState.contains(States.EXPANDABLE)) {
+      let statetr = aState.contains(States.EXPANDED) ?
+        "stateExpanded" : "stateCollapsed";
+      aOutput.push({string: statetr});
+    }
+
+    if (aState.contains(States.REQUIRED)) {
+      aOutput.push({string: "stateRequired"});
+    }
+
+    if (aState.contains(States.TRAVERSED)) {
+      aOutput.push({string: "stateTraversed"});
+    }
+
+    if (aState.contains(States.HASPOPUP)) {
+      aOutput.push({string: "stateHasPopup"});
+    }
+  },
+
+  _getListUtterance:
+    function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
+      let utterance = [];
+      this._addRole(utterance, aAccessible, aRoleStr);
+      utterance.push({
+        string: this._getOutputName("listItemsCount"),
+        count: aItemCount
+      });
+
+      this._addName(utterance, aAccessible, aFlags);
+      this._addLandmark(utterance, aAccessible);
+
+      return utterance;
+    }
+};
new file mode 100644
--- /dev/null
+++ b/accessible/jsat/Presentation.jsm
@@ -0,0 +1,332 @@
+/* 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/. */
+
+/* exported Presentation */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "PivotContext", // jshint ignore:line
+  "resource://gre/modules/accessibility/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "UtteranceGenerator", // jshint ignore:line
+  "resource://gre/modules/accessibility/OutputGenerator.jsm");
+ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "Roles", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+ChromeUtils.defineModuleGetter(this, "AndroidEvents", // jshint ignore:line
+  "resource://gre/modules/accessibility/Constants.jsm");
+
+var EXPORTED_SYMBOLS = ["Presentation"]; // jshint ignore:line
+
+const EDIT_TEXT_ROLES = new Set([
+  Roles.SPINBUTTON, Roles.PASSWORD_TEXT,
+  Roles.AUTOCOMPLETE, Roles.ENTRY, Roles.EDITCOMBOBOX]);
+
+class AndroidPresentor {
+  constructor() {
+    this.type = "Android";
+    this.displayedAccessibles = new WeakMap();
+  }
+
+  /**
+   * The virtual cursor's position changed.
+   * @param {PivotContext} aContext the context object for the new pivot
+   *   position.
+   * @param {int} aReason the reason for the pivot change.
+   *   See nsIAccessiblePivot.
+   * @param {bool} aBoundaryType the boundary type for the text movement
+   * or NO_BOUNDARY if it was not a text movement. See nsIAccessiblePivot.
+   */
+  pivotChanged(aPosition, aOldPosition, aStartOffset, aEndOffset, aReason, aBoundaryType) {
+    let context = new PivotContext(
+      aPosition, aOldPosition, aStartOffset, aEndOffset);
+    if (!context.accessible) {
+      return null;
+    }
+
+    let androidEvents = [];
+
+    const isExploreByTouch = aReason == Ci.nsIAccessiblePivot.REASON_POINT;
+
+    if (isExploreByTouch) {
+      // This isn't really used by TalkBack so this is a half-hearted attempt
+      // for now.
+      androidEvents.push({eventType: AndroidEvents.VIEW_HOVER_EXIT, text: []});
+    }
+
+    if (aPosition != aOldPosition) {
+      let info = this._infoFromContext(context);
+      let eventType = isExploreByTouch ?
+        AndroidEvents.VIEW_HOVER_ENTER :
+        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED;
+      androidEvents.push({...info, eventType});
+
+      try {
+        context.accessibleForBounds.scrollTo(
+          Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+      } catch (e) {}
+    }
+
+    if (aBoundaryType != Ci.nsIAccessiblePivot.NO_BOUNDARY) {
+      const adjustedText = context.textAndAdjustedOffsets;
+
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+        text: [adjustedText.text],
+        fromIndex: adjustedText.startOffset,
+        toIndex: adjustedText.endOffset
+      });
+
+      aPosition.QueryInterface(Ci.nsIAccessibleText).scrollSubstringTo(
+        aStartOffset, aEndOffset,
+        Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+    }
+
+    if (context.accessible) {
+      this.displayedAccessibles.set(context.accessible.document.window, context);
+    }
+
+    return androidEvents;
+  }
+
+  focused(aObject) {
+    let info = this._infoFromContext(
+      new PivotContext(aObject, null, -1, -1, true, false));
+    return [{ eventType: AndroidEvents.VIEW_FOCUSED, ...info }];
+  }
+
+  /**
+   * An object's check action has been invoked.
+   * Note: Checkable objects use TalkBack's text derived from the event state, so we don't
+   * populate the text here.
+   * @param {nsIAccessible} aAccessible the object that has been invoked.
+   */
+  checked(aAccessible) {
+    return [{
+      eventType: AndroidEvents.VIEW_CLICKED,
+      checked: Utils.getState(aAccessible).contains(States.CHECKED)
+    }];
+  }
+
+  /**
+   * An object's select action has been invoked.
+   * @param {nsIAccessible} aAccessible the object that has been invoked.
+   */
+  selected(aAccessible) {
+    return [{
+      eventType: AndroidEvents.VIEW_SELECTED,
+      selected: Utils.getState(aAccessible).contains(States.SELECTED)
+    }];
+  }
+
+  /**
+   * An object's action has been invoked.
+   */
+  actionInvoked() {
+    return [{ eventType: AndroidEvents.VIEW_CLICKED }];
+  }
+
+  /**
+   * Text has changed, either by the user or by the system. TODO.
+   */
+  textChanged(aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
+    let androidEvent = {
+      eventType: AndroidEvents.VIEW_TEXT_CHANGED,
+      text: [aText],
+      fromIndex: aStart,
+      removedCount: 0,
+      addedCount: 0
+    };
+
+    if (aIsInserted) {
+      androidEvent.addedCount = aLength;
+      androidEvent.beforeText =
+        aText.substring(0, aStart) + aText.substring(aStart + aLength);
+    } else {
+      androidEvent.removedCount = aLength;
+      androidEvent.beforeText =
+        aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
+    }
+
+    return [androidEvent];
+  }
+
+  /**
+   * Text selection has changed. TODO.
+   */
+  textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {
+    let androidEvents = [];
+
+    if (aIsFromUserInput) {
+      let [from, to] = aOldStart < aStart ?
+        [aOldStart, aStart] : [aStart, aOldStart];
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+        text: [aText],
+        fromIndex: from,
+        toIndex: to
+      });
+    } else {
+      androidEvents.push({
+        eventType: AndroidEvents.VIEW_TEXT_SELECTION_CHANGED,
+        text: [aText],
+        fromIndex: aStart,
+        toIndex: aEnd,
+        itemCount: aText.length
+      });
+    }
+
+    return androidEvents;
+  }
+
+  /**
+   * Selection has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aObject the object that has been selected.
+   */
+  selectionChanged(aObject) {
+    return ["todo.selection-changed"];
+  }
+
+  /**
+   * Name has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aAccessible the object whose value has changed.
+   */
+  nameChanged(aAccessible) {
+    return ["todo.name-changed"];
+  }
+
+  /**
+   * Value has changed.
+   * XXX: Implement android event?
+   * @param {nsIAccessible} aAccessible the object whose value has changed.
+   */
+  valueChanged(aAccessible) {
+    return ["todo.value-changed"];
+  }
+
+  /**
+   * The tab, or the tab's document state has changed.
+   * @param {nsIAccessible} aDocObj the tab document accessible that has had its
+   *    state changed, or null if the tab has no associated document yet.
+   * @param {string} aPageState the state name for the tab, valid states are:
+   *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
+   */
+  tabStateChanged(aDocObj, aPageState) {
+    return this.announce(
+      UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
+  }
+
+  /**
+   * The viewport has changed because of scroll.
+   * @param {Window} aWindow window of viewport that changed.
+   */
+  viewportScrolled(aWindow) {
+    const { windowUtils, devicePixelRatio } = aWindow;
+    const resolution = { value: 1 };
+    windowUtils.getResolution(resolution);
+    const scale = devicePixelRatio * resolution.value;
+    return [{
+      eventType: AndroidEvents.VIEW_SCROLLED,
+      scrollX: aWindow.scrollX * scale,
+      scrollY: aWindow.scrollY * scale,
+      maxScrollX: aWindow.scrollMaxX * scale,
+      maxScrollY: aWindow.scrollMaxY * scale,
+    }];
+  }
+
+  /**
+   * The viewport has changed, either a pan, zoom, or landscape/portrait toggle.
+   * @param {Window} aWindow window of viewport that changed.
+   */
+  viewportChanged(aWindow) {
+    const currentContext = this.displayedAccessibles.get(aWindow);
+    if (!currentContext) {
+      return;
+    }
+
+    const currentAcc = currentContext.accessibleForBounds;
+    if (Utils.isAliveAndVisible(currentAcc)) {
+      return [{
+        eventType: AndroidEvents.WINDOW_CONTENT_CHANGED,
+        bounds: Utils.getBounds(currentAcc)
+      }];
+    }
+  }
+
+  /**
+   * Announce something. Typically an app state change.
+   */
+  announce(aAnnouncement) {
+    let localizedAnnouncement = Utils.localize(aAnnouncement).join(" ");
+    return [{
+      eventType: AndroidEvents.ANNOUNCEMENT,
+      text: [localizedAnnouncement],
+      addedCount: localizedAnnouncement.length,
+      removedCount: 0,
+      fromIndex: 0
+    }];
+  }
+
+
+  /**
+   * User tried to move cursor forward or backward with no success.
+   * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
+   */
+  noMove(aMoveMethod) {
+    return [{
+      eventType: AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
+      exitView: aMoveMethod,
+      text: [""]
+    }];
+  }
+
+  /**
+   * Announce a live region.
+   * @param  {PivotContext} aContext context object for an accessible.
+   * @param  {boolean} aIsPolite A politeness level for a live region.
+   * @param  {boolean} aIsHide An indicator of hide/remove event.
+   * @param  {string} aModifiedText Optional modified text.
+   */
+  liveRegion(aAccessible, aIsPolite, aIsHide, aModifiedText) {
+    let context = !aModifiedText ?
+      new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide) : null;
+    return this.announce(
+      UtteranceGenerator.genForLiveRegion(context, aIsHide, aModifiedText));
+  }
+
+  _infoFromContext(aContext) {
+    const state = Utils.getState(aContext.accessible);
+    const info = {
+      bounds: aContext.bounds,
+      focusable: state.contains(States.FOCUSABLE),
+      focused: state.contains(States.FOCUSED),
+      clickable: aContext.accessible.actionCount > 0,
+      checkable: state.contains(States.CHECKABLE),
+      checked: state.contains(States.CHECKED),
+      editable: state.contains(States.EDITABLE),
+      selected: state.contains(States.SELECTED)
+    };
+
+    if (EDIT_TEXT_ROLES.has(aContext.accessible.role)) {
+      let textAcc = aContext.accessible.QueryInterface(Ci.nsIAccessibleText);
+      return {
+        ...info,
+        className: "android.widget.EditText",
+        hint: aContext.accessible.name,
+        text: [textAcc.getText(0, -1)]
+      };
+    }
+
+    return {
+      ...info,
+      className: "android.view.View",
+      text: Utils.localize(UtteranceGenerator.genForContext(aContext)),
+    };
+  }
+}
+
+const Presentation = new AndroidPresentor();
--- a/accessible/jsat/moz.build
+++ b/accessible/jsat/moz.build
@@ -4,13 +4,15 @@
 # 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.accessibility += [
     'AccessFu.jsm',
     'Constants.jsm',
     'ContentControl.jsm',
     'EventManager.jsm',
+    'OutputGenerator.jsm',
+    'Presentation.jsm',
     'Traversal.jsm',
     'Utils.jsm'
 ]
 
 JAR_MANIFESTS += ['jar.mn']
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -5,33 +5,24 @@ support-files =
   doc_traversal.html
   doc_content_integration.html
   doc_content_text.html
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/moz.png
 
 [test_alive.html]
 [test_content_integration.html]
-skip-if = true
 [test_explicit_names.html]
-skip-if = true
 [test_hints.html]
-skip-if = true
 [test_landmarks.html]
-skip-if = true
 [test_live_regions.html]
-skip-if = true
 [test_output_mathml.html]
-skip-if = true
 [test_output.html]
-skip-if = true
 [test_tables.html]
-skip-if = true
 [test_text_editable_navigation.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_editing.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_navigation_focus.html]
-skip-if = true
+skip-if = (verify && !debug && (os == 'linux'))
 [test_text_navigation.html]
-skip-if = true
 [test_traversal.html]
 [test_traversal_helper.html]
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -91,17 +91,17 @@
               <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.fastBlock.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.fastBlock.blocked.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
                      onclick="ContentBlocking.openPreferences('identityPopup-CB-fastblock');">&contentBlocking.fastBlock.add.label;</label>
             </hbox>
             <hbox id="identity-popup-content-blocking-category-tracking-protection"
                   class="identity-popup-content-blocking-category" align="center" role="group">
               <image class="identity-popup-content-blocking-category-icon tracking-protection-icon"/>
-              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.trackingProtection2.label;</label>
+              <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.trackingProtection3.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.trackingProtection.blocked.label;</label>
               <label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
                      onclick="ContentBlocking.openPreferences('identityPopup-CB-tracking-protection');">&contentBlocking.trackingProtection.add.label;</label>
             </hbox>
             <hbox id="identity-popup-content-blocking-category-3rdpartycookies"
                   class="identity-popup-content-blocking-category" align="center" role="group">
               <image class="identity-popup-content-blocking-category-icon thirdpartycookies-icon"/>
               <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.3rdPartyCookies.label;</label>
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -83,17 +83,17 @@
         </vbox>
       </hbox>
       <vbox>
         <hbox class="content-blocking-category tracking-protection-ui">
           <vbox>
             <checkbox id="contentBlockingTrackingProtectionCheckbox"
                       class="content-blocking-checkbox" flex="1"
                       src="chrome://browser/skin/controlcenter/trackers.svg"
-                      data-l10n-id="content-blocking-tracking-protection-all-detected-trackers-label"/>
+                      data-l10n-id="content-blocking-tracking-protection-trackers-label"/>
             <vbox class="content-blocking-category-labels" flex="1">
               <hbox id="contentBlockingTrackingProtectionExtensionContentLabel"
                     align="center" hidden="true" class="extension-controlled">
                 <description control="contentBlockingDisableTrackingProtectionExtension" flex="1"/>
                 <button id="contentBlockingDisableTrackingProtectionExtension"
                         class="extension-controlled-button accessory-button"
                         data-l10n-id="disable-extension" hidden="true"/>
               </hbox>
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -45,57 +45,46 @@ class UrlbarInput {
     this.panel = options.panel;
     this.window = this.textbox.ownerGlobal;
     this.controller = options.controller || new UrlbarController();
     this.view = new UrlbarView(this);
     this.valueIsTyped = false;
     this.userInitiatedFocus = false;
     this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
 
+    // Forward textbox methods and properties.
     const METHODS = ["addEventListener", "removeEventListener",
       "setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
       "focus", "blur", "select"];
-    const READ_ONLY_PROPERTIES = ["focused", "inputField", "editor"];
-    const READ_WRITE_PROPERTIES = ["value", "placeholder", "readOnly",
+    const READ_ONLY_PROPERTIES = ["inputField", "editor"];
+    const READ_WRITE_PROPERTIES = ["placeholder", "readOnly",
       "selectionStart", "selectionEnd"];
 
     for (let method of METHODS) {
       this[method] = (...args) => {
         this.textbox[method](...args);
       };
     }
 
     for (let property of READ_ONLY_PROPERTIES) {
       Object.defineProperty(this, property, {
         enumerable: true,
         get() {
-          let getter = "_get_" + property;
-          if (getter in this) {
-            return this[getter]();
-          }
           return this.textbox[property];
         },
       });
     }
 
     for (let property of READ_WRITE_PROPERTIES) {
       Object.defineProperty(this, property, {
         enumerable: true,
         get() {
-          let getter = "_get_" + property;
-          if (getter in this) {
-            return this[getter]();
-          }
           return this.textbox[property];
         },
         set(val) {
-          let setter = "_set_" + property;
-          if (setter in this) {
-            return this[setter](val);
-          }
           return this.textbox[property] = val;
         },
       });
     }
 
     XPCOMUtils.defineLazyGetter(this, "valueFormatter", () => {
       return new UrlbarValueFormatter(this);
     });
@@ -108,22 +97,25 @@ class UrlbarInput {
     this.inputField.addEventListener("overflow", this);
     this.inputField.addEventListener("underflow", this);
     this.inputField.addEventListener("scrollend", this);
     this.inputField.addEventListener("select", this);
 
     this.inputField.controllers.insertControllerAt(0, new CopyCutController(this));
   }
 
-  /* Shortens the given value, usually by removing http:// and trailing slashes,
+  /**
+   * Shortens the given value, usually by removing http:// and trailing slashes,
    * such that calling nsIURIFixup::createFixupURI with the result will produce
    * the same URI.
    *
    * @param {string} val
    *   The string to be trimmed if it appears to be URI
+   * @returns {string}
+   *   The trimmed string
    */
   trimValue(val) {
     return UrlbarPrefs.get("trimURLs") ? this.window.trimURL(val) : val;
   }
 
   /**
    * Applies styling to the text in the urlbar input, depending on the text.
    */
@@ -174,21 +166,25 @@ class UrlbarInput {
       this[methodName](event);
     } else {
       throw "Unrecognized urlbar event: " + event.type;
     }
   }
 
   // Getters and Setters below.
 
-  _get_focused() {
-    return this.inputField.getAttribute("focused") == "true";
+  get focused() {
+    return this.textbox.getAttribute("focused") == "true";
   }
 
-  _set_value(val) {
+  get value() {
+    return this.inputField.value;
+  }
+
+  set value(val) {
     val = this.trimValue(val);
 
     this.valueIsTyped = false;
     this.inputField.value = val;
     this.formatValue();
 
     return val;
   }
--- a/browser/components/urlbar/UrlbarValueFormatter.jsm
+++ b/browser/components/urlbar/UrlbarValueFormatter.jsm
@@ -21,28 +21,33 @@ XPCOMUtils.defineLazyModuleGetters(this,
 class UrlbarValueFormatter {
   /**
    * @param {UrlbarInput} urlbarInput
    */
   constructor(urlbarInput) {
     this.urlbarInput = urlbarInput;
     this.window = this.urlbarInput.window;
     this.document = this.window.document;
-    this.editor = this.urlbarInput.editor;
-    this.inputField = this.urlbarInput.inputField;
-    this.scheme =
-      this.document.getAnonymousElementByAttribute(this.urlbarInput.textbox, "anonid", "scheme");
 
     // This is used only as an optimization to avoid removing formatting in
     // the _remove* format methods when no formatting is actually applied.
     this._formattingApplied = false;
   }
 
+  get inputField() {
+    return this.urlbarInput.inputField;
+  }
+
+  get scheme() {
+    return this.document.getAnonymousElementByAttribute(
+      this.urlbarInput.textbox, "anonid", "scheme");
+  }
+
   update() {
-    if (!this.editor || !this.editor.rootElement.firstChild.textContent) {
+    if (!this.inputField.value) {
       return;
     }
 
     // Remove the current formatting.
     this._removeURLFormat();
     this._removeSearchAliasFormat();
 
     // Apply new formatting.  Formatter methods should return true if they
@@ -127,17 +132,17 @@ class UrlbarValueFormatter {
     return { preDomain, schemeWSlashes, domain, url, uriInfo, trimmedLength };
   }
 
   _removeURLFormat() {
     this.scheme.value = "";
     if (!this._formattingApplied) {
       return;
     }
-    let controller = this.editor.selectionController;
+    let controller = this.urlbarInput.editor.selectionController;
     let strikeOut =
       controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
     strikeOut.removeAllRanges();
     let selection =
       controller.getSelection(controller.SELECTION_URLSECONDARY);
     selection.removeAllRanges();
     this._formatScheme(controller.SELECTION_URLSTRIKEOUT, true);
     this._formatScheme(controller.SELECTION_URLSECONDARY, true);
@@ -168,21 +173,22 @@ class UrlbarValueFormatter {
     }
 
     this.ensureFormattedHostVisible(urlMetaData);
 
     if (!UrlbarPrefs.get("formatting.enabled")) {
       return false;
     }
 
-    let controller = this.editor.selectionController;
+    let editor = this.urlbarInput.editor;
+    let controller = editor.selectionController;
 
     this._formatScheme(controller.SELECTION_URLSECONDARY);
 
-    let textNode = this.editor.rootElement.firstChild;
+    let textNode = editor.rootElement.firstChild;
 
     // Strike out the "https" part if mixed active content is loaded.
     if (this.urlbarInput.getAttribute("pageproxystate") == "valid" &&
         url.startsWith("https:") &&
         this.window.gBrowser.securityUI.state &
           Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
       let range = this.document.createRange();
       range.setStart(textNode, 0);
@@ -243,17 +249,17 @@ class UrlbarValueFormatter {
       selection.addRange(r);
     }
   }
 
   _removeSearchAliasFormat() {
     if (!this._formattingApplied) {
       return;
     }
-    let selection = this.editor.selectionController.getSelection(
+    let selection = this.urlbarInput.editor.selectionController.getSelection(
       Ci.nsISelectionController.SELECTION_FIND
     );
     selection.removeAllRanges();
   }
 
   /**
    * If the input value starts with a search alias, this formatter method
    * highlights it.
@@ -284,27 +290,28 @@ class UrlbarValueFormatter {
       !popup.oneOffSearchButtons.selectedButton &&
       heuristicItem &&
       heuristicItem.getAttribute("actiontype") == "searchengine" &&
       this.urlbarInput._parseActionUrl(heuristicItem.getAttribute("url")).params.alias;
     if (!alias) {
       return false;
     }
 
-    let textNode = this.editor.rootElement.firstChild;
+    let editor = this.urlbarInput.editor;
+    let textNode = editor.rootElement.firstChild;
     let value = textNode.textContent;
 
     let index = value.indexOf(alias);
     if (index < 0) {
       return false;
     }
 
     // We abuse the SELECTION_FIND selection type to do our highlighting.
     // It's the only type that works with Selection.setColors().
-    let selection = this.editor.selectionController.getSelection(
+    let selection = editor.selectionController.getSelection(
       Ci.nsISelectionController.SELECTION_FIND
     );
 
     let range = this.document.createRange();
     range.setStart(textNode, index);
     range.setEnd(textNode, index + alias.length);
     selection.addRange(range);
 
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -834,16 +834,19 @@ content-blocking-category-label = Choose
 
 # "Slow" in this instance means "slow to load on the network".
 # FastBlock is a feature that blocks requests to tracking sites if they
 # have not finished loading after a certain threshold of seconds.
 content-blocking-fastblock-slow-loading-trackers-label =
   .label = Slow-Loading Trackers
   .accesskey = S
 content-blocking-fastblock-new-description = Block just the trackers that keep pages from loading quickly.
+content-blocking-tracking-protection-trackers-label =
+  .label = Trackers
+  .accesskey = T
 content-blocking-tracking-protection-all-detected-trackers-label =
   .label = All Detected Trackers
   .accesskey = T
 content-blocking-tracking-protection-new-description = Block all known trackers. (May prevent some pages from loading.)
 content-blocking-tracking-protection-option-always =
   .label = Always
   .accesskey = A
 content-blocking-tracking-protection-option-private =
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -945,16 +945,17 @@ you can use these alternative items. Oth
 <!ENTITY contentBlocking.fastBlock.blocked.label "Blocked">
 <!-- LOCALIZATION NOTE (contentBlocking.fastBlock.add.label):
      This is displayed as a link to preferences, where the user can add
      this specific type of content blocking. When this text is shown
      the type of content blocking is currently not enabled. -->
 <!ENTITY contentBlocking.fastBlock.add.label "Add Blocking…">
 
 <!ENTITY contentBlocking.trackingProtection2.label "All Detected Trackers">
+<!ENTITY contentBlocking.trackingProtection3.label "Trackers">
 <!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.blocked.label):
      This label signals that this type of content blocking is turned
      ON and is successfully blocking tracker content, so this is
      a positive thing. It forms the end of the (imaginary) sentence
      "Trackers [are] Blocked"-->
 <!ENTITY contentBlocking.trackingProtection.blocked.label "Blocked">
 <!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.add.label):
      This is displayed as a link to preferences, where the user can add
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -127,23 +127,16 @@ def search_path(mozilla_dir, packages_tx
     def handle_package(package):
         if package[0] == 'optional':
             try:
                 for path in handle_package(package[1:]):
                     yield path
             except Exception:
                 pass
 
-        if package[0] in ('windows', '!windows'):
-            for_win = not package[0].startswith('!')
-            is_win = sys.platform == 'win32'
-            if is_win == for_win:
-                for path in handle_package(package[1:]):
-                    yield path
-
         if package[0] == 'packages.txt':
             assert len(package) == 2
             for p in search_path(mozilla_dir, package[1]):
                 yield os.path.join(mozilla_dir, p)
 
         if package[0].endswith('.pth'):
             assert len(package) == 2
             yield os.path.join(mozilla_dir, package[1])
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -4,31 +4,31 @@ mozilla.pth:python/mozbuild
 mozilla.pth:python/mozlint
 mozilla.pth:python/mozrelease
 mozilla.pth:python/mozterm
 mozilla.pth:python/mozversioncontrol
 mozilla.pth:python/l10n
 mozilla.pth:third_party/python/atomicwrites
 mozilla.pth:third_party/python/attrs/src
 mozilla.pth:third_party/python/blessings
+mozilla.pth:third_party/python/Click
 mozilla.pth:third_party/python/compare-locales
 mozilla.pth:third_party/python/configobj
 mozilla.pth:third_party/python/cram
 mozilla.pth:third_party/python/dlmanager
 mozilla.pth:third_party/python/fluent
 mozilla.pth:third_party/python/funcsigs
 mozilla.pth:third_party/python/futures
 mozilla.pth:third_party/python/more-itertools
 mozilla.pth:third_party/python/gyp/pylib
 mozilla.pth:third_party/python/python-hglib
 mozilla.pth:third_party/python/pluggy
 mozilla.pth:third_party/python/jsmin
-!windows:optional:setup.py:third_party/python/psutil:build_ext:--inplace
-!windows:mozilla.pth:third_party/python/psutil
-windows:mozilla.pth:third_party/python/psutil-cp27-none-win_amd64
+optional:setup.py:third_party/python/psutil:build_ext:--inplace
+mozilla.pth:third_party/python/psutil
 mozilla.pth:third_party/python/pylru
 mozilla.pth:third_party/python/which
 mozilla.pth:third_party/python/pystache
 mozilla.pth:third_party/python/pyyaml/lib
 mozilla.pth:third_party/python/requests
 mozilla.pth:third_party/python/requests-unixsocket
 mozilla.pth:third_party/python/slugid
 mozilla.pth:third_party/python/py
--- a/devtools/client/netmonitor/src/components/CustomRequestPanel.js
+++ b/devtools/client/netmonitor/src/components/CustomRequestPanel.js
@@ -45,16 +45,17 @@ class CustomRequestPanel extends Compone
       request: PropTypes.object,
       sendCustomRequest: PropTypes.func.isRequired,
       updateRequest: PropTypes.func.isRequired,
     };
   }
 
   componentDidMount() {
     const { request, connector } = this.props;
+    this.initialRequestMethod = request.method;
     fetchNetworkUpdatePacket(connector.requestData, request, [
       "requestHeaders",
       "responseHeaders",
       "requestPostData",
     ]);
   }
 
   componentWillReceiveProps(nextProps) {
@@ -104,17 +105,21 @@ class CustomRequestPanel extends Compone
           requestHeaders: {
             customHeadersValue: val || "",
             // Parse text representation of multiple HTTP headers
             headers: this.parseRequestText(val, "\\S+?", ":")
           },
         };
         break;
       case "custom-method-value":
-        data = { method: val.trim() };
+        // If val is empty when leaving the "method" field, set the method to
+        // its original value
+        data = (evt.type === "blur" && val === "") ?
+          { method: this.initialRequestMethod } :
+          { method: val.trim() };
         break;
       case "custom-postdata-value":
         data = {
           requestPostData: {
             postData: { text: val },
           }
         };
         break;
@@ -207,17 +212,19 @@ class CustomRequestPanel extends Compone
           className: "tabpanel-summary-container custom-method-and-url",
           id: "custom-method-and-url",
         },
           input({
             className: "custom-method-value",
             id: "custom-method-value",
             onChange: (evt) =>
               this.updateCustomRequestFields(evt, request, updateRequest),
-            value: method || "GET",
+            onBlur: (evt) =>
+              this.updateCustomRequestFields(evt, request, updateRequest),
+            value: method,
           }),
           input({
             className: "custom-url-value",
             id: "custom-url-value",
             onChange: (evt) =>
               this.updateCustomRequestFields(evt, request, updateRequest),
             value: url || "http://",
           }),
--- a/devtools/client/netmonitor/test/browser_net_edit_resend_caret.js
+++ b/devtools/client/netmonitor/test/browser_net_edit_resend_caret.js
@@ -2,17 +2,18 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Tests if position of caret does not change (resets to the end) after setting
- * header's value to empty string.
+ * header's value to empty string. Also make sure the "method" field stays empty
+ * after removing it and resets to its original value when it looses focus.
  */
 
 add_task(async function() {
   const { tab, monitor } = await initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   const { document, store, windowRequire, parent } = monitor.panelWin;
   const parentDocument = parent.document;
@@ -32,16 +33,19 @@ add_task(async function() {
   await waitForRequestData(store, ["requestHeaders"]);
   EventUtils.sendMouseEvent({ type: "contextmenu" }, firstRequest);
 
   // Open "New Request" form
   const contextResend =  parentDocument.querySelector("#request-list-context-resend");
   contextResend.click();
   await waitUntil(() => document.querySelector("#custom-headers-value"));
   const headersTextarea = document.querySelector("#custom-headers-value");
+  await waitUntil(() => document.querySelector("#custom-method-value"));
+  const methodField = document.querySelector("#custom-method-value");
+  const originalMethodValue = methodField.value;
   const {
     getSelectedRequest
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
   const request = getSelectedRequest(store.getState());
   const hostHeader = request.requestHeaders.headers[0];
 
   // Close the open context menu, otherwise sendString will not work
   EventUtils.synthesizeKey("VK_ESCAPE", {});
@@ -49,18 +53,32 @@ add_task(async function() {
 
   // Clean value of host header
   const headersContent = headersTextarea.value;
   const start = "Host: ".length;
   const end = headersContent.indexOf("\n");
   headersTextarea.setSelectionRange(start, end);
   EventUtils.synthesizeKey("VK_DELETE", {});
 
+  methodField.focus();
+  methodField.select();
+  EventUtils.synthesizeKey("VK_DELETE", {});
+
   ok(getSelectedRequest(store.getState()).requestHeaders.headers[0] !== hostHeader,
     "Value of Host header was edited and should change"
   );
 
   ok(headersTextarea.selectionStart === start && headersTextarea.selectionEnd === start,
     "Position of caret should not change"
   );
 
+  ok(getSelectedRequest(store.getState()).method === "",
+    "Value of method header was deleted and should be empty"
+  );
+
+  headersTextarea.focus();
+
+  ok(getSelectedRequest(store.getState()).method === originalMethodValue,
+    "Value of method header should reset to its original value"
+  );
+
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -21,32 +21,33 @@ add_task(async function() {
   // Post data may be fetched by the Header panel,
   // so set the Timings panel as the new default.
   store.getState().ui.detailsPanelSelectedTab = "timings";
 
   // Open GET request in new tab
   await performRequest("GET");
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "GET");
+  gBrowser.removeCurrentTab();
 
   // Open POST request in new tab
   await performRequest("POST", "application/x-www-form-urlencoded", "foo=bar&baz=42");
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "POST", "application/x-www-form-urlencoded",
     "foo=bar&amp;baz=42");
+  gBrowser.removeCurrentTab();
 
   // Open POST application/json request in new tab
   await performRequest("POST", "application/json", '{"foo":"bar"}');
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "POST", "application/json", '{"foo":"bar"}');
+  gBrowser.removeCurrentTab();
 
   await teardown(monitor);
 
-  gBrowser.removeCurrentTab();
-
   async function openLastRequestInTab() {
     const wait = waitForDOM(contextMenuDoc, "#request-list-context-newtab");
     const requestItems = document.querySelectorAll(".request-list-item");
     const lastRequest = requestItems[requestItems.length - 1];
     EventUtils.sendMouseEvent({ type: "mousedown" }, lastRequest);
     EventUtils.sendMouseEvent({ type: "contextmenu" }, lastRequest);
     await wait;
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -308,16 +308,17 @@ DecreasePrivateDocShellCount()
     if (obsvc) {
       obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
     }
   }
 }
 
 nsDocShell::nsDocShell()
   : nsDocLoader()
+  , mContentWindowID(NextWindowID())
   , mForcedCharset(nullptr)
   , mParentCharset(nullptr)
   , mTreeOwner(nullptr)
   , mChromeEventHandler(nullptr)
   , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto)
   , mCharsetReloadState(eCharsetReloadInit)
   , mOrientationLock(hal::eScreenOrientation_None)
   , mParentCharsetSource(0)
@@ -1282,16 +1283,23 @@ nsDocShell::GetContentViewer(nsIContentV
   NS_ENSURE_ARG_POINTER(aContentViewer);
 
   *aContentViewer = mContentViewer;
   NS_IF_ADDREF(*aContentViewer);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetOuterWindowID(uint64_t *aWindowID)
+{
+  *aWindowID = mContentWindowID;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler)
 {
   // Weak reference. Don't addref.
   mChromeEventHandler = aChromeEventHandler;
 
   if (mScriptGlobal) {
     mScriptGlobal->SetChromeEventHandler(mChromeEventHandler);
   }
@@ -12721,21 +12729,19 @@ nsDocShell::EnsureScriptEnvironment()
   nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
   NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE);
 
   uint32_t chromeFlags;
   browserChrome->GetChromeFlags(&chromeFlags);
 
   // If our window is modal and we're not opened as chrome, make
   // this window a modal content window.
-  mScriptGlobal = NS_NewScriptGlobalObject(mItemType == typeChrome);
+  mScriptGlobal = nsGlobalWindowOuter::Create(this, mItemType == typeChrome);
   MOZ_ASSERT(mScriptGlobal);
 
-  mScriptGlobal->SetDocShell(this);
-
   // Ensure the script object is set up to run script.
   return mScriptGlobal->EnsureScriptEnvironment();
 }
 
 nsresult
 nsDocShell::EnsureEditorData()
 {
   MOZ_ASSERT(!mIsBeingDestroyed);
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -931,16 +931,17 @@ private: // data members
   RefPtr<nsDOMNavigationTiming> mTiming;
   RefPtr<nsDSURIContentListener> mContentListener;
   RefPtr<nsGlobalWindowOuter> mScriptGlobal;
   nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
   nsCOMPtr<nsILoadURIDelegate> mLoadURIDelegate;
   nsCOMPtr<nsIMutableArray> mRefreshURIList;
   nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
   nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager;
+  uint64_t mContentWindowID;
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIWidget> mParentWidget;
   RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
   nsCOMPtr<nsIWebBrowserFind> mFind;
   nsCOMPtr<nsICommandManager> mCommandManager;
   RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
 
   // Dimensions of the docshell
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -261,16 +261,21 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * Content Viewer that is currently loaded for this DocShell.  This may
    * change as the underlying content changes.
    */
   readonly attribute nsIContentViewer contentViewer;
 
   /**
+   * Get the id of the outer window that is or will be in this docshell.
+   */
+  [infallible] readonly attribute unsigned long long outerWindowID;
+
+  /**
    * This attribute allows chrome to tie in to handle DOM events that may
    * be of interest to chrome.
    */
   attribute EventTarget chromeEventHandler;
 
   /**
    * This allows chrome to set a custom User agent on a specific docshell
    */
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -807,18 +807,18 @@ NewOuterWindowProxy(JSContext *cx, JS::H
   MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
   return obj;
 }
 
 //*****************************************************************************
 //***    nsGlobalWindowOuter: Object Management
 //*****************************************************************************
 
-nsGlobalWindowOuter::nsGlobalWindowOuter()
-  : nsPIDOMWindowOuter(),
+nsGlobalWindowOuter::nsGlobalWindowOuter(uint64_t aWindowID)
+  : nsPIDOMWindowOuter(aWindowID),
     mIdleFuzzFactor(0),
     mIdleCallbackIndex(-1),
     mCurrentlyIdle(false),
     mAddActiveEventFuzzTime(true),
     mFullscreen(false),
     mFullscreenMode(false),
     mIsClosed(false),
     mInClose(false),
@@ -7656,22 +7656,24 @@ nsGlobalWindowOuter::TemporarilyDisableD
 
 mozilla::dom::TabGroup*
 nsPIDOMWindowOuter::TabGroup()
 {
   return nsGlobalWindowOuter::Cast(this)->TabGroupOuter();
 }
 
 /* static */ already_AddRefed<nsGlobalWindowOuter>
-nsGlobalWindowOuter::Create(bool aIsChrome)
-{
-  RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter();
+nsGlobalWindowOuter::Create(nsIDocShell* aDocShell, bool aIsChrome)
+{
+  uint64_t outerWindowID = aDocShell->GetOuterWindowID();
+  RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
   if (aIsChrome) {
     window->mIsChrome = true;
   }
+  window->SetDocShell(aDocShell);
 
   window->InitWasOffline();
   return window.forget();
 }
 
 nsIURI*
 nsPIDOMWindowOuter::GetDocumentURI() const
 {
@@ -7710,42 +7712,32 @@ nsPIDOMWindowOuter::GetDocGroup() const
 {
   nsIDocument* doc = GetExtantDoc();
   if (doc) {
     return doc->GetDocGroup();
   }
   return nullptr;
 }
 
-// XXX: Can we define this in a header instead of here?
-namespace mozilla {
-namespace dom {
-extern uint64_t
-NextWindowID();
-} // namespace dom
-} // namespace mozilla
-
-nsPIDOMWindowOuter::nsPIDOMWindowOuter()
+nsPIDOMWindowOuter::nsPIDOMWindowOuter(uint64_t aWindowID)
   : mFrameElement(nullptr)
   , mDocShell(nullptr)
   , mModalStateDepth(0)
   , mIsActive(false)
   , mIsBackground(false)
   , mMediaSuspend(
       Preferences::GetBool("media.block-autoplay-until-in-foreground", true)
         ? nsISuspendedTypes::SUSPENDED_BLOCK
         : nsISuspendedTypes::NONE_SUSPENDED)
   , mAudioMuted(false)
   , mAudioVolume(1.0)
   , mDesktopModeViewport(false)
   , mIsRootOuterWindow(false)
   , mInnerWindow(nullptr)
-  ,
-  // Make sure no actual window ends up with mWindowID == 0
-  mWindowID(NextWindowID())
+  , mWindowID(aWindowID)
   , mMarkedCCGeneration(0)
   , mServiceWorkersTestingEnabled(false)
   , mLargeAllocStatus(LargeAllocStatus::NONE)
 {
 }
 
 nsPIDOMWindowOuter::~nsPIDOMWindowOuter() {}
 
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -214,17 +214,17 @@ public:
   }
 
   static nsGlobalWindowOuter *FromSupports(nsISupports *supports)
   {
     // Make sure this matches the casts we do in QueryInterface().
     return (nsGlobalWindowOuter *)(mozilla::dom::EventTarget *)supports;
   }
 
-  static already_AddRefed<nsGlobalWindowOuter> Create(bool aIsChrome);
+  static already_AddRefed<nsGlobalWindowOuter> Create(nsIDocShell* aDocShell, bool aIsChrome);
 
   // public methods
   nsPIDOMWindowOuter* GetPrivateParent();
 
   // callback for close event
   void ReallyCloseWindow();
 
   // nsISupports
@@ -315,17 +315,16 @@ public:
   virtual bool IsSuspended() const override;
   virtual bool IsFrozen() const override;
 
   virtual nsresult FireDelayedDOMEvents() override;
 
   // Outer windows only.
   bool WouldReuseInnerWindow(nsIDocument* aNewDocument);
 
-  void SetDocShell(nsIDocShell* aDocShell);
   void DetachFromDocShell();
 
   virtual nsresult SetNewDocument(nsIDocument *aDocument,
                                   nsISupports *aState,
                                   bool aForceReuseInnerWindow) override;
 
   // Outer windows only.
   void DispatchDOMWindowCreated();
@@ -576,17 +575,16 @@ public:
   void BlurOuter();
   already_AddRefed<nsPIDOMWindowOuter> GetFramesOuter();
   nsDOMWindowList* GetFrames() final;
   uint32_t Length();
   already_AddRefed<nsPIDOMWindowOuter> GetTopOuter();
 
   nsresult GetPrompter(nsIPrompt** aPrompt) override;
 protected:
-  explicit nsGlobalWindowOuter();
   nsPIDOMWindowOuter* GetOpenerWindowOuter();
   // Initializes the mWasOffline member variable
   void InitWasOffline();
 public:
   nsPIDOMWindowOuter*
   GetSanitizedOpener(nsPIDOMWindowOuter* aOpener);
 
   already_AddRefed<nsPIDOMWindowOuter> GetOpener() override;
@@ -826,16 +824,18 @@ protected:
   // Outer windows only.
   virtual nsresult
   OpenNoNavigate(const nsAString& aUrl,
                  const nsAString& aName,
                  const nsAString& aOptions,
                  nsPIDOMWindowOuter** _retval) override;
 
 private:
+  explicit nsGlobalWindowOuter(uint64_t aWindowID);
+
   /**
    * @param aUrl the URL we intend to load into the window.  If aNavigate is
    *        true, we'll actually load this URL into the window. Otherwise,
    *        aUrl is advisory; OpenInternal will not load the URL into the
    *        new window.
    *
    * @param aName the name to use for the new window
    *
@@ -1042,16 +1042,18 @@ private:
     eIgnoreOpener
   };
   // Called only on outer windows to compute the value that will be returned by
   // IsSecureContext() for the inner window that corresponds to aDocument.
   bool ComputeIsSecureContext(nsIDocument* aDocument,
                               SecureContextFlags aFlags =
                                 SecureContextFlags::eDefault);
 
+  void SetDocShell(nsIDocShell* aDocShell);
+
   // nsPIDOMWindow{Inner,Outer} should be able to see these helper methods.
   friend class nsPIDOMWindowInner;
   friend class nsPIDOMWindowOuter;
 
   mozilla::dom::TabGroup* TabGroupOuter();
 
   void SetIsBackgroundInternal(bool aIsBackground);
 
@@ -1253,16 +1255,9 @@ nsGlobalWindowOuter::IsFrame()
 inline void
 nsGlobalWindowOuter::MaybeClearInnerWindow(nsGlobalWindowInner* aExpectedInner)
 {
   if(mInnerWindow == aExpectedInner->AsInner()) {
     mInnerWindow = nullptr;
   }
 }
 
-/* factory function */
-inline already_AddRefed<nsGlobalWindowOuter>
-NS_NewScriptGlobalObject(bool aIsChrome)
-{
-  return nsGlobalWindowOuter::Create(aIsChrome);
-}
-
 #endif /* nsGlobalWindowOuter_h___ */
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -711,17 +711,17 @@ protected:
   mozilla::dom::Event* mEvent;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 class nsPIDOMWindowOuter : public mozIDOMWindowProxy
 {
 protected:
-  explicit nsPIDOMWindowOuter();
+  explicit nsPIDOMWindowOuter(uint64_t aWindowID);
 
   ~nsPIDOMWindowOuter();
 
   void RefreshMediaElementsVolume();
   void RefreshMediaElementsSuspend(SuspendTypes aSuspend);
   bool IsDisposableSuspend(SuspendTypes aSuspend) const;
   void MaybeNotifyMediaResumedFromBlock(SuspendTypes aSuspend);
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/HTMLEditor.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
@@ -28,22 +29,25 @@
 #include "mozilla/dom/WheelEventBinding.h"
 
 #include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "WheelHandlingHelper.h"
 
 #include "nsCommandParams.h"
 #include "nsCOMPtr.h"
+#include "nsCopySupport.h"
 #include "nsFocusManager.h"
 #include "nsGenericHTMLElement.h"
+#include "nsIClipboard.h"
 #include "nsIContent.h"
 #include "nsIContentInlines.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
+#include "nsITextControlElement.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "nsIFormControl.h"
 #include "nsComboboxControlFrame.h"
 #include "nsIScrollableFrame.h"
 #include "nsIDOMXULControlElement.h"
@@ -3373,27 +3377,24 @@ EventStateManager::PostHandleEvent(nsPre
       GenerateMouseEnterExit(pointerEvent);
       mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
     }
     break;
   }
   case eMouseUp:
     {
       ClearGlobalActiveContent(this);
-      WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
-      if (mouseEvent && mouseEvent->IsReal()) {
-        if (!mCurrentTarget) {
-          GetEventTarget();
-        }
+      WidgetMouseEvent* mouseUpEvent = aEvent->AsMouseEvent();
+      if (mouseUpEvent && EventCausesClickEvents(*mouseUpEvent)) {
         // Make sure to dispatch the click even if there is no frame for
         // the current target element. This is required for Web compatibility.
         RefPtr<EventStateManager> esm =
           ESMFromContentOrThis(aOverrideClickTarget);
-        ret = esm->CheckForAndDispatchClick(mouseEvent, aStatus,
-                                            aOverrideClickTarget);
+        ret = esm->PostHandleMouseUp(mouseUpEvent, aStatus,
+                                     aOverrideClickTarget);
       }
 
       nsIPresShell *shell = presContext->GetPresShell();
       if (shell) {
         RefPtr<nsFrameSelection> frameSelection = shell->FrameSelection();
         frameSelection->SetDragState(false);
       }
     }
@@ -4950,111 +4951,343 @@ EventStateManager::SetClickCount(WidgetM
       mLastRightMouseDownContentParent = nullptr;
     }
     break;
   }
 
   return NS_OK;
 }
 
+// static
+bool
+EventStateManager::EventCausesClickEvents(const WidgetMouseEvent& aMouseEvent)
+{
+  if (NS_WARN_IF(aMouseEvent.mMessage != eMouseUp)) {
+    return false;
+  }
+  // If the mouseup event is synthesized event, we don't need to dispatch
+  // click events.
+  if (!aMouseEvent.IsReal()) {
+    return false;
+  }
+  // If mouse is still over same element, clickcount will be > 1.
+  // If it has moved it will be zero, so no click.
+  if (!aMouseEvent.mClickCount) {
+    return false;
+  }
+  // Check that the window isn't disabled before firing a click
+  // (see bug 366544).
+  return !(aMouseEvent.mWidget && !aMouseEvent.mWidget->IsEnabled());
+}
+
 nsresult
-EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aEvent,
+EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aMouseUpEvent,
                                              nsEventStatus* aStatus,
                                              EventMessage aMessage,
                                              nsIPresShell* aPresShell,
-                                             nsIContent* aMouseTarget,
+                                             nsIContent* aMouseUpContent,
                                              AutoWeakFrame aCurrentTarget,
                                              bool aNoContentDispatch,
                                              nsIContent* aOverrideClickTarget)
 {
-  WidgetMouseEvent event(aEvent->IsTrusted(), aMessage,
-                         aEvent->mWidget, WidgetMouseEvent::eReal);
-
-  event.mRefPoint = aEvent->mRefPoint;
-  event.mClickCount = aEvent->mClickCount;
-  event.mModifiers = aEvent->mModifiers;
-  event.buttons = aEvent->buttons;
-  event.mTime = aEvent->mTime;
-  event.mTimeStamp = aEvent->mTimeStamp;
+  MOZ_ASSERT(aMouseUpEvent);
+  MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
+  MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget);
+
+  WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage,
+                         aMouseUpEvent->mWidget, WidgetMouseEvent::eReal);
+
+  event.mRefPoint = aMouseUpEvent->mRefPoint;
+  event.mClickCount = aMouseUpEvent->mClickCount;
+  event.mModifiers = aMouseUpEvent->mModifiers;
+  event.buttons = aMouseUpEvent->buttons;
+  event.mTime = aMouseUpEvent->mTime;
+  event.mTimeStamp = aMouseUpEvent->mTimeStamp;
   event.mFlags.mNoContentDispatch = aNoContentDispatch;
-  event.button = aEvent->button;
-  event.pointerId = aEvent->pointerId;
-  event.inputSource = aEvent->inputSource;
-  nsIContent* target = aMouseTarget;
+  event.button = aMouseUpEvent->button;
+  event.pointerId = aMouseUpEvent->pointerId;
+  event.inputSource = aMouseUpEvent->inputSource;
+  nsIContent* target = aMouseUpContent;
   nsIFrame* targetFrame = aCurrentTarget;
   if (aOverrideClickTarget) {
     target = aOverrideClickTarget;
     targetFrame = aOverrideClickTarget->GetPrimaryFrame();
   }
 
-  return aPresShell->HandleEventWithTarget(&event, targetFrame,
-                                           target, aStatus);
+  // Use local event status for each click event dispatching since it'll be
+  // cleared by EventStateManager::PreHandleEvent().  Therefore, dispatching
+  // an event means that previous event status will be ignored.
+  nsEventStatus status = nsEventStatus_eIgnore;
+  nsresult rv = aPresShell->HandleEventWithTarget(&event, targetFrame,
+                                                  target, &status);
+  // Copy mMultipleActionsPrevented flag from a click event to the mouseup
+  // event only when it's set to true.  It may be set to true if an editor has
+  // already handled it.  This is important to avoid two or more default
+  // actions handled here.
+  aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
+    event.mFlags.mMultipleActionsPrevented;
+  // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
+  // overwrite it.
+  if (*aStatus == nsEventStatus_eConsumeNoDefault) {
+    return rv;
+  }
+  // If new status is nsEventStatus_eConsumeNoDefault or
+  // nsEventStatus_eConsumeDoDefault, use it.
+  if (status == nsEventStatus_eConsumeNoDefault ||
+      status == nsEventStatus_eConsumeDoDefault) {
+    *aStatus = status;
+    return rv;
+  }
+  // Otherwise, keep the original status.
+  return rv;
+}
+
+nsresult
+EventStateManager::PostHandleMouseUp(WidgetMouseEvent* aMouseUpEvent,
+                                     nsEventStatus* aStatus,
+                                     nsIContent* aOverrideClickTarget)
+{
+  MOZ_ASSERT(aMouseUpEvent);
+  MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
+  MOZ_ASSERT(aStatus);
+
+  nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
+  if (!presShell) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIContent> mouseUpContent = GetEventTargetContent(aMouseUpEvent);
+  // Click events apply to *elements* not nodes. At this point the target
+  // content may have been reset to some non-element content, and so we need
+  // to walk up the closest ancestor element, just like we do in
+  // nsPresShell::HandleEvent.
+  while (mouseUpContent && !mouseUpContent->IsElement()) {
+    mouseUpContent = mouseUpContent->GetFlattenedTreeParent();
+  }
+
+  if (!mouseUpContent && !mCurrentTarget && !aOverrideClickTarget) {
+    return NS_OK;
+  }
+
+  // Fire click events if the event target is still available.
+  // Note that do not include the eMouseUp event's status since we ignore it
+  // for compatibility with the other browsers.
+  nsEventStatus status = nsEventStatus_eIgnore;
+  nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, &status,
+                                    mouseUpContent, aOverrideClickTarget);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Do not do anything if preceding click events are consumed.
+  // Note that Chromium dispatches "paste" event and actually pates clipboard
+  // text into focused editor even if the preceding click events are consumed.
+  // However, this is different from our traditional behavior and does not
+  // conform to DOM events.  If we need to keep compatibility with Chromium,
+  // we should change it later.
+  if (status == nsEventStatus_eConsumeNoDefault) {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
+    return NS_OK;
+  }
+
+  // Handle middle click paste if it's enabled and the mouse button is middle.
+  if (aMouseUpEvent->button != WidgetMouseEventBase::eMiddleButton ||
+      !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
+    return NS_OK;
+  }
+  DebugOnly<nsresult> rvIgnored =
+    HandleMiddleClickPaste(presShell, aMouseUpEvent, &status, nullptr);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+                       "Failed to paste for a middle click");
+
+  // If new status is nsEventStatus_eConsumeNoDefault or
+  // nsEventStatus_eConsumeDoDefault, use it.
+  if (*aStatus != nsEventStatus_eConsumeNoDefault &&
+      (status == nsEventStatus_eConsumeNoDefault ||
+       status == nsEventStatus_eConsumeDoDefault)) {
+    *aStatus = status;
+  }
+
+  // Don't return error even if middle mouse paste fails since we haven't
+  // handled it here.
+  return NS_OK;
 }
 
 nsresult
-EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
-                                            nsEventStatus* aStatus,
-                                            nsIContent* aOverrideClickTarget)
-{
-  nsresult ret = NS_OK;
-
-  //If mouse is still over same element, clickcount will be > 1.
-  //If it has moved it will be zero, so no click.
-  if (aEvent->mClickCount) {
-    //Check that the window isn't disabled before firing a click
-    //(see bug 366544).
-    if (aEvent->mWidget && !aEvent->mWidget->IsEnabled()) {
-      return ret;
+EventStateManager::DispatchClickEvents(nsIPresShell* aPresShell,
+                                       WidgetMouseEvent* aMouseUpEvent,
+                                       nsEventStatus* aStatus,
+                                       nsIContent* aMouseUpContent,
+                                       nsIContent* aOverrideClickTarget)
+{
+  MOZ_ASSERT(aPresShell);
+  MOZ_ASSERT(aMouseUpEvent);
+  MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent));
+  MOZ_ASSERT(aStatus);
+  MOZ_ASSERT(aMouseUpContent || mCurrentTarget || aOverrideClickTarget);
+
+  bool notDispatchToContents =
+   (aMouseUpEvent->button == WidgetMouseEvent::eMiddleButton ||
+    aMouseUpEvent->button == WidgetMouseEvent::eRightButton);
+
+  bool fireAuxClick = notDispatchToContents;
+
+  // HandleEvent clears out mCurrentTarget which we might need again
+  AutoWeakFrame currentTarget = mCurrentTarget;
+  nsresult rv =
+    InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseClick,
+                              aPresShell, aMouseUpContent, currentTarget,
+                              notDispatchToContents,
+                              aOverrideClickTarget);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Fire double click event if click count is 2.
+  if (aMouseUpEvent->mClickCount == 2 &&
+      aMouseUpContent && aMouseUpContent->IsInComposedDoc()) {
+    rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
+                                   aPresShell, aMouseUpContent, currentTarget,
+                                   notDispatchToContents,
+                                   aOverrideClickTarget);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  // Fire auxclick even if necessary.
+  if (fireAuxClick &&
+      aMouseUpContent && aMouseUpContent->IsInComposedDoc()) {
+    rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick,
+                                   aPresShell, aMouseUpContent, currentTarget,
+                                   false, aOverrideClickTarget);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
+  }
+
+  return rv;
+}
+
+nsresult
+EventStateManager::HandleMiddleClickPaste(nsIPresShell* aPresShell,
+                                          WidgetMouseEvent* aMouseEvent,
+                                          nsEventStatus* aStatus,
+                                          TextEditor* aTextEditor)
+{
+  MOZ_ASSERT(aPresShell);
+  MOZ_ASSERT(aMouseEvent);
+  MOZ_ASSERT((aMouseEvent->mMessage == eMouseClick &&
+              aMouseEvent->button == WidgetMouseEventBase::eMiddleButton) ||
+             EventCausesClickEvents(*aMouseEvent));
+  MOZ_ASSERT(aStatus);
+  MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
+
+  // Even if we're called twice or more for a mouse operation, we should
+  // handle only once.  Although mMultipleActionsPrevented may be set to
+  // true by different event handler in the future, we can use it for now.
+  if (aMouseEvent->mFlags.mMultipleActionsPrevented) {
+    return NS_OK;
+  }
+  aMouseEvent->mFlags.mMultipleActionsPrevented = true;
+
+  RefPtr<Selection> selection;
+  if (aTextEditor) {
+    selection = aTextEditor->GetSelection();
+    if (NS_WARN_IF(!selection)) {
+      return NS_ERROR_FAILURE;
+    }
+  } else {
+    nsIDocument* document = aPresShell->GetDocument();
+    if (NS_WARN_IF(!document)) {
+      return NS_ERROR_FAILURE;
     }
-    //fire click
-    bool notDispatchToContents =
-     (aEvent->button == WidgetMouseEvent::eMiddleButton ||
-      aEvent->button == WidgetMouseEvent::eRightButton);
-
-    bool fireAuxClick = notDispatchToContents;
-
-    nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
-    if (presShell) {
-      nsCOMPtr<nsIContent> mouseContent = GetEventTargetContent(aEvent);
-      // Click events apply to *elements* not nodes. At this point the target
-      // content may have been reset to some non-element content, and so we need
-      // to walk up the closest ancestor element, just like we do in
-      // nsPresShell::HandleEvent.
-      while (mouseContent && !mouseContent->IsElement()) {
-        mouseContent = mouseContent->GetFlattenedTreeParent();
-      }
-
-      if (!mouseContent && !mCurrentTarget && !aOverrideClickTarget) {
-        return NS_OK;
-      }
-
-      // HandleEvent clears out mCurrentTarget which we might need again
-      AutoWeakFrame currentTarget = mCurrentTarget;
-      ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseClick,
-                                      presShell, mouseContent, currentTarget,
-                                      notDispatchToContents,
-                                      aOverrideClickTarget);
-
-      if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 &&
-          mouseContent && mouseContent->IsInComposedDoc()) {
-        //fire double click
-        ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseDoubleClick,
-                                        presShell, mouseContent, currentTarget,
-                                        notDispatchToContents,
-                                        aOverrideClickTarget);
-      }
-      if (NS_SUCCEEDED(ret) && mouseContent && fireAuxClick &&
-          mouseContent->IsInComposedDoc()) {
-        ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseAuxClick,
-                                        presShell, mouseContent, currentTarget,
-                                        false, aOverrideClickTarget);
-      }
+    nsCopySupport::GetSelectionForCopy(document, getter_AddRefs(selection));
+    if (NS_WARN_IF(!selection)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  // Move selection to the clicked point.
+  nsCOMPtr<nsIContent> container;
+  int32_t offset;
+  nsLayoutUtils::GetContainerAndOffsetAtEvent(aPresShell, aMouseEvent,
+                                              getter_AddRefs(container),
+                                              &offset);
+  if (container) {
+    // XXX If readonly or disabled <input> or <textarea> in contenteditable
+    //     designMode editor is clicked, the point is in the editor.
+    //     However, outer HTMLEditor and Selection should handle it.
+    //     So, in such case, Selection::Collapse() will fail.
+    DebugOnly<nsresult> rv = selection->Collapse(container, offset);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+      "Failed to collapse Selection at middle clicked");
+  }
+
+  int32_t clipboardType = nsIClipboard::kGlobalClipboard;
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIClipboard> clipboardService =
+    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
+  if (NS_SUCCEEDED(rv)) {
+    bool selectionSupported;
+    rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
+    if (NS_SUCCEEDED(rv) && selectionSupported) {
+      clipboardType = nsIClipboard::kSelectionClipboard;
     }
   }
-  return ret;
+
+  // Fire ePaste event by ourselves since we need to dispatch "paste" event
+  // even if the middle click event was consumed for compatibility with
+  // Chromium.
+  if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType,
+                                         aPresShell, selection)) {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
+    return NS_OK;
+  }
+
+  // Although we've fired "paste" event, there is no editor to accept the
+  // clipboard content.
+  if (!aTextEditor) {
+    return NS_OK;
+  }
+
+  // Check if the editor is still the good target to paste.
+  if (aTextEditor->Destroyed() ||
+      aTextEditor->IsReadonly() ||
+      aTextEditor->IsDisabled()) {
+    // XXX Should we consume the event when the editor is readonly and/or
+    //     disabled?
+    return NS_OK;
+  }
+
+  // The selection may have been modified during reflow.  Therefore, we
+  // should adjust event target to pass IsAcceptableInputEvent().
+  nsRange* range = selection->GetRangeAt(0);
+  if (!range) {
+    return NS_OK;
+  }
+  WidgetMouseEvent mouseEvent(*aMouseEvent);
+  mouseEvent.mOriginalTarget = range->GetStartContainer();
+  if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
+      !aTextEditor->IsAcceptableInputEvent(&mouseEvent)) {
+    return NS_OK;
+  }
+
+  // If Control key is pressed, we should paste clipboard content as
+  // quotation.  Otherwise, paste it as is.
+  if (aMouseEvent->IsControl()) {
+    DebugOnly<nsresult> rv =
+      aTextEditor->PasteAsQuotationAsAction(clipboardType, false);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
+  } else {
+    DebugOnly<nsresult> rv =
+      aTextEditor->PasteAsAction(clipboardType, false);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
+  }
+  *aStatus = nsEventStatus_eConsumeNoDefault;
+
+  return NS_OK;
 }
 
 nsIFrame*
 EventStateManager::GetEventTarget()
 {
   nsIPresShell *shell;
   if (mCurrentTarget ||
       !mPresContext ||
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -107,16 +107,17 @@ public:
                           nsEventStatus* aStatus,
                           nsIContent* aOverrideClickTarget);
 
   /* The PostHandleEvent method should contain all system processing which
    * should occur conditionally based on DOM or frame processing.  It should
    * also contain any centralized event processing which must occur after
    * DOM and frame processing.
    */
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult PostHandleEvent(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIFrame* aTargetFrame,
                            nsEventStatus* aStatus,
                            nsIContent* aOverrideClickTarget);
 
   void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
                                nsIFrame* aTargetFrame, nsEventStatus& aStatus);
@@ -357,16 +358,38 @@ public:
    * If the absolute values of mMultiplierX and/or mMultiplierY are equal or
    * larger than this value, the computed scroll amount isn't rounded down to
    * the page width or height.
    */
   enum {
     MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000
   };
 
+  /**
+   * HandleMiddleClickPaste() handles middle mouse button event as pasting
+   * clipboard text.  Note that if aTextEditor is nullptr, this only
+   * dispatches ePaste event because it's necessary for some web apps which
+   * want to implement their own editor and supports middle click paste.
+   *
+   * @param aPresShell              The PresShell for the ESM.  This lifetime
+   *                                should be guaranteed by the caller.
+   * @param aMouseEvent             The eMouseClick event which caused the
+   *                                paste.
+   * @param aStatus                 The event status of aMouseEvent.
+   * @param aTextEditor             TextEditor which may be pasted the
+   *                                clipboard text by the middle click.
+   *                                If there is no editor for aMouseEvent,
+   *                                set nullptr.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  nsresult HandleMiddleClickPaste(nsIPresShell* aPresShell,
+                                  WidgetMouseEvent* aMouseEvent,
+                                  nsEventStatus* aStatus,
+                                  TextEditor* aTextEditor);
+
 protected:
   /**
    * Prefs class capsules preference management.
    */
   class Prefs
   {
   public:
     static bool KeyCausesActivation() { return sKeyCausesActivation; }
@@ -456,29 +479,103 @@ protected:
                            nsIContent* aTargetContent,
                            AutoWeakFrame& aTargetFrame);
   /**
    * Update the initial drag session data transfer with any changes that occur
    * on cloned data transfer objects used for events.
    */
   void UpdateDragDataTransfer(WidgetDragEvent* dragEvent);
 
-  static nsresult InitAndDispatchClickEvent(WidgetMouseEvent* aEvent,
+  /**
+   * InitAndDispatchClickEvent() dispatches a click event.
+   *
+   * @param aMouseUpEvent           eMouseUp event which causes the click event.
+   *                                EventCausesClickEvents() must return true
+   *                                if this event is set to it.
+   * @param aStatus                 Returns the result of click event.
+   *                                If the status indicates consumed, the
+   *                                value won't be overwritten with
+   *                                nsEventStatus_eIgnore.
+   * @param aMessage                Should be eMouseClick, eMouseDoubleClick or
+   *                                eMouseAuxClick.
+   * @param aPresShell              The PresShell.
+   * @param aMouseUpContent         The event target of aMouseUpEvent.
+   * @param aCurrentTarget          Current target of the caller.
+   * @param aNoContentDispatch      true if the event shouldn't be exposed to
+   *                                web contents (although will be fired on
+   *                                document and window).
+   * @param aOverrideClickTarget    Preferred click event target.  If this is
+   *                                not nullptr, aMouseUpContent and
+   *                                aCurrentTarget are ignored.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  static nsresult InitAndDispatchClickEvent(WidgetMouseEvent* aMouseUpEvent,
                                             nsEventStatus* aStatus,
                                             EventMessage aMessage,
                                             nsIPresShell* aPresShell,
-                                            nsIContent* aMouseTarget,
+                                            nsIContent* aMouseUpContent,
                                             AutoWeakFrame aCurrentTarget,
                                             bool aNoContentDispatch,
                                             nsIContent* aOverrideClickTarget);
+
   nsresult SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus,
                          nsIContent* aOverrideClickTarget = nullptr);
-  nsresult CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
-                                    nsEventStatus* aStatus,
-                                    nsIContent* aOverrideClickTarget);
+
+  /**
+   * EventCausesClickEvents() returns true when aMouseEvent is an eMouseUp
+   * event and it should cause eMouseClick, eMouseDoubleClick and/or
+   * eMouseAuxClick events.  Note that this method assumes that
+   * aMouseEvent.mClickCount has already been initialized with SetClickCount().
+   */
+  static bool EventCausesClickEvents(const WidgetMouseEvent& aMouseEvent);
+
+  /**
+   * PostHandleMouseUp() handles default actions of eMouseUp event.
+   *
+   * @param aMouseUpEvent           eMouseUp event which causes the click event.
+   *                                EventCausesClickEvents() must return true
+   *                                if this event is set to it.
+   * @param aStatus                 Returns the result of event status.
+   *                                If one of dispatching event is consumed or
+   *                                this does something as default action,
+   *                                returns nsEventStatus_eConsumeNoDefault.
+   * @param aOverrideClickTarget    Preferred click event target.  If nullptr,
+   *                                aMouseUpEvent target and current target
+   *                                are used.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  nsresult PostHandleMouseUp(WidgetMouseEvent* aMouseUpEvent,
+                             nsEventStatus* aStatus,
+                             nsIContent* aOverrideClickTarget);
+
+  /**
+   * DispatchClickEvents() dispatches eMouseClick, eMouseDoubleClick and
+   * eMouseAuxClick events for aMouseUpEvent.  aMouseUpEvent should cause
+   * click event.
+   *
+   * @param aPresShell              The PresShell.
+   * @param aMouseUpEvent           eMouseUp event which causes the click event.
+   *                                EventCausesClickEvents() must return true
+   *                                if this event is set to it.
+   * @param aStatus                 Returns the result of event status.
+   *                                If one of dispatching click event is
+   *                                consumed, returns
+   *                                nsEventStatus_eConsumeNoDefault.
+   * @param aMouseUpContent         The event target of aMouseUpEvent.
+   * @param aOverrideClickTarget    Preferred click event target.  If this is
+   *                                not nullptr, aMouseUpContent and
+   *                                current target frame of the ESM are ignored.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  nsresult DispatchClickEvents(nsIPresShell* aPresShell,
+                               WidgetMouseEvent* aMouseUpEvent,
+                               nsEventStatus* aStatus,
+                               nsIContent* aMouseUpContent,
+                               nsIContent* aOverrideClickTarget);
+
   void EnsureDocument(nsPresContext* aPresContext);
   void FlushPendingEvents(nsPresContext* aPresContext);
 
   /**
    * The phases of WalkESMTreeToHandleAccessKey processing. See below.
    */
   typedef enum {
     eAccessKeyProcessingNormal = 0,
--- a/dom/events/UIEvent.cpp
+++ b/dom/events/UIEvent.cpp
@@ -179,64 +179,44 @@ UIEvent::PageY() const
 
   return Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint,
                               mClientPoint).y;
 }
 
 already_AddRefed<nsINode>
 UIEvent::GetRangeParent()
 {
-  nsIFrame* targetFrame = nullptr;
-
-  if (mPresContext) {
-    nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
-    if (shell) {
-      shell->FlushPendingNotifications(FlushType::Layout);
-      targetFrame = mPresContext->EventStateManager()->GetEventTarget();
-    }
+  if (NS_WARN_IF(!mPresContext)) {
+    return nullptr;
   }
-
-  if (targetFrame) {
-    nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent,
-                                                              targetFrame);
-    nsCOMPtr<nsIContent> parent = targetFrame->GetContentOffsetsFromPoint(pt).content;
-    if (parent) {
-      if (parent->ChromeOnlyAccess() &&
-          !nsContentUtils::CanAccessNativeAnon()) {
-        return nullptr;
-      }
-      return parent.forget();
-    }
+  nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
+  if (NS_WARN_IF(!presShell)) {
+    return nullptr;
   }
-
-  return nullptr;
+  nsCOMPtr<nsIContent> container;
+  nsLayoutUtils::GetContainerAndOffsetAtEvent(presShell, mEvent,
+                                              getter_AddRefs(container),
+                                              nullptr);
+  return container.forget();
 }
 
 int32_t
 UIEvent::RangeOffset() const
 {
-  if (!mPresContext) {
-    return 0;
-  }
-
-  nsCOMPtr<nsIPresShell> shell = mPresContext->GetPresShell();
-  if (!shell) {
+  if (NS_WARN_IF(!mPresContext)) {
     return 0;
   }
-
-  shell->FlushPendingNotifications(FlushType::Layout);
-
-  nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget();
-  if (!targetFrame) {
+  nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
+  if (NS_WARN_IF(!presShell)) {
     return 0;
   }
-
-  nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent,
-                                                            targetFrame);
-  return targetFrame->GetContentOffsetsFromPoint(pt).offset;
+  int32_t offset = 0;
+  nsLayoutUtils::GetContainerAndOffsetAtEvent(presShell, mEvent,
+                                              nullptr, &offset);
+  return offset;
 }
 
 nsIntPoint
 UIEvent::GetLayerPoint() const
 {
   if (mEvent->mFlags.mIsPositionless) {
     return nsIntPoint(0, 0);
   }
--- a/dom/events/UIEvent.h
+++ b/dom/events/UIEvent.h
@@ -83,18 +83,20 @@ public:
   {
     MOZ_ASSERT(mEvent->mClass != eKeyboardEventClass,
                "Key events should override Which()");
     MOZ_ASSERT(mEvent->mClass != eMouseEventClass,
                "Mouse events should override Which()");
     return 0;
   }
 
+  MOZ_CAN_RUN_SCRIPT
   already_AddRefed<nsINode> GetRangeParent();
 
+  MOZ_CAN_RUN_SCRIPT
   int32_t RangeOffset() const;
 
 protected:
   ~UIEvent() {}
 
   // Internal helper functions
   nsIntPoint GetMovementPoint();
   nsIntPoint GetLayerPoint() const;
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -2429,16 +2429,30 @@ ServiceWorkerManager::Update(nsIPrincipa
   ServiceWorkerUpdaterChild* actor =
     new ServiceWorkerUpdaterChild(promise, successRunnable, failureRunnable);
 
   mActor->SendPServiceWorkerUpdaterConstructor(actor,
                                                aPrincipal->OriginAttributesRef(),
                                                nsCString(aScope));
 }
 
+namespace {
+
+void
+RejectUpdateWithInvalidStateError(ServiceWorkerUpdateFinishCallback& aCallback)
+{
+  ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR);
+  aCallback.UpdateFailed(error);
+
+  // In case the callback does not consume the exception
+  error.SuppressException();
+}
+
+}
+
 void
 ServiceWorkerManager::UpdateInternal(nsIPrincipal* aPrincipal,
                                      const nsACString& aScope,
                                      ServiceWorkerUpdateFinishCallback* aCallback)
 {
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aCallback);
 
@@ -2454,22 +2468,22 @@ ServiceWorkerManager::UpdateInternal(nsI
     return;
   }
 
   // "Let newestWorker be the result of running Get Newest Worker algorithm
   // passing registration as its argument.
   // If newestWorker is null, return a promise rejected with "InvalidStateError"
   RefPtr<ServiceWorkerInfo> newest = registration->Newest();
   if (!newest) {
-    ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR);
-    aCallback->UpdateFailed(error);
-
-    // In case the callback does not consume the exception
-    error.SuppressException();
-
+    RejectUpdateWithInvalidStateError(*aCallback);
+    return;
+  }
+
+  if (newest->State() == ServiceWorkerState::Installing) {
+    RejectUpdateWithInvalidStateError(*aCallback);
     return;
   }
 
   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
 
   // "Invoke Update algorithm, or its equivalent, with client, registration as
   // its argument."
   RefPtr<ServiceWorkerUpdateJob> job =
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -62,16 +62,28 @@ async function reset() {
   // Reset value of contentInput.
   contentInput.value = "INPUT TEXT";
 }
 
 function getClipboardText() {
   return SpecialPowers.getClipboardData("text/unicode");
 }
 
+function getHTMLEditor() {
+  let editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  if (!editingSession) {
+    return null;
+  }
+  let editor = editingSession.getEditorForWindow(window);
+  if (!editor) {
+    return null;
+  }
+  return editor.QueryInterface(SpecialPowers.Ci.nsIHTMLEditor);
+}
+
 async function putOnClipboard(expected, operationFn, desc, type) {
   await SimpleTest.promiseClipboardChange(expected, operationFn, type);
   ok(true, desc);
 }
 
 async function wontPutOnClipboard(expected, operationFn, desc, type) {
   await SimpleTest.promiseClipboardChange(null, operationFn, type, 300, true);
   ok(SpecialPowers.getClipboardData(type || "text/unicode")
@@ -795,21 +807,97 @@ add_task(async function test_event_targe
   contenteditableContainer.innerHTML = '<div contenteditable><p id="p1">foo</p><p id="p2">bar</p></div>';
   contenteditableContainer.firstChild.focus();
 
   let p1 = document.getElementById("p1");
   let p2 = document.getElementById("p2");
   selection.setBaseAndExtent(p1.firstChild, 1, p2.firstChild, 1);
 
   let pasteTarget = null;
-  document.addEventListener("paste", (event) => { pasteTarget = event.target; }, {once: true});
-
+  let pasteEventCount = 0;
+  function pasteEventLogger(event) {
+    pasteTarget = event.target;
+    pasteEventCount++;
+  }
+  document.addEventListener("paste", pasteEventLogger);
   synthesizeKey("v", {accelKey: 1});
   is(pasteTarget.getAttribute("id"), "p1",
      "'paste' event's target should be always an element which includes start container of the first Selection range");
+  is(pasteEventCount, 1,
+     "'paste' event should be fired only once when Accel+'v' is pressed");
+  document.removeEventListener("paste", pasteEventLogger);
+  contenteditableContainer.innerHTML = "";
+});
 
+add_task(async function test_paste_event_for_middle_click_without_HTMLEditor() {
+  await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
+                                           ["middlemouse.contentLoadURL", false]]});
+
+  await reset();
+
+  contenteditableContainer.innerHTML = '<div id="non-editable-target">non-editable</div>';
+  let noneditableDiv = document.getElementById("non-editable-target");
+
+  ok(!getHTMLEditor(), "There should not be HTMLEditor");
+
+  let selection = document.getSelection();
+  selection.setBaseAndExtent(content.firstChild, 0,
+                             content.firstChild, "CONTENT".length);
+
+  await putOnClipboard("CONTENT", () => {
+    synthesizeKey("c", {accelKey: 1});
+  }, "copy text from non-editable element");
+
+  let auxclickFired = false;
+  function onAuxClick(event) {
+    auxclickFired = true;
+  }
+  document.addEventListener("auxclick", onAuxClick);
+
+  let pasteEventCount = 0;
+  function onPaste(event) {
+    pasteEventCount++;
+    ok(auxclickFired, "'auxclick' event should be fired before 'paste' event");
+    is(event.target, noneditableDiv,
+       "'paste' event should be fired on the clicked element");
+  }
+  document.addEventListener("paste", onPaste);
+
+  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
+  is(pasteEventCount, 1, "'paste' event should be fired just once");
+
+  pasteEventCount = 0;
+  auxclickFired = false;
+  document.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
+  is(pasteEventCount, 1,
+     "Even if 'mouseup' event is consumed, 'paste' event should be fired");
+
+  pasteEventCount = 0;
+  auxclickFired = false;
+  document.addEventListener("click", (event) => { event.preventDefault(); }, {once: true, capture: true});
+  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
+  is(pasteEventCount, 0,
+     "If 'click' event is consumed at capturing phase at the document node, 'paste' event should be not be fired");
+
+  pasteEventCount = 0;
+  auxclickFired = false;
+  noneditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
+  is(pasteEventCount, 1,
+     "Even if 'click' event listener is added to the click event target, 'paste' event should be fired");
+
+  pasteEventCount = 0;
+  auxclickFired = false;
+  document.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
+  is(pasteEventCount, 0,
+     "If 'auxclick' event is consumed, 'paste' event should be not be fired");
+
+  document.removeEventListener("auxclick", onAuxClick);
+  document.removeEventListener("paste", onPaste);
   contenteditableContainer.innerHTML = "";
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -1196,17 +1196,17 @@ NS_IMETHODIMP
 EditorBase::CanDelete(bool* aCanDelete)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 EditorBase::Paste(int32_t aClipboardType)
 {
-  nsresult rv = AsTextEditor()->PasteAsAction(aClipboardType);
+  nsresult rv = AsTextEditor()->PasteAsAction(aClipboardType, true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 EditorBase::PasteTransferable(nsITransferable* aTransferable)
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -554,17 +554,17 @@ PasteCommand::DoCommand(const char* aCom
                         nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard, true);
 }
 
 NS_IMETHODIMP
 PasteCommand::DoCommandParams(const char* aCommandName,
                               nsICommandParams* aParams,
                               nsISupports* aCommandRefCon)
 {
   return DoCommand(aCommandName, aCommandRefCon);
@@ -1303,31 +1303,33 @@ PasteQuotationCommand::DoCommand(const c
                                  nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard,
+                                              true);
 }
 
 NS_IMETHODIMP
 PasteQuotationCommand::DoCommandParams(const char* aCommandName,
                                        nsICommandParams* aParams,
                                        nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (!editor) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard,
+                                              true);
 }
 
 NS_IMETHODIMP
 PasteQuotationCommand::GetCommandStateParams(const char* aCommandName,
                                              nsICommandParams* aParams,
                                              nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -6,16 +6,17 @@
 
 #include "EditorEventListener.h"
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ContentEvents.h"      // for InternalFocusEvent
 #include "mozilla/EditorBase.h"         // for EditorBase, etc.
 #include "mozilla/EventListenerManager.h" // for EventListenerManager
+#include "mozilla/EventStateManager.h"  // for EventStateManager
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/TextEditor.h"         // for TextEditor
 #include "mozilla/TextEvents.h"         // for WidgetCompositionEvent
 #include "mozilla/dom/Element.h"        // for Element
 #include "mozilla/dom/Event.h"          // for Event
 #include "mozilla/dom/EventTarget.h"    // for EventTarget
 #include "mozilla/dom/MouseEvent.h"     // for MouseEvent
@@ -375,29 +376,36 @@ EditorEventListener::HandleEvent(Event* 
   // NOTE: Each event handler may require specific event interface.  Before
   //       calling it, this queries the specific interface.  If it would fail,
   //       each event handler would just ignore the event.  So, in this method,
   //       you don't need to check if the QI succeeded before each call.
   WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
   switch (internalEvent->mMessage) {
     // dragenter
     case eDragEnter: {
-      return DragEnter(aEvent->AsDragEvent());
+      // aEvent should be grabbed by the caller since this is
+      // nsIDOMEventListener method.  However, our clang plugin cannot check it
+      // if we use Event::As*Event().  So, we need to grab it by ourselves.
+      RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+      return DragEnter(dragEvent);
     }
     // dragover
     case eDragOver: {
-      return DragOver(aEvent->AsDragEvent());
+      RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+      return DragOver(dragEvent);
     }
     // dragexit
     case eDragExit: {
-      return DragExit(aEvent->AsDragEvent());
+      RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+      return DragExit(dragEvent);
     }
     // drop
     case eDrop: {
-      return Drop(aEvent->AsDragEvent());
+      RefPtr<DragEvent> dragEvent = aEvent->AsDragEvent();
+      return Drop(dragEvent);
     }
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
     // keydown
     case eKeyDown: {
       return KeyDown(internalEvent->AsKeyboardEvent());
     }
     // keyup
     case eKeyUp:
@@ -415,17 +423,17 @@ EditorEventListener::HandleEvent(Event* 
       // Therefore, even if case #2 or case #3 occurs,
       // mMouseDownOrUpConsumedByIME is true here.  Therefore, we should always
       // overwrite it here.
       mMouseDownOrUpConsumedByIME =
         NotifyIMEOfMouseButtonEvent(internalEvent->AsMouseEvent());
       if (mMouseDownOrUpConsumedByIME) {
         return NS_OK;
       }
-      MouseEvent* mouseEvent = aEvent->AsMouseEvent();
+      RefPtr<MouseEvent> mouseEvent = aEvent->AsMouseEvent();
       return NS_WARN_IF(!mouseEvent) ? NS_OK : MouseDown(mouseEvent);
     }
     // mouseup
     case eMouseUp: {
       // See above comment in the eMouseDown case, first.
       // This code assumes that case #1 is occuring.  However, if case #3 may
       // occurs after case #2 and the mousedown is consumed,
       // mMouseDownOrUpConsumedByIME is true even though EditorEventListener
@@ -436,31 +444,33 @@ EditorEventListener::HandleEvent(Event* 
       // So, before a click event is fired, mMouseDownOrUpConsumedByIME is
       // always initialized in the eMouseDown case if it's referred.
       if (NotifyIMEOfMouseButtonEvent(internalEvent->AsMouseEvent())) {
         mMouseDownOrUpConsumedByIME = true;
       }
       if (mMouseDownOrUpConsumedByIME) {
         return NS_OK;
       }
-      MouseEvent* mouseEvent = aEvent->AsMouseEvent();
+      RefPtr<MouseEvent> mouseEvent = aEvent->AsMouseEvent();
       return NS_WARN_IF(!mouseEvent) ? NS_OK : MouseUp(mouseEvent);
     }
     // click
     case eMouseClick: {
-      MouseEvent* mouseEvent = aEvent->AsMouseEvent();
-      NS_ENSURE_TRUE(mouseEvent, NS_OK);
+      WidgetMouseEvent* widgetMouseEvent = internalEvent->AsMouseEvent();
+      if (NS_WARN_IF(!widgetMouseEvent)) {
+        return NS_OK;
+      }
       // If the preceding mousedown event or mouseup event was consumed,
       // editor shouldn't handle this click event.
       if (mMouseDownOrUpConsumedByIME) {
         mMouseDownOrUpConsumedByIME = false;
-        mouseEvent->PreventDefault();
+        widgetMouseEvent->PreventDefault();
         return NS_OK;
       }
-      return MouseClick(mouseEvent);
+      return MouseClick(widgetMouseEvent);
     }
     // focus
     case eFocus:
       return Focus(internalEvent->AsFocusEvent());
     // blur
     case eBlur:
       return Blur(internalEvent->AsFocusEvent());
     // text
@@ -627,112 +637,89 @@ EditorEventListener::KeyPress(WidgetKeyb
                         nsIWidget::NativeKeyBindingsForRichTextEditor,
                         DoCommandCallback, doc)) {
     aKeyboardEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 nsresult
-EditorEventListener::MouseClick(MouseEvent* aMouseEvent)
+EditorEventListener::MouseClick(WidgetMouseEvent* aMouseClickEvent)
 {
-  if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) {
+  if (NS_WARN_IF(!aMouseClickEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
   // nothing to do if editor isn't editable or clicked on out of the editor.
-  RefPtr<EditorBase> editorBase(mEditorBase);
-  WidgetMouseEvent* clickEvent =
-    aMouseEvent->WidgetEventPtr()->AsMouseEvent();
-  if (editorBase->IsReadonly() || editorBase->IsDisabled() ||
-      !editorBase->IsAcceptableInputEvent(clickEvent)) {
+  RefPtr<TextEditor> textEditor = mEditorBase->AsTextEditor();
+  if (textEditor->IsReadonly() || textEditor->IsDisabled() ||
+      !textEditor->IsAcceptableInputEvent(aMouseClickEvent)) {
     return NS_OK;
   }
 
   // Notifies clicking on editor to IMEStateManager even when the event was
   // consumed.
   if (EditorHasFocus()) {
-    nsPresContext* presContext = GetPresContext();
+    RefPtr<nsPresContext> presContext = GetPresContext();
     if (presContext) {
       IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(),
-                                       clickEvent);
+                                       aMouseClickEvent);
       if (DetachedFromEditor()) {
         return NS_OK;
       }
     }
   }
 
-  if (DetachedFromEditorOrDefaultPrevented(clickEvent)) {
+  if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent)) {
     // We're done if 'preventdefault' is true (see for example bug 70698).
     return NS_OK;
   }
 
   // If we got a mouse down inside the editing area, we should force the
-  // IME to commit before we change the cursor position
+  // IME to commit before we change the cursor position.
   if (!EnsureCommitCompoisition()) {
     return NS_OK;
   }
 
-  if (clickEvent->button == 1) {
-    return HandleMiddleClickPaste(aMouseEvent);
-  }
-  return NS_OK;
-}
+  // XXX The following code is hack for our buggy "click" and "auxclick"
+  //     implementation.  "auxclick" event was added recently, however,
+  //     any non-primary button click event handlers in our UI still keep
+  //     listening to "click" events.  Additionally, "auxclick" event is
+  //     fired after "click" events and even if we do this in the system event
+  //     group, middle click opens new tab before us.  Therefore, we need to
+  //     handle middle click at capturing phase of the default group even
+  //     though this makes web apps cannot prevent middle click paste with
+  //     calling preventDefault() of "click" nor "auxclick".
 
-nsresult
-EditorEventListener::HandleMiddleClickPaste(MouseEvent* aMouseEvent)
-{
-  MOZ_ASSERT(aMouseEvent);
-
-  WidgetMouseEvent* clickEvent =
-    aMouseEvent->WidgetEventPtr()->AsMouseEvent();
-  MOZ_ASSERT(!DetachedFromEditorOrDefaultPrevented(clickEvent));
-
-  if (!Preferences::GetBool("middlemouse.paste", false)) {
-    // Middle click paste isn't enabled.
+  if (aMouseClickEvent->button != WidgetMouseEventBase::eMiddleButton ||
+      !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
     return NS_OK;
   }
 
-  // Set the selection to the point under the mouse cursor:
-  nsCOMPtr<nsINode> parent = aMouseEvent->GetRangeParent();
-  int32_t offset = aMouseEvent->RangeOffset();
-
-  RefPtr<TextEditor> textEditor = mEditorBase->AsTextEditor();
-  MOZ_ASSERT(textEditor);
-
-  RefPtr<Selection> selection = textEditor->GetSelection();
-  if (selection) {
-    selection->Collapse(parent, offset);
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+  if (NS_WARN_IF(!presShell)) {
+    return NS_OK;
+  }
+  nsPresContext* presContext = GetPresContext();
+  if (NS_WARN_IF(!presContext)) {
+    return NS_OK;
   }
-
-  nsresult rv;
-  int32_t clipboard = nsIClipboard::kGlobalClipboard;
-  nsCOMPtr<nsIClipboard> clipboardService =
-    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
-  if (NS_SUCCEEDED(rv)) {
-    bool selectionSupported;
-    rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
-    if (NS_SUCCEEDED(rv) && selectionSupported) {
-      clipboard = nsIClipboard::kSelectionClipboard;
-    }
+  MOZ_ASSERT(!aMouseClickEvent->DefaultPrevented());
+  nsEventStatus status = nsEventStatus_eIgnore;
+  RefPtr<EventStateManager> esm = presContext->EventStateManager();
+  DebugOnly<nsresult> rv =
+    esm->HandleMiddleClickPaste(presShell, aMouseClickEvent, &status,
+                                textEditor);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "Failed to paste for the middle button click");
+  if (status == nsEventStatus_eConsumeNoDefault) {
+    // Prevent the event from propagating up to be possibly handled
+    // again by the containing window:
+    aMouseClickEvent->StopImmediatePropagation();
+    aMouseClickEvent->PreventDefault();
   }
-
-  // If the ctrl key is pressed, we'll do paste as quotation.
-  // Would've used the alt key, but the kde wmgr treats alt-middle specially.
-  if (clickEvent->IsControl()) {
-    textEditor->PasteAsQuotationAsAction(clipboard);
-  } else {
-    textEditor->PasteAsAction(clipboard);
-  }
-
-  // Prevent the event from propagating up to be possibly handled
-  // again by the containing window:
-  clickEvent->StopPropagation();
-  clickEvent->PreventDefault();
-
-  // We processed the event, whether drop/paste succeeded or not
   return NS_OK;
 }
 
 bool
 EditorEventListener::NotifyIMEOfMouseButtonEvent(
                        WidgetMouseEvent* aMouseEvent)
 {
   MOZ_ASSERT(aMouseEvent);
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -43,17 +43,20 @@ class EditorEventListener : public nsIDO
 public:
   EditorEventListener();
 
   virtual nsresult Connect(EditorBase* aEditorBase);
 
   void Disconnect();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSIDOMEVENTLISTENER
+
+  // nsIDOMEventListener
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
+  NS_IMETHOD HandleEvent(dom::Event *aEvent) override;
 
   void SpellCheckIfNeeded();
 
 protected:
   virtual ~EditorEventListener();
 
   nsresult InstallToEditor();
   void UninstallFromEditor();
@@ -61,37 +64,38 @@ protected:
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
   nsresult KeyDown(const WidgetKeyboardEvent* aKeyboardEvent);
   nsresult KeyUp(const WidgetKeyboardEvent* aKeyboardEvent);
 #endif
   nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
   nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
   nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
   void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult MouseDown(dom::MouseEvent* aMouseEvent);
   virtual nsresult MouseUp(dom::MouseEvent* aMouseEvent) { return NS_OK; }
-  virtual nsresult MouseClick(dom::MouseEvent* aMouseEvent);
+  MOZ_CAN_RUN_SCRIPT
+  virtual nsresult MouseClick(WidgetMouseEvent* aMouseClickEvent);
   nsresult Focus(InternalFocusEvent* aFocusEvent);
   nsresult Blur(InternalFocusEvent* aBlurEvent);
-  nsresult DragEnter(dom::DragEvent* aDragEvent);
-  nsresult DragOver(dom::DragEvent* aDragEvent);
+  MOZ_CAN_RUN_SCRIPT nsresult DragEnter(dom::DragEvent* aDragEvent);
+  MOZ_CAN_RUN_SCRIPT nsresult DragOver(dom::DragEvent* aDragEvent);
   nsresult DragExit(dom::DragEvent* aDragEvent);
-  nsresult Drop(dom::DragEvent* aDragEvent);
+  MOZ_CAN_RUN_SCRIPT nsresult Drop(dom::DragEvent* aDragEvent);
 
-  bool CanDrop(dom::DragEvent* aEvent);
+  MOZ_CAN_RUN_SCRIPT bool CanDrop(dom::DragEvent* aEvent);
   void CleanupDragDropCaret();
   nsIPresShell* GetPresShell() const;
   nsPresContext* GetPresContext() const;
   nsIContent* GetFocusedRootContent();
   // Returns true if IME consumes the mouse event.
   bool NotifyIMEOfMouseButtonEvent(WidgetMouseEvent* aMouseEvent);
   bool EditorHasFocus();
   bool IsFileControlTextBox();
   bool ShouldHandleNativeKeyBindings(WidgetKeyboardEvent* aKeyboardEvent);
-  nsresult HandleMiddleClickPaste(dom::MouseEvent* aMouseEvent);
 
   /**
    * DetachedFromEditor() returns true if editor was detached.
    * Otherwise, false.
    */
   bool DetachedFromEditor() const;
 
   /**
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -154,18 +154,21 @@ public:
 
   /**
    * PasteAsQuotationAsAction() pastes content in clipboard with newly created
    * blockquote element.  If the editor is in plaintext mode, will paste the
    * content with appending ">" to start of each line.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType) override;
+  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType,
+                                            bool aDispatchPasteEvent) override;
 
   /**
    * Can we paste |aTransferable| or, if |aTransferable| is null, will a call
    * to pasteTransferable later possibly succeed if given an instance of
    * nsITransferable then? True if the doc is modifiable, and, if
    * |aTransfeable| is non-null, we have pasteable data in |aTransfeable|.
    */
   virtual bool CanPasteTransferable(nsITransferable* aTransferable) override;
@@ -1305,20 +1308,23 @@ protected: // Shouldn't be used by frien
                                   ErrorResult& aRv,
                                   bool* aIsCellSelected = nullptr) const;
 
   /**
    * PasteInternal() pasts text with replacing selected content.
    * This tries to dispatch ePaste event first.  If its defaultPrevent() is
    * called, this does nothing but returns NS_OK.
    *
-   * @param aClipboardType  nsIClipboard::kGlobalClipboard or
-   *                        nsIClipboard::kSelectionClipboard.
+   * @param aClipboardType      nsIClipboard::kGlobalClipboard or
+   *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  nsresult PasteInternal(int32_t aClipboardType);
+  nsresult PasteInternal(int32_t aClipboardType,
+                         bool aDispatchPasteEvent);
 
   /**
    * InsertNodeIntoProperAncestorWithTransaction() attempts to insert aNode
    * into the document, at aPointToInsert.  Checks with strict dtd to see if
    * containment is allowed.  If not allowed, will attempt to find a parent
    * in the parent hierarchy of aPointToInsert.GetContainer() that will accept
    * aNode as a child.  If such a parent is found, will split the document
    * tree from aPointToInsert up to parent, and then insert aNode.
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1454,19 +1454,20 @@ HTMLEditor::HavePrivateHTMLFlavor(nsICli
                                            &bHavePrivateHTMLFlavor))) {
     return bHavePrivateHTMLFlavor;
   }
 
   return false;
 }
 
 nsresult
-HTMLEditor::PasteInternal(int32_t aClipboardType)
+HTMLEditor::PasteInternal(int32_t aClipboardType,
+                          bool aDispatchPasteEvent)
 {
-  if (!FireClipboardEvent(ePaste, aClipboardType)) {
+  if (aDispatchPasteEvent && !FireClipboardEvent(ePaste, aClipboardType)) {
     return NS_OK;
   }
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -1685,22 +1686,24 @@ HTMLEditor::CanPasteTransferable(nsITran
       return true;
     }
   }
 
   return false;
 }
 
 nsresult
-HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
+HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
+                                     bool aDispatchPasteEvent)
 {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
   if (IsPlaintextEditor()) {
+    // XXX In this case, we don't dispatch ePaste event.  Why?
     return PasteAsPlaintextQuotation(aClipboardType);
   }
 
   // If it's not in plain text edit mode, paste text into new
   // <blockquote type="cite"> element after removing selection.
 
   AutoPlaceholderBatch beginBatching(this);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
@@ -1738,17 +1741,21 @@ HTMLEditor::PasteAsQuotationAsAction(int
 
   // Collapse Selection in the new <blockquote> element.
   rv = selection->Collapse(newNode, 0);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // XXX Why don't we call HTMLEditRules::DidDoAction() after Paste()?
-  rv = PasteInternal(aClipboardType);
+  // XXX If ePaste event has not been dispatched yet but selected content
+  //     has already been removed and created a <blockquote> element.
+  //     So, web apps cannot prevent the default of ePaste event which
+  //     will be dispatched by PasteInternal().
+  rv = PasteInternal(aClipboardType, aDispatchPasteEvent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 /**
  * Paste a plaintext quotation.
--- a/editor/libeditor/HTMLEditorEventListener.cpp
+++ b/editor/libeditor/HTMLEditorEventListener.cpp
@@ -178,34 +178,36 @@ HTMLEditorEventListener::MouseDown(Mouse
     int32_t clientY = aMouseEvent->ClientY();
     htmlEditor->OnMouseDown(clientX, clientY, element, aMouseEvent);
   }
 
   return EditorEventListener::MouseDown(aMouseEvent);
 }
 
 nsresult
-HTMLEditorEventListener::MouseClick(MouseEvent* aMouseEvent)
+HTMLEditorEventListener::MouseClick(WidgetMouseEvent* aMouseClickEvent)
 {
   if (NS_WARN_IF(DetachedFromEditor())) {
     return NS_OK;
   }
 
-  RefPtr<EventTarget> target = aMouseEvent->GetTarget();
-  NS_ENSURE_TRUE(target, NS_ERROR_NULL_POINTER);
+  RefPtr<EventTarget> target = aMouseClickEvent->GetDOMEventTarget();
+  if (NS_WARN_IF(!target)) {
+    return NS_ERROR_FAILURE;
+  }
   nsCOMPtr<Element> element = do_QueryInterface(target);
   if (NS_WARN_IF(!element)) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<HTMLEditor> htmlEditor = mEditorBase->AsHTMLEditor();
   MOZ_ASSERT(htmlEditor);
   htmlEditor->DoInlineTableEditingAction(*element);
   // DoInlineTableEditingAction might cause reframe
   // Editor is destroyed.
   if (htmlEditor->Destroyed()) {
     return NS_OK;
   }
 
-  return EditorEventListener::MouseClick(aMouseEvent);
+  return EditorEventListener::MouseClick(aMouseClickEvent);
 }
 
 } // namespace mozilla
--- a/editor/libeditor/HTMLEditorEventListener.h
+++ b/editor/libeditor/HTMLEditorEventListener.h
@@ -25,16 +25,18 @@ public:
   }
 
   /**
    * Connect() fails if aEditorBase isn't an HTMLEditor instance.
    */
   virtual nsresult Connect(EditorBase* aEditorBase) override;
 
 protected:
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult MouseDown(dom::MouseEvent* aMouseEvent) override;
   virtual nsresult MouseUp(dom::MouseEvent* aMouseEvent) override;
-  virtual nsresult MouseClick(dom::MouseEvent* aMouseEvent) override;
+  MOZ_CAN_RUN_SCRIPT
+  virtual nsresult MouseClick(WidgetMouseEvent* aMouseClickEvent) override;
 };
 
 } // namespace mozilla
 
 #endif // #ifndef HTMLEditorEventListener_h
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1931,29 +1931,32 @@ TextEditor::ComputeValueInternal(const n
     return NS_ERROR_FAILURE;
   }
 
   // XXX Why don't we call TextEditRules::DidDoAction() here?
   return encoder->EncodeToString(aOutputString);
 }
 
 nsresult
-TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
+TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
+                                     bool aDispatchPasteEvent)
 {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // XXX Why don't we dispatch ePaste event here?
+
   // Get the nsITransferable interface for getting the data from the clipboard
   nsCOMPtr<nsITransferable> trans;
   rv = PrepareTransferable(getter_AddRefs(trans));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   if (!trans) {
     return NS_OK;
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -113,18 +113,21 @@ public:
 
   /**
    * PasteAsAction() pastes clipboard content to Selection.  This method
    * may dispatch ePaste event first.  If its defaultPrevent() is called,
    * this does nothing but returns NS_OK.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  nsresult PasteAsAction(int32_t aClipboardType);
+  nsresult PasteAsAction(int32_t aClipboardType,
+                         bool aDispatchPasteEvent);
 
   /**
    * InsertTextAsAction() inserts aStringToInsert at selection.
    * Although this method is implementation of nsIPlaintextEditor.insertText(),
    * this treats the input is an edit action.  If you'd like to insert text
    * as part of edit action, you probably should use InsertTextAsSubAction().
    *
    * @param aStringToInsert     The string to insert.
@@ -133,18 +136,21 @@ public:
 
   /**
    * PasteAsQuotationAsAction() pastes content in clipboard as quotation.
    * If the editor is TextEditor or in plaintext mode, will paste the content
    * with appending ">" to start of each line.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType);
+  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType,
+                                            bool aDispatchPasteEvent);
 
   /**
    * DeleteSelectionAsAction() removes selection content or content around
    * caret with transactions.  This should be used for handling it as an
    * edit action.  If you'd like to remove selection for preparing to insert
    * something, you probably should use DeleteSelectionAsSubAction().
    *
    * @param aDirection          How much range should be removed.
@@ -208,16 +214,17 @@ public:
    * OnCompositionChange() is called.
    */
   void OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent);
 
   /**
    * OnDrop() is called from EditorEventListener::Drop that is handler of drop
    * event.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult OnDrop(dom::DragEvent* aDropEvent);
 
   /**
    * ComputeTextValue() computes plaintext value of this editor.  This may be
    * too expensive if it's in hot path.
    *
    * @param aDocumentEncoderFlags   Flags of nsIDocumentEncoder.
    * @param aCharset                Encoding of the document.
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -290,27 +290,29 @@ TextEditor::OnDrop(DragEvent* aDropEvent
   }
 
   ScrollSelectionIntoView(false);
 
   return NS_OK;
 }
 
 nsresult
-TextEditor::PasteAsAction(int32_t aClipboardType)
+TextEditor::PasteAsAction(int32_t aClipboardType,
+                          bool aDispatchPasteEvent)
 {
   if (AsHTMLEditor()) {
-    nsresult rv = AsHTMLEditor()->PasteInternal(aClipboardType);
+    nsresult rv =
+      AsHTMLEditor()->PasteInternal(aClipboardType, aDispatchPasteEvent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
-  if (!FireClipboardEvent(ePaste, aClipboardType)) {
+  if (aDispatchPasteEvent && !FireClipboardEvent(ePaste, aClipboardType)) {
     return NS_OK;
   }
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -1,12 +1,12 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <title>Test for paste as quotation with middle button click</title>
+  <title>Test for paste with middle button click</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none;">
 
@@ -103,27 +103,129 @@ async function doTextareaTests(aTextarea
 
   await copyPlaintext("abc\ndef\n\n");
   aTextarea.focus();
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      "> abc\n> def\n> \n",
      "If pasted text ends with \"\\n\", only the last line should not started with \">\"");
   aTextarea.value = "";
+
+  let pasteEventCount = 0;
+  function pasteEventLogger(event) {
+    pasteEventCount++;
+  }
+  aTextarea.addEventListener("paste", pasteEventLogger);
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  is(aTextarea.value, "",
+     "If 'click' event is consumed at capturing phase of the <body>, paste should be canceled");
+  is(pasteEventCount, 0,
+     "If 'click' event is consumed at capturing phase of the <body>, 'paste' event should not be fired");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  is(aTextarea.value, "abc",
+     "Even if 'mouseup' event is consumed, paste should be done");
+  is(pasteEventCount, 1,
+     "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  is(aTextarea.value, "abc",
+     "Even if 'click' event handler is added to the <textarea>, paste should not be canceled");
+  is(pasteEventCount, 1,
+     "Even if 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  todo_is(aTextarea.value, "",
+          "If 'auxclick' event is consumed, paste should be canceled");
+  todo_is(pasteEventCount, 0,
+          "If 'auxclick' event is consumed, 'paste' event should not be fired once");
+  aTextarea.value = "";
+
+  aTextarea.removeEventListener("paste", pasteEventLogger);
 }
 
 async function doContenteditableTests(aEditableDiv) {
   await copyPlaintext("abc\ndef\nghi");
   aEditableDiv.focus();
   synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
   is(aEditableDiv.innerHTML,
      "<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
      "Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
   aEditableDiv.innerHTML = "";
 
+  let pasteEventCount = 0;
+  function pasteEventLogger(event) {
+    pasteEventCount++;
+  }
+  aEditableDiv.addEventListener("paste", pasteEventLogger);
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  window.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  is(aEditableDiv.innerHTML, "",
+     "If 'click' event is consumed at capturing phase of the window, paste should be canceled");
+  is(pasteEventCount, 0,
+     "If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  is(aEditableDiv.innerHTML, "abc",
+     "Even if 'mouseup' event is consumed, paste should be done");
+  is(pasteEventCount, 1,
+     "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  is(aEditableDiv.innerHTML, "abc",
+     "Even if 'click' event handler is added to the editing host, paste should not be canceled");
+  is(pasteEventCount, 1,
+     "Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  todo_is(aEditableDiv.innerHTML, "",
+          "If 'auxclick' event is consumed, paste should be canceled");
+  todo_is(pasteEventCount, 0,
+          "If 'auxclick' event is consumed, 'paste' event should not be fired");
+  aEditableDiv.innerHTML = "";
+
+  aEditableDiv.removeEventListener("paste", pasteEventLogger);
+
   // Oddly, copyHTMLContent fails randomly only on Linux.  Let's skip this.
   if (navigator.platform.startsWith("Linux")) {
     return;
   }
 
   await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
   aEditableDiv.focus();
   synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
@@ -135,24 +237,100 @@ async function doContenteditableTests(aE
     // Oddly, on Android, we use <br> elements for pasting <p> elements.
     is(aEditableDiv.innerHTML,
        "<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
        "Pasted HTML content should be set to the <blockquote>");
   }
   aEditableDiv.innerHTML = "";
 }
 
+async function doNestedEditorTests(aEditableDiv) {
+  await copyPlaintext("CLIPBOARD TEXT");
+  aEditableDiv.innerHTML = '<p id="p">foo</p><textarea id="textarea"></textarea>';
+  aEditableDiv.focus();
+  let textarea = document.getElementById("textarea");
+  let pasteTarget = null;
+  function onPaste(aEvent) {
+    pasteTarget = aEvent.target;
+  }
+  document.addEventListener("paste", onPaste);
+
+  synthesizeMouseAtCenter(textarea, {button: 1});
+  is(pasteTarget.getAttribute("id"), "textarea",
+     "Target of 'paste' event should be the clicked <textarea>");
+  is(textarea.value, "CLIPBOARD TEXT",
+     "Clicking in <textarea> in an editable <div> should paste the clipboard text into the <textarea>");
+  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea"></textarea>',
+     "Pasting in the <textarea> shouldn't be handled by the HTMLEditor");
+
+  textarea.value = "";
+  textarea.readOnly = true;
+  pasteTarget = null;
+  synthesizeMouseAtCenter(textarea, {button: 1});
+  todo_is(pasteTarget, textarea,
+          "Target of 'paste' event should be the clicked <textarea> even if it's read-only");
+  is(textarea.value, "",
+     "Clicking in read-only <textarea> in an editable <div> should not paste the clipboard text into the read-only <textarea>");
+  // HTMLEditor thinks that read-only <textarea> is not modifiable.
+  // Therefore, HTMLEditor does not paste the text.
+  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" readonly=""></textarea>',
+     "Clicking in read-only <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
+
+  textarea.value = "";
+  textarea.readOnly = false;
+  textarea.disabled = true;
+  pasteTarget = null;
+  synthesizeMouseAtCenter(textarea, {button: 1});
+  // Although, this compares with <textarea>, I'm not sure it's proper event
+  // target because of disabled <textarea>.
+  todo_is(pasteTarget, textarea,
+          "Target of 'paste' event should be the clicked <textarea> even if it's disabled");
+  is(textarea.value, "",
+     "Clicking in disabled <textarea> in an editable <div> should not paste the clipboard text into the disabled <textarea>");
+  // HTMLEditor thinks that disabled <textarea> is not modifiable.
+  // Therefore, HTMLEditor does not paste the text.
+  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" disabled=""></textarea>',
+     "Clicking in disabled <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
+
+  document.removeEventListener("paste", onPaste);
+  aEditableDiv.innerHTML = "";
+}
+
+async function doAfterRemoveOfClickedElementTest(aEditableDiv) {
+  await copyPlaintext("CLIPBOARD TEXT");
+  aEditableDiv.innerHTML = '<p id="p">foo<span id="span">bar</span></p>';
+  aEditableDiv.focus();
+  let span = document.getElementById("span");
+  let pasteTarget = null;
+  document.addEventListener("paste", (aEvent) => { pasteTarget = aEvent.target; }, {once: true});
+  document.addEventListener("auxclick", (aEvent) => {
+    is(aEvent.target.getAttribute("id"), "span",
+       "Target of auxclick event should be the <span> element");
+    span.parentElement.removeChild(span);
+  }, {once: true});
+  synthesizeMouseAtCenter(span, {button: 1});
+  todo_is(pasteTarget.getAttribute("id"), "p",
+          "Target of 'paste' event should be the <p> element since <span> has gone");
+  // XXX Currently, pasted to start of the <p> because EventStateManager
+  //     do not recompute event target frame.
+  todo_is(aEditableDiv.innerHTML, '<p id="p">fooCLIPBOARD TEXT</p>',
+          "Clipbpard text should looks like replacing the <span> element");
+  aEditableDiv.innerHTML = "";
+}
+
 async function doTests() {
   await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
                                            ["middlemouse.contentLoadURL", false]]});
   let container = document.getElementById("container");
   container.innerHTML = "<textarea id=\"editor\"></textarea>";
   await doTextareaTests(document.getElementById("editor"));
   container.innerHTML = "<div id=\"editor\" contenteditable style=\"min-height: 1em;\"></div>";
   await doContenteditableTests(document.getElementById("editor"));
+  await doNestedEditorTests(document.getElementById("editor"));
+  await doAfterRemoveOfClickedElementTest(document.getElementById("editor"));
   SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(doTests);
 </script>
 </pre>
 </body>
 </html>
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7,16 +7,17 @@
 #include "nsLayoutUtils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/layers/PAPZ.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/ContentChild.h"
@@ -2225,16 +2226,69 @@ nsLayoutUtils::GetPopupFrameForEventCoor
           GetEventCoordinatesRelativeTo(aEvent, popup))) {
       return popup;
     }
   }
 #endif
   return nullptr;
 }
 
+void
+nsLayoutUtils::GetContainerAndOffsetAtEvent(nsIPresShell* aPresShell,
+                                            const WidgetEvent* aEvent,
+                                            nsIContent** aContainer,
+                                            int32_t* aOffset)
+{
+  MOZ_ASSERT(aContainer || aOffset);
+
+  if (aContainer) {
+    *aContainer = nullptr;
+  }
+  if (aOffset) {
+    *aOffset = 0;
+  }
+
+  if (!aPresShell) {
+    return;
+  }
+
+  aPresShell->FlushPendingNotifications(FlushType::Layout);
+
+  RefPtr<nsPresContext> presContext = aPresShell->GetPresContext();
+  if (!presContext) {
+    return;
+  }
+
+  nsIFrame* targetFrame = presContext->EventStateManager()->GetEventTarget();
+  if (!targetFrame) {
+    return;
+  }
+
+  nsPoint point =
+    nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, targetFrame);
+
+  if (aContainer) {
+    // TODO: This result may be useful to change to Selection.  However, this
+    //       may return improper node (e.g., native anonymous node) for the
+    //       Selection.  Perhaps, this should take Selection optionally and
+    //       if it's specified, needs to check if it's proper for the
+    //       Selection.
+    nsCOMPtr<nsIContent> container =
+      targetFrame->GetContentOffsetsFromPoint(point).content;
+    if (container &&
+        (!container->ChromeOnlyAccess() ||
+         nsContentUtils::CanAccessNativeAnon())) {
+      container.forget(aContainer);
+    }
+  }
+  if (aOffset) {
+    *aOffset = targetFrame->GetContentOffsetsFromPoint(point).offset;
+  }
+}
+
 static void ConstrainToCoordValues(float& aStart, float& aSize)
 {
   MOZ_ASSERT(aSize >= 0);
 
   // Here we try to make sure that the resulting nsRect will continue to cover
   // as much of the area that was covered by the original gfx Rect as possible.
 
   // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -772,16 +772,32 @@ public:
    * @return        Null, if there is no popup frame at the point, otherwise,
    *                returns top-most popup frame at the point.
    */
   static nsIFrame* GetPopupFrameForEventCoordinates(
                      nsPresContext* aPresContext,
                      const mozilla::WidgetEvent* aEvent);
 
   /**
+   * Get container and offset if aEvent collapses Selection.
+   * @param aPresShell      The PresShell handling aEvent.
+   * @param aEvent          The event having coordinates where you want to
+   *                        collapse Selection.
+   * @param aContainer      Returns the container node at the point.
+   *                        Set nullptr if you don't need this.
+   * @param aOffset         Returns offset in the container node at the point.
+   *                        Set nullptr if you don't need this.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  static void GetContainerAndOffsetAtEvent(nsIPresShell* aPresShell,
+                                           const mozilla::WidgetEvent* aEvent,
+                                           nsIContent** aContainer,
+                                           int32_t* aOffset);
+
+  /**
    * Translate from widget coordinates to the view's coordinates
    * @param aPresContext the PresContext for the view
    * @param aWidget the widget
    * @param aPt the point relative to the widget
    * @param aView  view to which returned coordinates are relative
    * @return the point in the view's coordinates
    */
   static nsPoint TranslateWidgetToView(nsPresContext* aPresContext,
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -627,17 +627,17 @@ nsXULPopupManager::InitTriggerEvent(Even
       // get the trigger content from the event
       nsCOMPtr<nsIContent> target = do_QueryInterface(aEvent->GetTarget());
       target.forget(aTriggerContent);
     }
   }
 
   mCachedModifiers = 0;
 
-  UIEvent* uiEvent = aEvent ? aEvent->AsUIEvent() : nullptr;
+  RefPtr<UIEvent> uiEvent = aEvent ? aEvent->AsUIEvent() : nullptr;
   if (uiEvent) {
     mRangeParent = uiEvent->GetRangeParent();
     mRangeOffset = uiEvent->RangeOffset();
 
     // get the event coordinates relative to the root frame of the document
     // containing the popup.
     NS_ASSERTION(aPopup, "Expected a popup node");
     WidgetEvent* event = aEvent->WidgetEventPtr();
--- a/layout/xul/nsXULPopupManager.h
+++ b/layout/xul/nsXULPopupManager.h
@@ -709,16 +709,17 @@ protected:
   nsMenuChainItem* GetTopVisibleMenu();
 
   // Hide all of the visible popups from the given list. This function can
   // cause style changes and frame destruction.
   void HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames);
 
   // set the event that was used to trigger the popup, or null to clear the
   // event details. aTriggerContent will be set to the target of the event.
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void InitTriggerEvent(mozilla::dom::Event* aEvent, nsIContent* aPopup, nsIContent** aTriggerContent);
 
   // callbacks for ShowPopup and HidePopup as events may be done asynchronously
   void ShowPopupCallback(nsIContent* aPopup,
                          nsMenuPopupFrame* aPopupFrame,
                          bool aIsContextMenu,
                          bool aSelectFirstItem);
   void HidePopupCallback(nsIContent* aPopup,
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1742,17 +1742,17 @@ public class BrowserApp extends GeckoApp
                         !IntentUtils.getIsInAutomationFromEnvironment(new SafeIntent(getIntent()))) {
                     // TODO: Better scheduling of DLC actions (Bug 1257492)
                     DlcSyncService.enqueueServiceWork(this);
                 }
 
                 break;
 
             case "GeckoView:AccessibilityEnabled":
-                mDynamicToolbar.setAccessibilityEnabled(message.getBoolean("touchEnabled"));
+                mDynamicToolbar.setAccessibilityEnabled(message.getBoolean("enabled"));
                 break;
 
             case "Menu:Open":
                 if (mBrowserToolbar.isEditing()) {
                     mBrowserToolbar.cancelEdit();
                 }
                 openOptionsMenu();
                 break;
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -62,16 +62,17 @@ public class Tabs implements BundleEvent
     private volatile CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
 
     // A cache that maps a tab ID to an mOrder tab position.  All access should be synchronized.
     private final TabPositionCache tabPositionCache = new TabPositionCache();
 
     // All writes to mSelectedTab must be synchronized on the Tabs instance.
     // In general, it's preferred to always use selectTab()).
     private volatile Tab mSelectedTab;
+    private volatile int mPreviouslySelectedTabId = INVALID_TAB_ID;
 
     // All accesses to mTabs must be synchronized on the Tabs instance.
     private final HashMap<Integer, Tab> mTabs = new HashMap<Integer, Tab>();
 
     private AccountManager mAccountManager;
     private OnAccountsUpdateListener mAccountListener;
 
     public static final int LOADURL_NONE         = 0;
@@ -332,16 +333,17 @@ public class Tabs implements BundleEvent
         }
 
         mSelectedTab = tab;
         mSelectedTab.updatePageAction();
 
         notifyListeners(tab, TabEvents.SELECTED);
 
         if (oldTab != null) {
+            mPreviouslySelectedTabId = oldTab.getId();
             notifyListeners(oldTab, TabEvents.UNSELECTED);
         }
 
         // Pass a message to Gecko to update tab state in BrowserApp.
         final GeckoBundle data = new GeckoBundle(1);
         data.putInt("id", tab.getId());
         mEventDispatcher.dispatch("Tab:Selected", data);
         EventDispatcher.getInstance().dispatch("Tab:Selected", data);
@@ -487,25 +489,22 @@ public class Tabs implements BundleEvent
             Tab lastTab = mOrder.get(mOrder.size() - 1);
             if (!lastTab.isPrivate()) {
                 nextTab = lastTab;
             } else {
                 nextTab = getPreviousTabFrom(lastTab, false);
             }
         }
 
-        Tab parent = getTab(tab.getParentId());
-        if (parent != null) {
-            // If the next tab is a sibling, switch to it. Otherwise go back to the parent.
-            if (nextTab != null && nextTab.getParentId() == tab.getParentId())
-                return nextTab;
-            else
-                return parent;
+        final Tab parentTab = getTab(tab.getParentId());
+        if (tab.getParentId() == mPreviouslySelectedTabId && tab.getParentId() != INVALID_TAB_ID && parentTab != null) {
+            return parentTab;
+        } else {
+            return nextTab;
         }
-        return nextTab;
     }
 
     public Iterable<Tab> getTabsInOrder() {
         return mOrder;
     }
 
     /**
      * @return the current GeckoApp instance, or throws if
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -481,17 +481,17 @@ var BrowserApp = {
       // Make sure the "Open in App" context menu item appears at the bottom of the list
       this.initContextMenu();
       ExternalApps.init();
     }, NativeWindow, "contextmenus");
 
     if (AppConstants.ACCESSIBILITY) {
       InitLater(() => GlobalEventDispatcher.dispatch("GeckoView:AccessibilityReady"));
       GlobalEventDispatcher.registerListener((aEvent, aData, aCallback) => {
-        if (aData.touchEnabled) {
+        if (aData.enabled) {
           AccessFu.enable();
         } else {
           AccessFu.disable();
         }
       }, "GeckoView:AccessibilitySettings");
     }
 
     InitLater(() => {
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -1,23 +1,23 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 
 import android.graphics.Rect
 
 import android.os.Build
 import android.os.Bundle
-import android.os.SystemClock
 
 import android.support.test.filters.MediumTest
 import android.support.test.InstrumentationRegistry
 import android.support.test.runner.AndroidJUnit4
 import android.text.InputType
 import android.util.SparseLongArray
 
 import android.view.accessibility.AccessibilityNodeInfo
@@ -92,17 +92,16 @@ class AccessibilityTest : BaseSessionTes
         fun onClicked(event: AccessibilityEvent) { }
         fun onFocused(event: AccessibilityEvent) { }
         fun onSelected(event: AccessibilityEvent) { }
         fun onScrolled(event: AccessibilityEvent) { }
         fun onTextSelectionChanged(event: AccessibilityEvent) { }
         fun onTextChanged(event: AccessibilityEvent) { }
         fun onTextTraversal(event: AccessibilityEvent) { }
         fun onWinContentChanged(event: AccessibilityEvent) { }
-        fun onWinStateChanged(event: AccessibilityEvent) { }
     }
 
     @Before fun setup() {
         // We initialize a view with a parent and grandparent so that the
         // accessibility events propagate up at least to the parent.
         view = FrameLayout(InstrumentationRegistry.getTargetContext())
         FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view)
         FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view.parent as View)
@@ -122,46 +121,35 @@ class AccessibilityTest : BaseSessionTes
                     AccessibilityEvent.TYPE_VIEW_CLICKED -> newDelegate.onClicked(event)
                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
                     AccessibilityEvent.TYPE_VIEW_SELECTED -> newDelegate.onSelected(event)
                     AccessibilityEvent.TYPE_VIEW_SCROLLED -> newDelegate.onScrolled(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> newDelegate.onTextSelectionChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
                     AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
-                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
                     else -> {}
                 }
                 return false
             }
         }) },
         { (view.parent as View).setAccessibilityDelegate(null) },
         object : EventDelegate { })
     }
 
     @After fun teardown() {
         sessionRule.session.accessibility.view = null
         nodeInfos.forEach { node -> node.recycle() }
     }
 
-    private fun waitForInitialFocus(moveToFirstChild: Boolean = false) {
-        // XXX: Sometimes we get the window state change of the initial
-        // about:blank page loading. Need to figure out how to ignore that.
+    private fun waitForInitialFocus() {
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onFocused(event: AccessibilityEvent) { }
-
-            @AssertCalled
-            override fun onWinStateChanged(event: AccessibilityEvent) { }
         })
-
-        if (moveToFirstChild) {
-            provider.performAction(View.NO_ID,
-                AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
-        }
     }
 
     @Test fun testRootNode() {
         assertThat("provider is not null", provider, notNullValue())
         val node = createNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID)
         assertThat("Root node should have WebView class name",
             node.className.toString(), equalTo("android.webkit.WebView"))
     }
@@ -173,53 +161,52 @@ class AccessibilityTest : BaseSessionTes
             @AssertCalled(count = 1)
             override fun onFocused(event: AccessibilityEvent) { }
         })
     }
 
     @Test fun testAccessibilityFocus() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(INPUTS_PATH)
-        waitForInitialFocus(true)
+        waitForInitialFocus()
+
+        provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
+            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
-                assertThat("Label accessibility focused", node.className.toString(),
-                        equalTo("android.view.View"))
                 assertThat("Text node should not be focusable", node.isFocusable, equalTo(false))
             }
         })
 
         provider.performAction(nodeId,
             AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
-                assertThat("Editbox accessibility focused", node.className.toString(),
-                        equalTo("android.widget.EditText"))
                 assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
             }
         })
     }
 
     @Test fun testTextEntryNode() {
         sessionRule.session.loadString("<input aria-label='Name' value='Tobias'>", "text/html")
         waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
-            override fun onFocused(event: AccessibilityEvent) {
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 val nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Focused EditBox", node.className.toString(),
                         equalTo("android.widget.EditText"))
                 if (Build.VERSION.SDK_INT >= 19) {
                     assertThat("Hint has field name",
                             node.extras.getString("AccessibilityNodeInfo.hint"),
                             equalTo("Name"))
@@ -292,17 +279,17 @@ class AccessibilityTest : BaseSessionTes
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
         sessionRule.session.loadString("<input value='hello cruel world' id='input'>", "text/html")
         waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
-            override fun onFocused(event: AccessibilityEvent) {
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Focused EditBox", node.className.toString(),
                         equalTo("android.widget.EditText"))
             }
 
             @AssertCalled(count = 1)
             override fun onTextSelectionChanged(event: AccessibilityEvent) {
@@ -337,17 +324,20 @@ class AccessibilityTest : BaseSessionTes
                 assertThat("text should be pasted", event.text[0].toString(), equalTo("hello cruel cruel cruel"))
             }
         })
     }
 
     @Test fun testMoveByCharacter() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        waitForInitialFocus(true)
+        waitForInitialFocus()
+
+        provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
+                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Accessibility focus on first paragraph", node.text as String, startsWith("Lorem ipsum"))
             }
@@ -367,17 +357,20 @@ class AccessibilityTest : BaseSessionTes
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
         waitUntilTextTraversed(0, 1) // "L"
     }
 
     @Test fun testMoveByWord() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        waitForInitialFocus(true)
+        waitForInitialFocus()
+
+        provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
+                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Accessibility focus on first paragraph", node.text as String, startsWith("Lorem ipsum"))
             }
@@ -397,17 +390,17 @@ class AccessibilityTest : BaseSessionTes
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
         waitUntilTextTraversed(0, 5) // "Lorem"
     }
 
     @Test fun testMoveByLine() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
-        waitForInitialFocus(true)
+        waitForInitialFocus()
 
         provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
                 AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
@@ -429,57 +422,58 @@ class AccessibilityTest : BaseSessionTes
         provider.performAction(nodeId,
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
         waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
     }
 
     @Test fun testCheckbox() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
-        sessionRule.session.loadString("<label><input type='checkbox'>many option</label>", "text/html")
-        waitForInitialFocus(true)
+        sessionRule.session.loadString("<label><input id='checkbox' type='checkbox'>many option</label>", "text/html")
+        waitForInitialFocus()
 
+        mainSession.evaluateJS("$('#checkbox').focus()")
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 var node = createNodeInfo(nodeId)
                 assertThat("Checkbox node is checkable", node.isCheckable, equalTo(true))
                 assertThat("Checkbox node is clickable", node.isClickable, equalTo(true))
                 assertThat("Checkbox node is focusable", node.isFocusable, equalTo(true))
                 assertThat("Checkbox node is not checked", node.isChecked, equalTo(false))
-                assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option"))
+                assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option check button"))
             }
         })
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
         waitUntilClick(true)
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
         waitUntilClick(false)
     }
 
     @Test fun testSelectable() {
         var nodeId = View.NO_ID
         sessionRule.session.loadString(
                 """<ul style="list-style-type: none;" role="listbox">
                         <li id="li" role="option" onclick="this.setAttribute('aria-selected',
                             this.getAttribute('aria-selected') == 'true' ? 'false' : 'true')">1</li>
-                        <li role="option" aria-selected="false">2</li>
                 </ul>""","text/html")
-        waitForInitialFocus(true)
+        waitForInitialFocus()
 
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 var node = createNodeInfo(nodeId)
                 assertThat("Selectable node is clickable", node.isClickable, equalTo(true))
                 assertThat("Selectable node is not selected", node.isSelected, equalTo(false))
-                assertThat("Selectable node has correct text", node.text.toString(), equalTo("1"))
+                assertThat("Selectable node has correct role", node.text.toString(), equalTo("1 option list box"))
             }
         })
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
         waitUntilSelect(true)
 
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
         waitUntilSelect(false)
@@ -503,104 +497,111 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.session.loadString(
                 """<body style="margin: 0;">
                         <div style="height: 100vh;"></div>
                         <button>Hello</button>
                         <p style="margin: 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                             sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                 </body>""",
                 "text/html")
+        sessionRule.waitForPageStop()
 
         sessionRule.waitUntilCalled(object : EventDelegate {
-            @AssertCalled
-            override fun onWinStateChanged(event: AccessibilityEvent) { }
-
             @AssertCalled(count = 1)
             override fun onFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 var node = createNodeInfo(nodeId)
                 var nodeBounds = Rect()
                 node.getBoundsInParent(nodeBounds)
                 assertThat("Default root node bounds are correct", nodeBounds, equalTo(screenRect))
             }
         })
 
-        provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
+        provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onScrolled(event: AccessibilityEvent) {
                 assertThat("View is scrolled for focused node to be onscreen", event.scrollY, greaterThan(0))
                 assertThat("View is not scrolled to the end", event.scrollY, lessThan(event.maxScrollY))
             }
 
             @AssertCalled(count = 1, order = [3])
             override fun onWinContentChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
         })
 
-        SystemClock.sleep(100);
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onScrolled(event: AccessibilityEvent) {
-                assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
+                assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onWinContentChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
                 assertThat("Focused node is still onscreen", screenContainsNode(nodeId), equalTo(true))
             }
         })
 
-        SystemClock.sleep(100)
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onScrolled(event: AccessibilityEvent) {
                 assertThat("View is scrolled to the beginning", event.scrollY, equalTo(0))
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onWinContentChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
                 assertThat("Focused node is offscreen", screenContainsNode(nodeId), equalTo(false))
             }
         })
 
-        SystemClock.sleep(100)
         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onScrolled(event: AccessibilityEvent) {
-                assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
+                assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
             }
 
             @AssertCalled(count = 1, order = [3])
             override fun onWinContentChanged(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
             }
         })
     }
 
+    @ReuseSession(false) // XXX automation crash fix (bug 1485107)
+    @WithDevToolsAPI
     @Test fun autoFill() {
         // Wait for the accessibility nodes to populate.
         mainSession.loadTestPath(FORMS_HTML_PATH)
-        waitForInitialFocus()
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            // For the root document and the iframe document, each has a form group and
+            // a group for inputs outside of forms, so the total count is 4.
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
 
         val autoFills = mapOf(
                 "#user1" to "bar", "#pass1" to "baz", "#user2" to "bar", "#pass2" to "baz") +
                 if (Build.VERSION.SDK_INT >= 19) mapOf(
                         "#email1" to "a@b.c", "#number1" to "24", "#tel1" to "42")
                 else mapOf(
                         "#email1" to "bar", "#number1" to "", "#tel1" to "bar")
 
@@ -660,180 +661,74 @@ class AccessibilityTest : BaseSessionTes
         autoFillChild(View.NO_ID, createNodeInfo(View.NO_ID))
 
         // Wait on the promises and check for correct values.
         for ((actual, expected) in promises.map { it.value.asJSList<String>() }) {
             assertThat("Auto-filled value must match", actual, equalTo(expected))
         }
     }
 
+    @ReuseSession(false) // XXX automation crash fix (bug 1485107)
     @Test fun autoFill_navigation() {
         fun countAutoFillNodes(cond: (AccessibilityNodeInfo) -> Boolean =
                                        { it.className == "android.widget.EditText" },
                                id: Int = View.NO_ID): Int {
             val info = createNodeInfo(id)
-            return (if (cond(info) && info.className != "android.webkit.WebView" ) 1 else 0) + (if (info.childCount > 0)
+            return (if (cond(info)) 1 else 0) + (if (info.childCount > 0)
                 (0 until info.childCount).sumBy {
                     countAutoFillNodes(cond, info.getChildId(it))
                 } else 0)
         }
 
         // Wait for the accessibility nodes to populate.
         mainSession.loadTestPath(FORMS_HTML_PATH)
-        waitForInitialFocus()
-
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
         assertThat("Initial auto-fill count should match",
                    countAutoFillNodes(), equalTo(14))
         assertThat("Password auto-fill count should match",
                    countAutoFillNodes({ it.isPassword }), equalTo(4))
 
         // Now wait for the nodes to clear.
         mainSession.loadTestPath(HELLO_HTML_PATH)
-        waitForInitialFocus()
+        mainSession.waitForPageStop()
         assertThat("Should not have auto-fill fields",
                    countAutoFillNodes(), equalTo(0))
 
         // Now wait for the nodes to reappear.
         mainSession.goBack()
-        waitForInitialFocus()
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 4)
+            override fun onWinContentChanged(event: AccessibilityEvent) {
+            }
+        })
         assertThat("Should have auto-fill fields again",
                    countAutoFillNodes(), equalTo(14))
         assertThat("Should not have focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(0))
 
         mainSession.evaluateJS("$('#pass1').focus()")
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled
             override fun onFocused(event: AccessibilityEvent) {
             }
         })
         assertThat("Should have one focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(1))
+        // The focused field, its siblings, and its parent should be visible.
+        assertThat("Should have six visible nodes",
+                   countAutoFillNodes({ node -> node.isVisibleToUser &&
+                           !(Rect().also({ node.getBoundsInScreen(it) }).isEmpty) }),
+                   equalTo(6))
 
         mainSession.evaluateJS("$('#pass1').blur()")
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled
             override fun onFocused(event: AccessibilityEvent) {
             }
         })
         assertThat("Should not have focused field",
                    countAutoFillNodes({ it.isFocused }), equalTo(0))
     }
-
-    @Test fun testTree() {
-        sessionRule.session.loadString(
-                "<label for='name'>Name:</label><input id='name' type='text' value='Julie'><button>Submit</button>",
-                "text/html")
-        waitForInitialFocus()
-
-        val rootNode = createNodeInfo(View.NO_ID)
-        assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
-
-        val labelNode = createNodeInfo(rootNode.getChildId(0))
-        assertThat("First node is a label", labelNode.className.toString(), equalTo("android.view.View"))
-        assertThat("Label has text", labelNode.text.toString(), equalTo("Name:"))
-
-        val entryNode = createNodeInfo(rootNode.getChildId(1))
-        assertThat("Second node is an entry", entryNode.className.toString(), equalTo("android.widget.EditText"))
-        assertThat("Entry value is text", entryNode.text.toString(), equalTo("Julie"))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("Entry hint is label",
-                    entryNode.extras.getString("AccessibilityNodeInfo.hint"),
-                    equalTo("Name:"))
-            assertThat("Entry input type is correct", entryNode.inputType,
-                    equalTo(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
-        }
-
-
-        val buttonNode = createNodeInfo(rootNode.getChildId(2))
-        assertThat("Last node is a button", buttonNode.className.toString(), equalTo("android.widget.Button"))
-        assertThat("Button has a single text leaf", buttonNode.childCount, equalTo(1))
-        assertThat("Button has correct text", buttonNode.text.toString(), equalTo("Submit"))
-
-        val textLeaf = createNodeInfo(buttonNode.getChildId(0))
-        assertThat("First node is a label", textLeaf.className.toString(), equalTo("android.view.View"))
-        assertThat("Text leaf has correct text", textLeaf.text.toString(), equalTo("Submit"))
-    }
-
-    @Test fun testCollection() {
-        sessionRule.session.loadString(
-                """<ul>
-                  |  <li>One</li>
-                  |  <li>Two</li>
-                  |</ul>
-                  |<ul>
-                  |  <li>1<ul><li>1.1</li><li>1.2</li></ul></li>
-                  |</ul>
-                """.trimMargin(),
-                "text/html")
-        waitForInitialFocus()
-
-        val rootNode = createNodeInfo(View.NO_ID)
-        assertThat("Document has 2 children", rootNode.childCount, equalTo(2))
-
-        val firstList = createNodeInfo(rootNode.getChildId(0))
-        assertThat("First list has 2 children", firstList.childCount, equalTo(2))
-        assertThat("List is a ListView", firstList.className.toString(), equalTo("android.widget.ListView"))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("First list should have collectionInfo", firstList.collectionInfo, notNullValue())
-            assertThat("First list has 2 rowCount", firstList.collectionInfo.rowCount, equalTo(2))
-            assertThat("First list should not be hierarchical", firstList.collectionInfo.isHierarchical, equalTo(false))
-        }
-
-        val firstListFirstItem = createNodeInfo(firstList.getChildId(0))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("Item has collectionItemInfo", firstListFirstItem.collectionItemInfo, notNullValue())
-            assertThat("Item has collectionItemInfo", firstListFirstItem.collectionItemInfo.rowIndex, equalTo(1))
-        }
-
-        val secondList = createNodeInfo(rootNode.getChildId(1))
-        assertThat("Second list has 1 child", secondList.childCount, equalTo(1))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("Second list should have collectionInfo", secondList.collectionInfo, notNullValue())
-            assertThat("Second list has 2 rowCount", secondList.collectionInfo.rowCount, equalTo(1))
-            assertThat("Second list should be hierarchical", secondList.collectionInfo.isHierarchical, equalTo(true))
-        }
-    }
-
-    @Test fun testRange() {
-        sessionRule.session.loadString(
-                """<input type="range" aria-label="Rating" min="1" max="10" value="4">
-                  |<input type="range" aria-label="Stars" min="1" max="5" step="0.5" value="4.5">
-                  |<input type="range" aria-label="Percent" min="0" max="1" step="0.01" value="0.83">
-                """.trimMargin(),
-                "text/html")
-        waitForInitialFocus()
-
-        val rootNode = createNodeInfo(View.NO_ID)
-        assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
-
-        val firstRange = createNodeInfo(rootNode.getChildId(0))
-        assertThat("Range has right label", firstRange.text.toString(), equalTo("Rating"))
-        assertThat("Range is SeekBar", firstRange.className.toString(), equalTo("android.widget.SeekBar"))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("'Rating' has rangeInfo", firstRange.rangeInfo, notNullValue())
-            assertThat("'Rating' has correct value", firstRange.rangeInfo.current, equalTo(4f))
-            assertThat("'Rating' has correct max", firstRange.rangeInfo.max, equalTo(10f))
-            assertThat("'Rating' has correct min", firstRange.rangeInfo.min, equalTo(1f))
-            assertThat("'Rating' has correct range type", firstRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT))
-        }
-
-        val secondRange = createNodeInfo(rootNode.getChildId(1))
-        assertThat("Range has right label", secondRange.text.toString(), equalTo("Stars"))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("'Rating' has rangeInfo", secondRange.rangeInfo, notNullValue())
-            assertThat("'Rating' has correct value", secondRange.rangeInfo.current, equalTo(4.5f))
-            assertThat("'Rating' has correct max", secondRange.rangeInfo.max, equalTo(5f))
-            assertThat("'Rating' has correct min", secondRange.rangeInfo.min, equalTo(1f))
-            assertThat("'Rating' has correct range type", secondRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_FLOAT))
-        }
-
-        val thirdRange = createNodeInfo(rootNode.getChildId(2))
-        assertThat("Range has right label", thirdRange.text.toString(), equalTo("Percent"))
-        if (Build.VERSION.SDK_INT >= 19) {
-            assertThat("'Rating' has rangeInfo", thirdRange.rangeInfo, notNullValue())
-            assertThat("'Rating' has correct value", thirdRange.rangeInfo.current, equalTo(0.83f))
-            assertThat("'Rating' has correct max", thirdRange.rangeInfo.max, equalTo(1f))
-            assertThat("'Rating' has correct min", thirdRange.rangeInfo.min, equalTo(0f))
-            assertThat("'Rating' has correct range type", thirdRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_PERCENT))
-        }
-    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -1079,16 +1079,19 @@ public class GeckoSession extends LayerS
         onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
     }
 
     private void onWindowChanged(int change, boolean inProgress) {
         if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
             mTextInput.onWindowChanged(mWindow);
         }
         if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
+            if (mAccessibility != null) {
+                mAccessibility.clearAutoFill();
+            }
             mTextInput.clearAutoFill();
         }
     }
 
     /**
      * Get the SessionTextInput instance for this session. May be called on any thread.
      *
      * @return SessionTextInput instance.
@@ -1408,16 +1411,20 @@ public class GeckoSession extends LayerS
      *                false if the session should lose focus.
      *
      * @see #setActive
      */
     public void setFocused(boolean focused) {
         final GeckoBundle msg = new GeckoBundle(1);
         msg.putBoolean("focused", focused);
         mEventDispatcher.dispatch("GeckoView:SetFocused", msg);
+
+        if (focused && mAccessibility != null) {
+            mAccessibility.onWindowFocus();
+        }
     }
 
     /**
      * Class representing a saved session state.
      */
     public static class SessionState implements Parcelable {
         private String mState;
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -1,126 +1,182 @@
 /* -*- 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.GeckoThread;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.PrefsHelper;
+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 org.mozilla.gecko.mozglue.JNIObject;
 
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.text.InputType;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 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.AccessibilityNodeInfo.RangeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
-import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
 public class SessionAccessibility {
     private static final String LOGTAG = "GeckoAccessibility";
+    private static final boolean DEBUG = false;
+
+    // This is a special ID we use for nodes that are event 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;
+
+    private static final int ACTION_SET_TEXT = 0x200000;
     private static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
             "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
 
-    @WrapForJNI static final int FLAG_ACCESSIBILITY_FOCUSED = 0;
-    @WrapForJNI static final int FLAG_CHECKABLE = 1 << 1;
-    @WrapForJNI static final int FLAG_CHECKED = 1 << 2;
-    @WrapForJNI static final int FLAG_CLICKABLE = 1 << 3;
-    @WrapForJNI static final int FLAG_CONTENT_INVALID = 1 << 4;
-    @WrapForJNI static final int FLAG_CONTEXT_CLICKABLE = 1 << 5;
-    @WrapForJNI static final int FLAG_EDITABLE = 1 << 6;
-    @WrapForJNI static final int FLAG_ENABLED = 1 << 7;
-    @WrapForJNI static final int FLAG_FOCUSABLE = 1 << 8;
-    @WrapForJNI static final int FLAG_FOCUSED = 1 << 9;
-    @WrapForJNI static final int FLAG_LONG_CLICKABLE = 1 << 10;
-    @WrapForJNI static final int FLAG_MULTI_LINE = 1 << 11;
-    @WrapForJNI static final int FLAG_PASSWORD = 1 << 12;
-    @WrapForJNI static final int FLAG_SCROLLABLE = 1 << 13;
-    @WrapForJNI static final int FLAG_SELECTED = 1 << 14;
-    @WrapForJNI static final int FLAG_VISIBLE_TO_USER = 1 << 15;
-    @WrapForJNI static final int FLAG_SELECTABLE = 1 << 16;
-
-
     /* package */ final class NodeProvider extends AccessibilityNodeProvider {
         @Override
         public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
-            AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView, virtualDescendantId);
-            if (mAttached) {
-                populateNodeFromBundle(node, nativeProvider.getNodeInfo(virtualDescendantId));
-            } else {
+            AccessibilityNodeInfo info = getAutoFillNode(virtualDescendantId);
+            if (info != null) {
+                // Try auto-fill nodes first.
+                return info;
+            }
+
+            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.
                 if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
                     // When running junit tests we don't have a display
-                    mView.onInitializeAccessibilityNodeInfo(node);
+                    mView.onInitializeAccessibilityNodeInfo(info);
+                }
+                info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+                info.setClassName("android.webkit.WebView"); // TODO: WTF
+
+                if (Build.VERSION.SDK_INT >= 19) {
+                    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");
                 }
-                node.setClassName("android.webkit.WebView");
+                info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+
+                if (mAutoFillRoots != null) {
+                    // Add auto-fill nodes.
+                    if (DEBUG) {
+                        Log.d(LOGTAG, "Adding roots " + mAutoFillRoots);
+                    }
+                    for (int i = 0; i < mAutoFillRoots.size(); i++) {
+                        info.addChild(mView, mAutoFillRoots.keyAt(i));
+                    }
+                }
+                break;
+            default:
+                info.setParent(mView);
+                info.setSource(mView, virtualDescendantId);
+                info.setVisibleToUser(mView.isShown());
+                info.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+                info.setEnabled(true);
+                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);
+                break;
             }
-
-            node.setAccessibilityFocused(mAccessibilityFocusedNode == virtualDescendantId);
-            return node;
+            return info;
         }
 
         @Override
-        public boolean performAction(final int virtualViewId, int action, Bundle arguments) {
-            final GeckoBundle data;
+        public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+            if (virtualViewId == View.NO_ID) {
+                return performRootAction(action, arguments);
+            }
+            if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                final String value = arguments.getString(Build.VERSION.SDK_INT >= 21
+                        ? AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+                        : ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                return performAutoFill(virtualViewId, value);
+            }
+            return performContentAction(action, arguments);
+        }
 
+        private boolean performRootAction(int action, Bundle arguments) {
             switch (action) {
             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
-                    if (virtualViewId == View.NO_ID) {
-                        sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, View.NO_ID, null, null);
-                    } else {
-                        final GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
-                        final int flags = nodeInfo.getInt("flags");
-                        if ((flags & FLAG_FOCUSED) != 0) {
-                            mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityCursorToFocused", null);
-                        } else {
-                            sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, virtualViewId, null, nodeInfo);
-                        }
-                    }
+            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);
+        }
+
+        @SuppressWarnings("fallthrough")
+        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);
-                GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
-                final int flags = nodeInfo.getInt("flags");
-                if ((flags & (FLAG_SELECTABLE | FLAG_CHECKABLE)) == 0) {
-                    sendEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, virtualViewId, null, nodeInfo);
-                }
                 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_SELECT:
                 mSession.getEventDispatcher().dispatch("GeckoView:AccessibilitySelect", null);
                 return true;
             case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+                if (mLastItem) {
+                    return false;
+                }
+                // fall-through
             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 ?
@@ -163,243 +219,65 @@ public class SessionAccessibility {
                 return true;
             case AccessibilityNodeInfo.ACTION_CUT:
             case AccessibilityNodeInfo.ACTION_COPY:
             case AccessibilityNodeInfo.ACTION_PASTE:
                 data = new GeckoBundle(1);
                 data.putInt("action", action);
                 mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityClipboard", data);
                 return true;
-            case AccessibilityNodeInfo.ACTION_SET_TEXT:
-                final String value = arguments.getString(Build.VERSION.SDK_INT >= 21
-                        ? AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
-                        : ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
-                if (mAttached) {
-                    nativeProvider.setText(virtualViewId, value);
-                }
-                return true;
             }
 
             return mView.performAccessibilityAction(action, arguments);
         }
-
-        @Override
-        public AccessibilityNodeInfo findFocus(int focus) {
-          if (focus == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY &&
-              mAccessibilityFocusedNode != 0) {
-            return createAccessibilityNodeInfo(mAccessibilityFocusedNode);
-          }
-
-          return super.findFocus(focus);
-        }
-
-        private void populateNodeFromBundle(final AccessibilityNodeInfo node, final GeckoBundle nodeInfo) {
-            if (mView == null || nodeInfo == null) {
-                return;
-            }
-
-            boolean isRoot = nodeInfo.getInt("id") == View.NO_ID;
-            if (isRoot) {
-                if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
-                    // When running junit tests we don't have a display
-                    mView.onInitializeAccessibilityNodeInfo(node);
-                }
-                node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-                node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-            } else {
-                node.setParent(mView, nodeInfo.getInt("parentId", View.NO_ID));
-            }
-
-            final int flags = nodeInfo.getInt("flags");
-
-            // The basics
-            node.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
-            node.setClassName(nodeInfo.getString("className", "android.view.View"));
-            node.setText(nodeInfo.getString("text", ""));
-
-            // Add actions
-            node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
-            node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-            node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
-            node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
-            node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
-            node.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
-            node.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
-                    AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
-                    AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
-                    AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
-            if ((flags & FLAG_CLICKABLE) != 0) {
-                node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
-            }
-
-
-            // Set boolean properties
-            node.setAccessibilityFocused((flags & FLAG_ACCESSIBILITY_FOCUSED) != 0);
-            node.setCheckable((flags & FLAG_CHECKABLE) != 0);
-            node.setChecked((flags & FLAG_CHECKED) != 0);
-            node.setClickable((flags & FLAG_CLICKABLE) != 0);
-            node.setEnabled((flags & FLAG_ENABLED) != 0);
-            node.setFocusable((flags & FLAG_FOCUSABLE) != 0);
-            node.setFocused((flags & FLAG_FOCUSED) != 0);
-            node.setLongClickable((flags & FLAG_LONG_CLICKABLE) != 0);
-            node.setPassword((flags & FLAG_PASSWORD) != 0);
-            node.setScrollable((flags & FLAG_SCROLLABLE) != 0);
-            node.setSelected((flags & FLAG_SELECTED) != 0);
-            node.setVisibleToUser((flags & FLAG_VISIBLE_TO_USER) != 0);
-            // Other boolean properties to consider later:
-            // setHeading, setImportantForAccessibility, setScreenReaderFocusable, setShowingHintText, setDismissable
-
-            // Bounds
-            int[] b = nodeInfo.getIntArray("bounds");
-            if (b != null) {
-                final Rect screenBounds = new Rect(b[0], b[1], b[2], b[3]);
-                node.setBoundsInScreen(screenBounds);
-
-                final Matrix matrix = new Matrix();
-                mSession.getClientToScreenMatrix(matrix);
-                final float[] origin = new float[2];
-                matrix.mapPoints(origin);
-                final Rect parentBounds = new Rect(b[0] - (int)origin[0], b[1] - (int)origin[1], b[2], b[3]);
-                node.setBoundsInParent(parentBounds);
-            }
-
-            // Children
-            int[] children = nodeInfo.getIntArray("children");
-            if (children != null) {
-                for (int childId : children) {
-                    node.addChild(mView, childId);
-                }
-            }
-
-            // SDK 18 and above
-            if (Build.VERSION.SDK_INT >= 18) {
-                if ((flags & FLAG_EDITABLE) != 0) {
-                    node.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
-                    node.addAction(AccessibilityNodeInfo.ACTION_CUT);
-                    node.addAction(AccessibilityNodeInfo.ACTION_COPY);
-                    node.addAction(AccessibilityNodeInfo.ACTION_PASTE);
-                    node.setEditable(true);
-                }
-            }
-
-            // SDK 19 and above
-            if (Build.VERSION.SDK_INT >= 19) {
-                node.setMultiLine((flags & FLAG_MULTI_LINE) != 0);
-                node.setContentInvalid((flags & FLAG_CONTENT_INVALID) != 0);
-
-                // Set bundle keys like role and hint
-                Bundle bundle = node.getExtras();
-                if (nodeInfo.containsKey("hint")) {
-                    final String hint =  nodeInfo.getString("hint");
-                    bundle.putCharSequence("AccessibilityNodeInfo.hint", hint);
-                    if (Build.VERSION.SDK_INT >= 26) {
-                        node.setHintText(hint);
-                    }
-                }
-                if (nodeInfo.containsKey("geckoRole")) {
-                    bundle.putCharSequence("AccessibilityNodeInfo.geckoRole", nodeInfo.getString("geckoRole"));
-                }
-                if (nodeInfo.containsKey("roleDescription")) {
-                    bundle.putCharSequence("AccessibilityNodeInfo.roleDescription", nodeInfo.getString("roleDescription"));
-                }
-                if (isRoot) {
-                    // Argument values for ACTION_NEXT_HTML_ELEMENT/ACTION_PREVIOUS_HTML_ELEMENT.
-                    // This is mostly here to let TalkBack know we are a legit "WebView".
-                    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");
-                }
-
-
-                // Set RangeInfo
-                GeckoBundle rangeBundle = nodeInfo.getBundle("rangeInfo");
-                if (rangeBundle != null) {
-                    final RangeInfo rangeInfo = RangeInfo.obtain(
-                            rangeBundle.getInt("type"),
-                            (float)rangeBundle.getDouble("min", Float.NEGATIVE_INFINITY),
-                            (float)rangeBundle.getDouble("max", Float.POSITIVE_INFINITY),
-                            (float)rangeBundle.getDouble("current", 0));
-                    node.setRangeInfo(rangeInfo);
-                }
-
-                // Set CollectionItemInfo
-                GeckoBundle collectionItemBundle = nodeInfo.getBundle("collectionItemInfo");
-                if (collectionItemBundle != null) {
-                    final CollectionItemInfo collectionItemInfo = CollectionItemInfo.obtain(
-                            collectionItemBundle.getInt("rowIndex"),
-                            collectionItemBundle.getInt("rowSpan"),
-                            collectionItemBundle.getInt("columnIndex"),
-                            collectionItemBundle.getInt("columnSpan"), false);
-                    node.setCollectionItemInfo(collectionItemInfo);
-                }
-
-                // Set CollectionInfo
-                GeckoBundle collectionBundle = nodeInfo.getBundle("collectionInfo");
-                if (collectionBundle != null) {
-                    final CollectionInfo collectionInfo = CollectionInfo.obtain(
-                            collectionBundle.getInt("rowCount"),
-                            collectionBundle.getInt("columnCount"),
-                            collectionBundle.getBoolean("isHierarchical", false),
-                            collectionBundle.getInt("selectionMode", 0));
-                    node.setCollectionInfo(collectionInfo);
-                }
-
-                // Set inputType
-                switch (nodeInfo.getString("inputType", "").toLowerCase()) {
-                    case "email":
-                        node.setInputType(InputType.TYPE_CLASS_TEXT |
-                                InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
-                        break;
-                    case "number":
-                        node.setInputType(InputType.TYPE_CLASS_NUMBER);
-                        break;
-                    case "password":
-                        node.setInputType(InputType.TYPE_CLASS_TEXT |
-                                InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
-                        break;
-                    case "tel":
-                        node.setInputType(InputType.TYPE_CLASS_PHONE);
-                        break;
-                    case "text":
-                        node.setInputType(InputType.TYPE_CLASS_TEXT |
-                                InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
-                        break;
-                    case "url":
-                        node.setInputType(InputType.TYPE_CLASS_TEXT |
-                                InputType.TYPE_TEXT_VARIATION_URI);
-                        break;
-                    default:
-                        break;
-                }
-            }
-
-            // SDK 23 and above
-            if (Build.VERSION.SDK_INT >= 23) {
-                node.setContextClickable((flags & FLAG_CONTEXT_CLICKABLE) != 0);
-            }
-        }
-    }
+    };
 
     // 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;
     // The native portion of the node provider.
     /* package */ final NativeProvider nativeProvider = new NativeProvider();
+    // Have 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;
+    // Auto-fill nodes.
+    private SparseArray<GeckoBundle> mAutoFillNodes;
+    private SparseArray<EventCallback> mAutoFillRoots;
+    private int mAutoFillFocusedId = View.NO_ID;
+    private int mAutoFillFocusedRoot = View.NO_ID;
+
     private boolean mAttached = false;
-    // The current node with accessibility focus
-    private int mAccessibilityFocusedNode = 0;
 
     /* package */ SessionAccessibility(final GeckoSession session) {
         mSession = session;
+
         Settings.updateAccessibilitySettings();
+
+        session.getEventDispatcher().registerUiThreadListener(new BundleEventListener() {
+                @Override
+                public void handleMessage(final String event, final GeckoBundle message,
+                                          final EventCallback callback) {
+                    if ("GeckoView:AccessibilityEvent".equals(event)) {
+                        sendAccessibilityEvent(message);
+                    } else if ("GeckoView:AddAutoFill".equals(event)) {
+                        addAutoFill(message, callback);
+                    } else if ("GeckoView:ClearAutoFill".equals(event)) {
+                        clearAutoFill();
+                    } else if ("GeckoView:OnAutoFillFocus".equals(event)) {
+                        onAutoFillFocus(message);
+                    }
+                }
+            },
+            "GeckoView:AccessibilityEvent",
+            "GeckoView:AddAutoFill",
+            "GeckoView:ClearAutoFill",
+            "GeckoView:OnAutoFillFocus",
+            null);
     }
 
     /**
       * Get the View instance that delegates accessibility to this session.
       *
       * @return View instance.
       */
     public View getView() {
@@ -412,16 +290,17 @@ public class SessionAccessibility {
       * @param view View instance.
       */
     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 NodeProvider mProvider;
 
@@ -477,18 +356,16 @@ public class SessionAccessibility {
                         sForceEnabled = value < 0;
                         dispatch();
                     }
                 }
             };
             PrefsHelper.addObserver(new String[]{ FORCE_ACCESSIBILITY_PREF }, prefHandler);
         }
 
-        public static boolean isPlatformEnabled() { return sEnabled; }
-
         public static boolean isEnabled() {
             return sEnabled || sForceEnabled;
         }
 
         public static boolean isTouchExplorationEnabled() {
             return sTouchExplorationEnabled || sForceEnabled;
         }
 
@@ -496,35 +373,181 @@ public class SessionAccessibility {
             final AccessibilityManager accessibilityManager = (AccessibilityManager)
                     GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
             sEnabled = accessibilityManager.isEnabled();
             sTouchExplorationEnabled = sEnabled && accessibilityManager.isTouchExplorationEnabled();
             dispatch();
         }
 
         /* package */ static void dispatch() {
-            final GeckoBundle ret = new GeckoBundle(2);
-            ret.putBoolean("touchEnabled", isTouchExplorationEnabled());
-            ret.putBoolean("enabled", isEnabled());
+            final GeckoBundle ret = new GeckoBundle(1);
+            ret.putBoolean("enabled", isTouchExplorationEnabled());
             // "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);
+        }
+    }
 
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                toggleNativeAccessibility(isEnabled());
-            } else {
-                GeckoThread.queueNativeCallUntil(
-                        GeckoThread.State.PROFILE_READY,
-                        Settings.class, "toggleNativeAccessibility", isEnabled());
+    private AccessibilityEvent obtainEvent(final int eventType, final int sourceId) {
+        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+        event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+        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] : "");
+        }
+
+        if (message.containsKey("className"))
+            event.setClassName(message.getString("className"));
+        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.setCheckable(message.getBoolean("checkable"));
+        node.setChecked(message.getBoolean("checked"));
+        node.setPassword(message.getBoolean("password"));
+        node.setFocusable(message.getBoolean("focusable"));
+        node.setFocused(message.getBoolean("focused"));
+        node.setSelected(message.getBoolean("selected"));
+
+        node.setClassName(message.getString("className", "android.view.View"));
+
+        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", ""));
+
+        if (Build.VERSION.SDK_INT >= 18 && message.getBoolean("editable")) {
+            node.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+            node.addAction(AccessibilityNodeInfo.ACTION_CUT);
+            node.addAction(AccessibilityNodeInfo.ACTION_COPY);
+            node.addAction(AccessibilityNodeInfo.ACTION_PASTE);
+            node.setEditable(true);
+        }
+
+        if (message.getBoolean("clickable")) {
+            node.setClickable(true);
+            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+        }
+
+        if (Build.VERSION.SDK_INT >= 19 && message.containsKey("hint")) {
+            Bundle bundle = node.getExtras();
+            bundle.putCharSequence("AccessibilityNodeInfo.hint", message.getString("hint"));
+        }
+    }
+
+    private void updateBounds(final AccessibilityNodeInfo node, final GeckoBundle message) {
+        final GeckoBundle bounds = message.getBundle("bounds");
+        if (bounds == null) {
+            return;
+        }
+
+        Rect screenBounds = new Rect(bounds.getInt("left"), bounds.getInt("top"),
+                                     bounds.getInt("right"), bounds.getInt("bottom"));
+        node.setBoundsInScreen(screenBounds);
+
+        final Matrix matrix = new Matrix();
+        final float[] origin = new float[2];
+        mSession.getClientToScreenMatrix(matrix);
+        matrix.mapPoints(origin);
+
+        screenBounds.offset((int) -origin[0], (int) -origin[1]);
+        node.setBoundsInParent(screenBounds);
+    }
+
+    private void updateState(final AccessibilityNodeInfo node, final GeckoBundle message) {
+        if (message.containsKey("checked")) {
+            node.setChecked(message.getBoolean("checked"));
+        }
+        if (message.containsKey("selected")) {
+            node.setSelected(message.getBoolean("selected"));
+        }
+    }
+
+    private void sendAccessibilityEvent(final GeckoBundle message) {
+        if (mView == null || !Settings.isTouchExplorationEnabled())
+            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;
             }
         }
 
-        @WrapForJNI(dispatchTo = "gecko")
-        private static native void toggleNativeAccessibility(boolean enable);
+        if (eventSource != View.NO_ID &&
+                (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED ||
+                 eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED ||
+                 eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)) {
+            // In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
+            // it work with TalkBack.
+            if (mVirtualContentNode != null) {
+                mVirtualContentNode.recycle();
+            }
+            mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
+            populateNodeInfoFromJSON(mVirtualContentNode, message);
+        }
+
+        if (mVirtualContentNode != null) {
+            // Bounds for the virtual content can be updated from any event.
+            updateBounds(mVirtualContentNode, message);
+
+            // State for the virtual content can be updated when view is clicked/selected.
+            if (eventType == AccessibilityEvent.TYPE_VIEW_CLICKED ||
+                eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
+                updateState(mVirtualContentNode, message);
+            }
+        }
+
+        final AccessibilityEvent accessibilityEvent = obtainEvent(eventType, eventSource);
+        populateEventFromJSON(accessibilityEvent, message);
+        ((ViewParent) mView).requestSendAccessibilityEvent(mView, accessibilityEvent);
     }
 
     public boolean onMotionEvent(final MotionEvent event) {
         if (!Settings.isTouchExplorationEnabled()) {
             return false;
         }
 
         if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) {
@@ -539,86 +562,260 @@ public class SessionAccessibility {
         }
 
         final GeckoBundle data = new GeckoBundle(2);
         data.putDoubleArray("coordinates", new double[] {event.getRawX(), event.getRawY()});
         mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityExploreByTouch", data);
         return true;
     }
 
-    /* package */ void sendEvent(final int eventType, final int sourceId, final GeckoBundle eventData, final GeckoBundle sourceInfo) {
-        ThreadUtils.assertOnUiThread();
-        if (mView == null) {
+    /* package */ AccessibilityNodeInfo getAutoFillNode(final int id) {
+        if (mView == null || mAutoFillRoots == null) {
+            return null;
+        }
+
+        final GeckoBundle bundle = mAutoFillNodes.get(id);
+        if (bundle == null) {
+            return null;
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "getAutoFillNode(" + id + ')');
+        }
+
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView, id);
+        node.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
+        node.setParent(mView, bundle.getInt("parent", View.NO_ID));
+        node.setEnabled(true);
+
+        if (mAutoFillFocusedRoot != View.NO_ID &&
+                mAutoFillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
+            // Some auto-fill clients require a dummy rect for the focused View.
+            final Rect rect = new Rect();
+            mSession.getSurfaceBounds(rect);
+            node.setVisibleToUser(!rect.isEmpty());
+            node.setBoundsInParent(rect);
+
+            final int[] offset = new int[2];
+            mView.getLocationOnScreen(offset);
+            rect.offset(offset[0], offset[1]);
+            node.setBoundsInScreen(rect);
+        }
+
+        final GeckoBundle[] children = bundle.getBundleArray("children");
+        if (children != null) {
+            for (final GeckoBundle child : children) {
+                final int childId = child.getInt("id");
+                node.addChild(mView, childId);
+                mAutoFillNodes.append(childId, child);
+            }
+        }
+
+        String tag = bundle.getString("tag", "");
+        final String type = bundle.getString("type", "text");
+        final GeckoBundle attrs = bundle.getBundle("attributes");
+
+        if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
+            tag = ""; // Don't process non-editable inputs (e.g. type="button").
+        }
+        switch (tag) {
+            case "INPUT":
+            case "TEXTAREA": {
+                final boolean disabled = bundle.getBoolean("disabled");
+                node.setClassName("android.widget.EditText");
+                node.setEnabled(!disabled);
+                node.setFocusable(!disabled);
+                node.setFocused(id == mAutoFillFocusedId);
+
+                if ("password".equals(type)) {
+                    node.setPassword(true);
+                }
+                if (Build.VERSION.SDK_INT >= 18) {
+                    node.setEditable(!disabled);
+                }
+                if (Build.VERSION.SDK_INT >= 19) {
+                    node.setMultiLine("TEXTAREA".equals(tag));
+                }
+                if (Build.VERSION.SDK_INT >= 21) {
+                    try {
+                        node.setMaxTextLength(Integer.parseInt(
+                                String.valueOf(attrs.get("maxlength"))));
+                    } catch (final NumberFormatException ignore) {
+                    }
+                }
+
+                if (!disabled) {
+                    if (Build.VERSION.SDK_INT >= 21) {
+                        node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
+                    } else {
+                        node.addAction(ACTION_SET_TEXT);
+                    }
+                }
+                break;
+            }
+            default:
+                if (children != null) {
+                    node.setClassName("android.view.ViewGroup");
+                } else {
+                    node.setClassName("android.view.View");
+                }
+                break;
+        }
+
+        if (Build.VERSION.SDK_INT >= 19 && "INPUT".equals(tag)) {
+            switch (type) {
+                case "email":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
+                    break;
+                case "number":
+                    node.setInputType(InputType.TYPE_CLASS_NUMBER);
+                    break;
+                case "password":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
+                    break;
+                case "tel":
+                    node.setInputType(InputType.TYPE_CLASS_PHONE);
+                    break;
+                case "text":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+                    break;
+                case "url":
+                    node.setInputType(InputType.TYPE_CLASS_TEXT |
+                                      InputType.TYPE_TEXT_VARIATION_URI);
+                    break;
+            }
+        }
+        return node;
+    }
+
+    /* package */ boolean performAutoFill(final int id, final String value) {
+        if (mAutoFillRoots == null) {
+            return false;
+        }
+
+        int rootId = id;
+        for (int currentId = id; currentId != View.NO_ID;) {
+            final GeckoBundle bundle = mAutoFillNodes.get(currentId);
+            if (bundle == null) {
+                return false;
+            }
+            rootId = currentId;
+            currentId = bundle.getInt("parent", View.NO_ID);
+        }
+
+        if (DEBUG) {
+            Log.d(LOGTAG, "performAutoFill(" + id + ", " + value + ')');
+        }
+
+        final EventCallback callback = mAutoFillRoots.get(rootId);
+        if (callback == null) {
+            return false;
+        }
+
+        final GeckoBundle response = new GeckoBundle(1);
+        response.putString(String.valueOf(id), value);
+        callback.sendSuccess(response);
+        return true;
+    }
+
+    private void fireWindowChangedEvent(final int id) {
+        if (Settings.isEnabled() && mView instanceof ViewParent) {
+            final AccessibilityEvent event = obtainEvent(
+                    AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, id);
+            if (Build.VERSION.SDK_INT >= 19) {
+                event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+            }
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+        }
+    }
+
+    /* package */ void addAutoFill(final GeckoBundle message, final EventCallback callback) {
+        if (!Settings.isEnabled()) {
             return;
         }
 
-        if (!Settings.isPlatformEnabled() && (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null)) {
-            // Accessibility could be activated in Gecko via xpcom, for example when using a11y
-            // devtools. Here we assure that either Android a11y is *really* enabled, or no
-            // display is attached and we must be in a junit test.
+        if (mAutoFillRoots == null) {
+            mAutoFillRoots = new SparseArray<>();
+            mAutoFillNodes = new SparseArray<>();
+        }
+
+        final int id = message.getInt("id");
+        if (DEBUG) {
+            Log.d(LOGTAG, "addAutoFill(" + id + ')');
+        }
+
+        mAutoFillRoots.append(id, callback);
+        mAutoFillNodes.append(id, message);
+        fireWindowChangedEvent(id);
+    }
+
+    /* package */ void clearAutoFill() {
+        if (mAutoFillRoots != null) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "clearAutoFill()");
+            }
+            mAutoFillRoots = null;
+            mAutoFillNodes = null;
+            mAutoFillFocusedId = View.NO_ID;
+            mAutoFillFocusedRoot = View.NO_ID;
+            fireWindowChangedEvent(View.NO_ID);
+        }
+    }
+
+    /* package */ void onAutoFillFocus(final GeckoBundle message) {
+        if (!Settings.isEnabled() || !(mView instanceof ViewParent) || mAutoFillNodes == null) {
             return;
         }
-        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
-            mAccessibilityFocusedNode = sourceId;
-        }
 
-        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
-        event.setSource(mView, sourceId);
-        event.setEnabled(true);
-
-        if (sourceInfo != null) {
-            final int flags = sourceInfo.getInt("flags");
-            event.setClassName(sourceInfo.getString("className", "android.view.View"));
-            event.setChecked((flags & FLAG_CHECKED) != 0);
-            event.getText().add(sourceInfo.getString("text", ""));
+        final int id;
+        final int root;
+        if (message != null) {
+            id = message.getInt("id");
+            root = message.getInt("root");
+        } else {
+            id = root = View.NO_ID;
         }
 
-        if (eventData != null) {
-            if (eventData.containsKey("text")) {
-                event.getText().add(eventData.getString("text"));
+        if (DEBUG) {
+            Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
+        }
+        if (mAutoFillFocusedId == id) {
+            return;
+        }
+        mAutoFillFocusedId = id;
+        mAutoFillFocusedRoot = root;
+
+        // We already send "TYPE_VIEW_FOCUSED" in touch exploration mode,
+        // so in that case don't send it here.
+        if (!Settings.isTouchExplorationEnabled()) {
+            AccessibilityEvent event = obtainEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, id);
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
+        }
+    }
+
+    /* package */ void onWindowFocus() {
+        // Auto-fill clients expect a state change event on focus.
+        if (Settings.isEnabled() && mView instanceof ViewParent) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "onWindowFocus()");
             }
-            event.setContentDescription(eventData.getString("description", ""));
-            event.setAddedCount(eventData.getInt("addedCount", -1));
-            event.setRemovedCount(eventData.getInt("removedCount", -1));
-            event.setFromIndex(eventData.getInt("fromIndex", -1));
-            event.setItemCount(eventData.getInt("itemCount", -1));
-            event.setCurrentItemIndex(eventData.getInt("currentItemIndex", -1));
-            event.setBeforeText(eventData.getString("beforeText", ""));
-            event.setToIndex(eventData.getInt("toIndex", -1));
-            event.setScrollX(eventData.getInt("scrollX", -1));
-            event.setScrollY(eventData.getInt("scrollY", -1));
-            event.setMaxScrollX(eventData.getInt("maxScrollX", -1));
-            event.setMaxScrollY(eventData.getInt("maxScrollY", -1));
+            final AccessibilityEvent event = obtainEvent(
+                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, View.NO_ID);
+            ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
         }
-
-        ((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
     }
 
     /* package */ final class NativeProvider extends JNIObject {
         @WrapForJNI(calledFrom = "ui")
         private void setAttached(final boolean attached) {
             mAttached = attached;
         }
 
         @Override // JNIObject
         protected void disposeNative() {
             // Disposal happens in native code.
             throw new UnsupportedOperationException();
         }
-
-        @WrapForJNI(dispatchTo = "current")
-        public native GeckoBundle getNodeInfo(int id);
-
-        @WrapForJNI(dispatchTo = "gecko")
-        public native void setText(int id, String text);
-
-        @WrapForJNI(calledFrom = "gecko", stubName = "SendEvent")
-        private void sendEventNative(final int eventType, final int sourceId, final GeckoBundle eventData, final GeckoBundle sourceInfo) {
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    sendEvent(eventType, sourceId, eventData, sourceInfo);
-                }
-            });
-        }
     }
 }
--- a/mobile/android/modules/geckoview/GeckoViewAccessibility.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewAccessibility.jsm
@@ -12,16 +12,16 @@ ChromeUtils.import("resource://gre/modul
 XPCOMUtils.defineLazyModuleGetters(this, {
   EventDispatcher: "resource://gre/modules/Messaging.jsm",
   AccessFu: "resource://gre/modules/accessibility/AccessFu.jsm"
 });
 
 class GeckoViewAccessibility extends GeckoViewModule {
   onInit() {
     EventDispatcher.instance.registerListener((aEvent, aData, aCallback) => {
-      if (aData.touchEnabled) {
+      if (aData.enabled) {
         AccessFu.enable();
       } else {
         AccessFu.disable();
       }
     }, "GeckoView:AccessibilitySettings");
   }
 }
--- a/mobile/android/tests/browser/chrome/test_session_parentid.html
+++ b/mobile/android/tests/browser/chrome/test_session_parentid.html
@@ -51,48 +51,70 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   const url = "data:text/html;charset=utf-8,It%20was%20a%20dark%20and%20stormy%20night.";
 
   add_task(async function test_sessionStoreParentId() {
     SimpleTest.registerCleanupFunction(function() {
       cleanupTabs();
     });
 
-    // First, check that passing a parent tab ID works as expected
+    // Initialize parent tab
     tabParent = BrowserApp.addTab(url, { selected: true });
-    await promiseBrowserEvent(tabParent.browser, "DOMContentLoaded");
+    await promiseTabEvent(tabParent.browser, "DOMContentLoaded");
+    is(BrowserApp.selectedTab, tabParent, "tabParent is selected");
 
+    // test case #1
     // Open tabs without passing a parent tab ID
     tabChild1 = BrowserApp.addTab(url, { selected: false });
+    await promiseTabEvent(tabChild1.browser, "DOMContentLoaded");
+    is(BrowserApp.selectedTab, tabParent, "tabParent is selected");
+
     tabChild2 = BrowserApp.addTab(url, { selected: true });
     await promiseTabEvent(BrowserApp.deck, "TabSelect");
     is(BrowserApp.selectedTab, tabChild2, "2nd child tab is selected");
 
     // After closing that tab, its neighbour should be selected
     BrowserApp.closeTab(tabChild2);
     tabChild2 = null;
-    await promiseTabEvent(BrowserApp.deck, "TabSelect");
+    await promiseTabEvent(BrowserApp.deck, "TabClose");
     is(BrowserApp.selectedTab, tabChild1, "1st child tab is selected");
 
-    // Add a new tab and pass a parent tab ID this time
-    tabChild2 = BrowserApp.addTab(url, { selected: true, parentId: tabParent.id });
+    // test case #2
+    // Let's open a new tab, this time with a parent id and let's check if after closing it,
+    // the selected tab will be the neighbour since the parent was not selected
+    tabChild2 = BrowserApp.addTab(url, { selected: false, parentId: tabParent.id });
+    await promiseTabEvent(tabChild2.browser, "DOMContentLoaded");
+    is(BrowserApp.selectedTab, tabChild1, "1st child tab is still selected");
+
+    BrowserApp.selectTab(tabChild2);
     await promiseTabEvent(BrowserApp.deck, "TabSelect");
     is(BrowserApp.selectedTab, tabChild2, "2nd child tab is selected");
 
-    // After closing that tab, its parent should be selected
     BrowserApp.closeTab(tabChild2);
     tabChild2 = null;
-    await promiseTabEvent(BrowserApp.deck, "TabSelect");
-    is(BrowserApp.selectedTab, tabParent, "parent tab is selected");
-
-    // Reset selection and switch to the other child tab
-    BrowserApp.selectTab(tabChild1);
-    await promiseTabEvent(BrowserApp.deck, "TabSelect");
+    await promiseTabEvent(BrowserApp.deck, "TabClose");
     is(BrowserApp.selectedTab, tabChild1, "1st child tab is selected");
 
+    // test case #3
+    // This time we open a new tab with a parent id but this time the parent should be selected
+    // after closing since the parent was the previously selected tab
+    BrowserApp.selectTab(tabParent);
+    await promiseTabEvent(BrowserApp.deck, "TabSelect");
+    is(BrowserApp.selectedTab, tabParent, "tabParent is selected");
+
+    tabChild2 = BrowserApp.addTab(url, { selected: true, parentId: tabParent.id });
+    await promiseTabEvent(tabChild2.browser, "DOMContentLoaded");
+    is(BrowserApp.selectedTab, tabChild2, "2d child tab is selected");
+
+    BrowserApp.closeTab(tabChild2);
+    tabChild2 = null;
+    await promiseTabEvent(BrowserApp.deck, "TabClose");
+    is(BrowserApp.selectedTab, tabParent, "tabParent is selected");
+
+    // test case #4
     // Now check that this works even if the child tab is closed and subsequently restored
     tabChild2 = BrowserApp.addTab(url, { selected: false, parentId: tabParent.id });
     await promiseTabEvent(tabChild2.browser, "SSTabDataUpdated");
     BrowserApp.closeTab(tabChild2);
     await promiseTabEvent(tabChild2.browser, "SSTabCloseProcessed");
 
     // Restore the tab
     let closedTabData = ss.getClosedTabs(chromeWin)[0];
@@ -100,17 +122,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     await promiseTabEvent(BrowserApp.deck, "TabSelect");
     tabChild2 = BrowserApp.getTabForBrowser(browser);
     is(BrowserApp.selectedTab, tabChild2, "restored 2nd child tab is selected");
 
     // After closing that tab, its parent should be selected
     BrowserApp.closeTab(tabChild2);
     tabChild2 = null;
     await promiseTabEvent(BrowserApp.deck, "TabSelect");
-    is(BrowserApp.selectedTab, tabParent, "parent tab is selected after restoring");
+    is(BrowserApp.selectedTab, tabParent, "tabParent is selected after restoring");
 
     cleanupTabs();
   });
 
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301160">Mozilla Bug 1301160</a>
--- a/moz.build
+++ b/moz.build
@@ -32,19 +32,16 @@ with Files('mach'):
     BUG_COMPONENT = ('Core', 'mach')
 
 with Files('*moz*'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('GNUmakefile'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
-with Files('Pipfile*'):
-    BUG_COMPONENT = ('Firefox Build System', 'General')
-
 with Files('*gradle*'):
     BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
     SCHEDULES.exclusive = ['android']
 
 with Files('*.json'):
     BUG_COMPONENT = ('Firefox Build System', 'General')
 
 with Files('**/l10n.toml'):
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -2657,19 +2657,16 @@ class Vendor(MachCommandBase):
         default=False)
     def vendor_aom(self, **kwargs):
         from mozbuild.vendor_aom import VendorAOM
         vendor_command = self._spawn(VendorAOM)
         vendor_command.vendor(**kwargs)
 
     @SubCommand('vendor', 'python',
                 description='Vendor Python packages from pypi.org into third_party/python')
-    @CommandArgument('--with-windows-wheel', action='store_true',
-        help='Vendor a wheel for Windows along with the source package',
-        default=False)
     @CommandArgument('packages', default=None, nargs='*', help='Packages to vendor. If omitted, packages and their dependencies defined in Pipfile.lock will be vendored. If Pipfile has been modified, then Pipfile.lock will be regenerated. Note that transient dependencies may be updated when running this command.')
     def vendor_python(self, **kwargs):
         from mozbuild.vendor_python import VendorPython
         vendor_command = self._spawn(VendorPython)
         vendor_command.vendor(**kwargs)
 
     @SubCommand('vendor', 'manifest',
                 description='Vendor externally hosted repositories into this '
--- a/python/mozbuild/mozbuild/vendor_python.py
+++ b/python/mozbuild/mozbuild/vendor_python.py
@@ -12,96 +12,79 @@ import mozfile
 import mozpack.path as mozpath
 from mozbuild.base import MozbuildObject
 from mozfile import NamedTemporaryFile, TemporaryDirectory
 from mozpack.files import FileFinder
 
 
 class VendorPython(MozbuildObject):
 
-    def vendor(self, packages=None, with_windows_wheel=False):
+    def vendor(self, packages=None):
         self.populate_logger()
         self.log_manager.enable_unstructured()
 
         vendor_dir = mozpath.join(
             self.topsrcdir, os.path.join('third_party', 'python'))
 
         packages = packages or []
-        if with_windows_wheel and len(packages) != 1:
-            raise Exception('--with-windows-wheel is only supported for a single package!')
-        pipenv = self.ensure_pipenv()
 
-        for package in packages:
-            if not all(package.partition('==')):
-                raise Exception('Package {} must be in the format name==version'.format(package))
+        self._activate_virtualenv()
+        pip_compile = os.path.join(self.virtualenv_manager.bin_path, 'pip-compile')
+        if not os.path.exists(pip_compile):
+            path = os.path.normpath(os.path.join(self.topsrcdir, 'third_party', 'python', 'pip-tools'))
+            self.virtualenv_manager.install_pip_package(path, vendored=True)
+        spec = os.path.join(vendor_dir, 'requirements.in')
+        requirements = os.path.join(vendor_dir, 'requirements.txt')
 
-        for package in packages:
-            subprocess.check_call(
-                [pipenv, 'install', package],
-                cwd=self.topsrcdir)
+        with NamedTemporaryFile('w') as tmpspec:
+            shutil.copyfile(spec, tmpspec.name)
+            self._update_packages(tmpspec.name, packages)
 
-        with NamedTemporaryFile('w') as requirements:
-            # determine the dependency graph and generate requirements.txt
-            subprocess.check_call(
-                [pipenv, 'lock', '--requirements'],
-                cwd=self.topsrcdir,
-                stdout=requirements)
+            # resolve the dependencies and update requirements.txt
+            subprocess.check_output([
+                pip_compile,
+                tmpspec.name,
+                '--no-header',
+                '--no-index',
+                '--output-file', requirements,
+                '--generate-hashes'])
 
             with TemporaryDirectory() as tmp:
                 # use requirements.txt to download archived source distributions of all packages
                 self.virtualenv_manager._run_pip([
                     'download',
-                    '-r', requirements.name,
+                    '-r', requirements,
                     '--no-deps',
                     '--dest', tmp,
                     '--no-binary', ':all:',
                     '--disable-pip-version-check'])
-                if with_windows_wheel:
-                    # This is hardcoded to CPython 2.7 for win64, which is good
-                    # enough for what we need currently. If we need psutil for Python 3
-                    # in the future that coudl be added here as well.
-                    self.virtualenv_manager._run_pip([
-                        'download',
-                        '--dest', tmp,
-                        '--no-deps',
-                        '--only-binary', ':all:',
-                        '--platform', 'win_amd64',
-                        '--implementation', 'cp',
-                        '--python-version', '27',
-                        '--abi', 'none',
-                        '--disable-pip-version-check',
-                        packages[0]])
                 self._extract(tmp, vendor_dir)
 
-        self.repository.add_remove_files(vendor_dir)
+            shutil.copyfile(tmpspec.name, spec)
+            self.repository.add_remove_files(vendor_dir)
+
+    def _update_packages(self, spec, packages):
+        for package in packages:
+            if not all(package.partition('==')):
+                raise Exception('Package {} must be in the format name==version'.format(package))
+
+        requirements = {}
+        with open(spec, 'r') as f:
+            for line in f.readlines():
+                name, version = line.rstrip().split('==')
+                requirements[name] = version
+        for package in packages:
+            name, version = package.split('==')
+            requirements[name] = version
+
+        with open(spec, 'w') as f:
+            for name, version in sorted(requirem