Bug 1412485, disable legacy touch APIs on desktop, r=masayuki
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Mon, 04 Mar 2019 13:35:53 +0200
changeset 520646 19d1f6485a134fa0ede7677a4a7ea7e5cee40a1b
parent 520645 fe3cd9f0d12bae82190bd17ea474d1a2f4bb80a2
child 520647 41c00dfcd1b6772781d1f74da8464562024d9e1a
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1412485
milestone67.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
Bug 1412485, disable legacy touch APIs on desktop, r=masayuki Hiding document.createEvent("TouchEvent"), document.createTouch, document.createTouchList and ontouch* event handlers on desktop to follow what Chrome has done. This patch explicitly does not remove createTouch or createTouchList everywhere, although those seem to have been removing already on some other browsers. Devtools use TOUCHEVENTS_OVERRIDE_ENABLED for touch event testing, and this patch keeps the old behavior per discussion with devtools devs. Differential Revision: https://phabricator.services.mozilla.com/D22081
dom/bindings/test/TestCodeGen.webidl
dom/bindings/test/TestExampleGen.webidl
dom/bindings/test/TestJSImplGen.webidl
dom/events/EventDispatcher.cpp
dom/events/TouchEvent.cpp
dom/events/TouchEvent.h
dom/events/test/mochitest.ini
dom/events/test/test_all_synthetic_events.html
dom/events/test/test_bug1429572.html
dom/events/test/test_bug648573.html
dom/events/test/test_legacy_touch_api.html
dom/html/nsGenericHTMLElement.cpp
dom/html/nsGenericHTMLElement.h
dom/webidl/Document.webidl
dom/webidl/HTMLElement.webidl
gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
js/xpconnect/tests/chrome/test_bug760131.html
modules/libpref/init/StaticPrefList.h
testing/web-platform/meta/dom/events/__dir__.ini
testing/web-platform/meta/dom/nodes/Document-createEvent.html.ini
testing/web-platform/meta/touch-events/historical.html.ini
testing/web-platform/meta/touch-events/touch-globaleventhandler-interface.html.ini
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -874,31 +874,31 @@ interface TestInterface {
   [Pref="abc.def"]
   readonly attribute boolean prefable2;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable3;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable4;
   [Pref="abc.def"]
   readonly attribute boolean prefable5;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable6;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable7;
-  [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="ghi.jkl", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable8;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable9;
   [Pref="abc.def"]
   void prefable10();
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable11();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable12;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable13();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable14;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable15;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable16;
   [Pref="abc.def", Func="TestFuncControlledMember"]
@@ -910,25 +910,25 @@ interface TestInterface {
   [Pref="abc.def", Func="TestFuncControlledMember", ChromeOnly]
   void prefable20();
 
   // Conditionally exposed methods/attributes involving [SecureContext]
   [SecureContext]
   readonly attribute boolean conditionalOnSecureContext1;
   [SecureContext, Pref="abc.def"]
   readonly attribute boolean conditionalOnSecureContext2;
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean conditionalOnSecureContext3;
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean conditionalOnSecureContext4;
   [SecureContext]
   void conditionalOnSecureContext5();
   [SecureContext, Pref="abc.def"]
   void conditionalOnSecureContext6();
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void conditionalOnSecureContext7();
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   void conditionalOnSecureContext8();
 
   // Miscellania
   [LenientThis] attribute long attrWithLenientThis;
   [Unforgeable] readonly attribute long unforgeableAttr;
   [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
@@ -1183,17 +1183,17 @@ dictionary DictForConstructor {
   any any1 = null;
 };
 
 dictionary DictWithConditionalMembers {
   [ChromeOnly]
   long chromeOnlyMember;
   [Func="TestFuncControlledMember"]
   long funcControlledMember;
-  [ChromeOnly, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [ChromeOnly, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   long chromeOnlyFuncControlledMember;
 };
 
 interface TestIndexedGetterInterface {
   getter long item(unsigned long idx);
   readonly attribute unsigned long length;
   legacycaller void();
   [Cached, Pure] readonly attribute long cachedAttr;
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -710,31 +710,31 @@ interface TestExampleInterface {
   [Pref="abc.def"]
   readonly attribute boolean prefable2;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable3;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable4;
   [Pref="abc.def"]
   readonly attribute boolean prefable5;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable6;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable7;
-  [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="ghi.jkl", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable8;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable9;
   [Pref="abc.def"]
   void prefable10();
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable11();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable12;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable13();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable14;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable15;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable16;
   [Pref="abc.def", Func="TestFuncControlledMember"]
@@ -744,25 +744,25 @@ interface TestExampleInterface {
   [Func="TestFuncControlledMember"]
   void prefable19();
 
   // Conditionally exposed methods/attributes involving [SecureContext]
   [SecureContext]
   readonly attribute boolean conditionalOnSecureContext1;
   [SecureContext, Pref="abc.def"]
   readonly attribute boolean conditionalOnSecureContext2;
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean conditionalOnSecureContext3;
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean conditionalOnSecureContext4;
   [SecureContext]
   void conditionalOnSecureContext5();
   [SecureContext, Pref="abc.def"]
   void conditionalOnSecureContext6();
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void conditionalOnSecureContext7();
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   void conditionalOnSecureContext8();
 
   // Miscellania
   [LenientThis] attribute long attrWithLenientThis;
   [Unforgeable] readonly attribute long unforgeableAttr;
   [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -728,31 +728,31 @@ interface TestJSImplInterface {
   [Pref="abc.def"]
   readonly attribute boolean prefable2;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable3;
   [Pref="ghi.jkl"]
   readonly attribute boolean prefable4;
   [Pref="abc.def"]
   readonly attribute boolean prefable5;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable6;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable7;
-  [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="ghi.jkl", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable8;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean prefable9;
   [Pref="abc.def"]
   void prefable10();
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable11();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable12;
-  [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void prefable13();
   [Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean prefable14;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable15;
   [Func="TestFuncControlledMember"]
   readonly attribute boolean prefable16;
   [Pref="abc.def", Func="TestFuncControlledMember"]
@@ -764,25 +764,25 @@ interface TestJSImplInterface {
   [Pref="abc.def", Func="TestFuncControlledMember", ChromeOnly]
   void prefable20();
 
   // Conditionally exposed methods/attributes involving [SecureContext]
   [SecureContext]
   readonly attribute boolean conditionalOnSecureContext1;
   [SecureContext, Pref="abc.def"]
   readonly attribute boolean conditionalOnSecureContext2;
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   readonly attribute boolean conditionalOnSecureContext3;
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   readonly attribute boolean conditionalOnSecureContext4;
   [SecureContext]
   void conditionalOnSecureContext5();
   [SecureContext, Pref="abc.def"]
   void conditionalOnSecureContext6();
-  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   void conditionalOnSecureContext7();
   [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"]
   void conditionalOnSecureContext8();
 
   // Miscellania
   [LenientThis] attribute long attrWithLenientThis;
   // FIXME: Bug 863954 Unforgeable things get all confused when
   // non-JS-implemented interfaces inherit from JS-implemented ones or vice
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -1272,18 +1272,19 @@ nsresult EventDispatcher::DispatchDOMEve
   }
   if (aEventType.LowerCaseEqualsLiteral("beforeunloadevent")) {
     return NS_NewDOMBeforeUnloadEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("scrollareaevent")) {
     return NS_NewDOMScrollAreaEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("touchevent") &&
-      TouchEvent::PrefEnabled(
-          nsContentUtils::GetDocShellForEventTarget(aOwner))) {
+      TouchEvent::LegacyAPIEnabled(
+          nsContentUtils::GetDocShellForEventTarget(aOwner),
+          aCallerType == CallerType::System)) {
     return NS_NewDOMTouchEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("hashchangeevent")) {
     HashChangeEventInit init;
     RefPtr<Event> event =
         HashChangeEvent::Constructor(aOwner, EmptyString(), init);
     event->MarkUninitialized();
     return event.forget();
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -4,16 +4,17 @@
  * 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/dom/Navigator.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/dom/TouchListBinding.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
 #include "mozilla/TouchEvents.h"
 #include "nsContentUtils.h"
 #include "nsIDocShell.h"
 #include "mozilla/WidgetUtils.h"
 
 namespace mozilla {
 
 namespace dom {
@@ -270,16 +271,41 @@ bool TouchEvent::PrefEnabled(nsIDocShell
 
   if (enabled) {
     nsContentUtils::InitializeTouchEventTable();
   }
   return enabled;
 }
 
 // static
+bool TouchEvent::LegacyAPIEnabled(JSContext* aCx, JSObject* aGlobal) {
+  nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
+  bool isSystem = principal && nsContentUtils::IsSystemPrincipal(principal);
+
+  nsIDocShell* docShell = nullptr;
+  if (aGlobal) {
+    nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobal);
+    if (win) {
+      docShell = win->GetDocShell();
+    }
+  }
+  return LegacyAPIEnabled(docShell, isSystem);
+}
+
+// static
+bool TouchEvent::LegacyAPIEnabled(nsIDocShell* aDocShell,
+                                  bool aCallerIsSystem) {
+  return (aCallerIsSystem ||
+          StaticPrefs::dom_w3c_touch_events_legacy_apis_enabled() ||
+          (aDocShell && aDocShell->GetTouchEventsOverride() ==
+                            nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED)) &&
+         PrefEnabled(aDocShell);
+}
+
+// static
 already_AddRefed<TouchEvent> TouchEvent::Constructor(
     const GlobalObject& aGlobal, const nsAString& aType,
     const TouchEventInit& aParam, ErrorResult& aRv) {
   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<TouchEvent> e = new TouchEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
   RefPtr<TouchList> touches = e->CopyTouches(aParam.mTouches);
   RefPtr<TouchList> targetTouches = e->CopyTouches(aParam.mTargetTouches);
--- a/dom/events/TouchEvent.h
+++ b/dom/events/TouchEvent.h
@@ -92,16 +92,18 @@ class TouchEvent : public UIEvent {
                       nsGlobalWindowInner* aView, int32_t aDetail,
                       bool aCtrlKey, bool aAltKey, bool aShiftKey,
                       bool aMetaKey, TouchList* aTouches,
                       TouchList* aTargetTouches, TouchList* aChangedTouches);
 
   static bool PlatformSupportsTouch();
   static bool PrefEnabled(JSContext* aCx, JSObject* aGlobal);
   static bool PrefEnabled(nsIDocShell* aDocShell);
+  static bool LegacyAPIEnabled(JSContext* aCx, JSObject* aGlobal);
+  static bool LegacyAPIEnabled(nsIDocShell* aDocShell, bool aCallerIsSystem);
 
   static already_AddRefed<TouchEvent> Constructor(const GlobalObject& aGlobal,
                                                   const nsAString& aType,
                                                   const TouchEventInit& aParam,
                                                   ErrorResult& aRv);
 
  protected:
   ~TouchEvent() {}
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -174,16 +174,17 @@ skip-if = toolkit == 'android' #TIMED_OU
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_eventctors_sensors.html]
 [test_disabled_events.html]
 [test_eventhandler_scoping.html]
 [test_eventTimeStamp.html]
 [test_focus_disabled.html]
 [test_focus_abspos.html]
 [test_legacy_event.html]
+[test_legacy_touch_api.html]
 [test_messageEvent.html]
 [test_messageEvent_init.html]
 [test_moz_mouse_pixel_scroll_event.html]
 [test_offsetxy.html]
 [test_onerror_handler_args.html]
 [test_passive_listeners.html]
 [test_paste_image.html]
 skip-if = headless # Bug 1405869
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -417,52 +417,61 @@ const kEventConstructors = {
                                                        },
                                              },
   SecurityPolicyViolationEvent:              { create: function (aName, aProps) {
                                                          return new SecurityPolicyViolationEvent(aName, aProps);
                                                        },
                                              },
 };
 
-for (var name of Object.keys(kEventConstructors)) {
-  if (!kEventConstructors[name].chromeOnly) {
-    continue;
-  }
-  if (window[name]) {
-    ok(false, name + " should be chrome only.");
-  }
-  window[name] = SpecialPowers.unwrap(SpecialPowers.wrap(window)[name]);
-}
-
-var props = Object.getOwnPropertyNames(window);
-for (var i = 0; i < props.length; i++) {
-  // Assume that event object must be named as "FooBarEvent".
-  if (!props[i].match(/^([A-Z][a-zA-Z]+)?Event$/)) {
-    continue;
+function test() {
+  for (var name of Object.keys(kEventConstructors)) {
+    if (!kEventConstructors[name].chromeOnly) {
+      continue;
+    }
+    if (window[name]) {
+      ok(false, name + " should be chrome only.");
+    }
+    window[name] = SpecialPowers.unwrap(SpecialPowers.wrap(window)[name]);
   }
-  if (!kEventConstructors[props[i]]) {
-    ok(false, "Unknown event found: " + props[i]);
-    continue;
-  }
-  if (!kEventConstructors[props[i]].create) {
-    todo(false, "Cannot create untrusted event of " + props[i]);
-    continue;
-  }
-  ok(true, "Creating " + props[i] + "...");
-  var event = kEventConstructors[props[i]].create("foo", {});
-  if (!event) {
-    ok(false, "Failed to create untrusted event: " + props[i]);
-    continue;
-  }
-  if (typeof(event.getModifierState) == "function") {
-    const kModifiers = [ "Shift", "Control", "Alt", "AltGr", "Meta", "CapsLock", "ScrollLock", "NumLock", "OS", "Fn", "FnLock", "Symbol", "SymbolLock" ];
-    for (var j = 0; j < kModifiers.length; j++) {
-      ok(true, "Calling " + props[i] + ".getModifierState(" + kModifiers[j] + ")...");
-      var modifierState = event.getModifierState(kModifiers[j]);
-      ok(true, props[i] + ".getModifierState(" + kModifiers[j] + ") = " + modifierState);
+
+  var props = Object.getOwnPropertyNames(window);
+  for (var i = 0; i < props.length; i++) {
+    // Assume that event object must be named as "FooBarEvent".
+    if (!props[i].match(/^([A-Z][a-zA-Z]+)?Event$/)) {
+      continue;
+    }
+    if (!kEventConstructors[props[i]]) {
+      ok(false, "Unknown event found: " + props[i]);
+      continue;
+    }
+    if (!kEventConstructors[props[i]].create) {
+      todo(false, "Cannot create untrusted event of " + props[i]);
+      continue;
+    }
+    ok(true, "Creating " + props[i] + "...");
+    var event = kEventConstructors[props[i]].create("foo", {});
+    if (!event) {
+      ok(false, "Failed to create untrusted event: " + props[i]);
+      continue;
+    }
+    if (typeof(event.getModifierState) == "function") {
+      const kModifiers = [ "Shift", "Control", "Alt", "AltGr", "Meta", "CapsLock", "ScrollLock", "NumLock", "OS", "Fn", "FnLock", "Symbol", "SymbolLock" ];
+      for (var j = 0; j < kModifiers.length; j++) {
+        ok(true, "Calling " + props[i] + ".getModifierState(" + kModifiers[j] + ")...");
+        var modifierState = event.getModifierState(kModifiers[j]);
+        ok(true, props[i] + ".getModifierState(" + kModifiers[j] + ") = " + modifierState);
+      }
     }
   }
 }
 
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+  {"set": [["dom.w3c_touch_events.legacy_apis.enabled", true]]},
+  function() {
+    test();
+    SimpleTest.finish();
+  });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/events/test/test_bug1429572.html
+++ b/dom/events/test/test_bug1429572.html
@@ -10,17 +10,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1429572 **/
   SimpleTest.waitForExplicitFinish();
 
   var win;
   function start() {
-    SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 1]]},
+    SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 1],
+                                       ["dom.w3c_touch_events.legacy_apis.enabled", true]]},
     function() {
       ok(true, "Starting the test.");
       win = window.open("window_bug1429572.html", "testwindow",
                         "width=" + window.screen.width +
                         ",height=" + window.screen.height);
     });
   }
 
--- a/dom/events/test/test_bug648573.html
+++ b/dom/events/test/test_bug648573.html
@@ -18,92 +18,103 @@
     <div id="content" style="display: none">
 
     </div>
     <pre id="test">
       <script type="application/javascript">
 
       /** Test for Bug 648573 **/
       SimpleTest.waitForExplicitFinish();
-      var utils = SpecialPowers.getDOMWindowUtils(window);
 
-      ok("createTouch" in document, "Should have createTouch function");
-      ok("createTouchList" in document, "Should have createTouchList function");
-      ok(document.createEvent("touchevent"), "Should be able to create TouchEvent objects");
+      function runTest() {
+        var iframe = document.createElement("iframe");
+        document.body.appendChild(iframe);
+        var win = iframe.contentWindow;
+        var doc = iframe.contentDocument;
+
+        var utils = SpecialPowers.getDOMWindowUtils(win);
+
+        ok("createTouch" in doc, "Should have createTouch function");
+        ok("createTouchList" in doc, "Should have createTouchList function");
+        ok(doc.createEvent("touchevent"), "Should be able to create TouchEvent objects");
 
-      var t1 = document.createTouch(window, document, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
-      is(t1.target, document, "Wrong target");
-      is(t1.identifier, 1, "Wrong identifier");
-      is(t1.pageX, 2, "Wrong pageX");
-      is(t1.pageY, 3, "Wrong pageY");
-      is(t1.screenX, 4, "Wrong screenX");
-      is(t1.screenY, 5, "Wrong screenY");
-      is(t1.clientX, 6, "Wrong clientX");
-      is(t1.clientY, 7, "Wrong clientY");
-      is(t1.radiusX, 8, "Wrong radiusX");
-      is(t1.radiusY, 9, "Wrong radiusY");
-      is(t1.rotationAngle, 10, "Wrong rotationAngle");
-      is(t1.force, 11, "Wrong force");
-
-      var t2 = document.createTouch();
+        var t1 = doc.createTouch(win, doc, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
+        is(t1.target, doc, "Wrong target");
+        is(t1.identifier, 1, "Wrong identifier");
+        is(t1.pageX, 2, "Wrong pageX");
+        is(t1.pageY, 3, "Wrong pageY");
+        is(t1.screenX, 4, "Wrong screenX");
+        is(t1.screenY, 5, "Wrong screenY");
+        is(t1.clientX, 6, "Wrong clientX");
+        is(t1.clientY, 7, "Wrong clientY");
+        is(t1.radiusX, 8, "Wrong radiusX");
+        is(t1.radiusY, 9, "Wrong radiusY");
+        is(t1.rotationAngle, 10, "Wrong rotationAngle");
+        is(t1.force, 11, "Wrong force");
 
-      var l1 = document.createTouchList(t1);
-      is(l1.length, 1, "Wrong length");
-      is(l1.item(0), t1, "Wront item (1)");
-      is(l1[0], t1, "Wront item (2)");
+        var t2 = doc.createTouch();
 
-      var l2 = document.createTouchList([t1, t2]);
-      is(l2.length, 2, "Wrong length");
-      is(l2.item(0), t1, "Wront item (3)");
-      is(l2.item(1), t2, "Wront item (4)");
-      is(l2[0], t1, "Wront item (5)");
-      is(l2[1], t2, "Wront item (6)");
+        var l1 = doc.createTouchList(t1);
+        is(l1.length, 1, "Wrong length");
+        is(l1.item(0), t1, "Wront item (1)");
+        is(l1[0], t1, "Wront item (2)");
 
-      var l3 = document.createTouchList();
+        var l2 = doc.createTouchList([t1, t2]);
+        is(l2.length, 2, "Wrong length");
+        is(l2.item(0), t1, "Wront item (3)");
+        is(l2.item(1), t2, "Wront item (4)");
+        is(l2[0], t1, "Wront item (5)");
+        is(l2[1], t2, "Wront item (6)");
+
+        var l3 = doc.createTouchList();
 
-      var e = document.createEvent("touchevent");
-      e.initTouchEvent("touchmove", true, true, window, 0, true, true, true, true,
-                       l1, l2, l3);
-      is(e.touches, l1, "Wrong list (1)");
-      is(e.targetTouches, l2, "Wrong list (2)");
-      is(e.changedTouches, l3, "Wrong list (3)");
-      ok(e.altKey, "Alt should be true");
-      ok(e.metaKey, "Meta should be true");
-      ok(e.ctrlKey, "Ctrl should be true");
-      ok(e.shiftKey, "Shift should be true");
+        var e = doc.createEvent("touchevent");
+        e.initTouchEvent("touchmove", true, true, win, 0, true, true, true, true,
+                         l1, l2, l3);
+        is(e.touches, l1, "Wrong list (1)");
+        is(e.targetTouches, l2, "Wrong list (2)");
+        is(e.changedTouches, l3, "Wrong list (3)");
+        ok(e.altKey, "Alt should be true");
+        ok(e.metaKey, "Meta should be true");
+        ok(e.ctrlKey, "Ctrl should be true");
+        ok(e.shiftKey, "Shift should be true");
 
 
-      var events =
-      ["touchstart",
-       "touchend",
-       "touchmove",
-       "touchcancel"];
+        var events =
+        ["touchstart",
+         "touchend",
+         "touchmove",
+         "touchcancel"];
 
-      function runEventTest(type) {
-        var e = document.createEvent("touchevent");
-        e.initTouchEvent(type, true, true, window, 0, true, true, true, true,
-                         l1, l2, l3);
-        var t = document.createElement("div");
-        // Testing target.onFoo;
-        var didCall = false;
-        t["on" + type] = function (evt) {
-          is(evt, e, "Wrong event");
-          evt.target.didCall = true;
+        function runEventTest(type) {
+          var e = doc.createEvent("touchevent");
+          e.initTouchEvent(type, true, true, win, 0, true, true, true, true,
+                           l1, l2, l3);
+          var t = doc.createElement("div");
+          // Testing target.onFoo;
+          var didCall = false;
+          t["on" + type] = function (evt) {
+            is(evt, e, "Wrong event");
+            evt.target.didCall = true;
+          }
+          t.dispatchEvent(e);
+          ok(t.didCall, "Should have called the listener(1)");
+
+          // Testing <element onFoo="">
+          t = doc.createElement("div");
+          t.setAttribute("on" + type, "this.didCall = true;");
+          t.dispatchEvent(e);
+          ok(t.didCall, "Should have called the listener(2)");
         }
-        t.dispatchEvent(e);
-        ok(t.didCall, "Should have called the listener(1)");
 
-        // Testing <element onFoo="">
-        t = document.createElement("div");
-        t.setAttribute("on" + type, "this.didCall = true;");
-        t.dispatchEvent(e);
-        ok(t.didCall, "Should have called the listener(2)");
+        for (var i = 0; i < events.length; ++i) {
+          runEventTest(events[i]);
+        }
+
+        SimpleTest.finish();
       }
 
-      for (var i = 0; i < events.length; ++i) {
-        runEventTest(events[i]);
-      }
-
-      SimpleTest.finish();
+      SpecialPowers.pushPrefEnv(
+        {"set": [["dom.w3c_touch_events.legacy_apis.enabled", true]]}, runTest);
       </script>
     </pre>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_legacy_touch_api.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1412485
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1412485</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1412485 **/
+  SimpleTest.waitForExplicitFinish();
+
+  function testExistenceOfLegacyTouchAPIs(win, enabled) {
+    try {
+      var event = document.createEvent("TouchEvent");
+      ok(event instanceof TouchEvent, "Should be able to create TouchEvent using createEvent.");
+    } catch(ex) {
+      ok(true, "Shouldn't be able create TouchEvent using createEvent.");
+    }
+
+    var targets = [win, win.document, win.document.body];
+    for (target of targets) {
+      is("ontouchstart" in target, enabled, `ontouchstart on target [${enabled}].`);
+      is("ontouchend" in target, enabled, `ontouchend on target [${enabled}].`);
+      is("ontouchmove" in target, enabled, `ontouchmove on target [${enabled}].`);
+      is("ontouchcancel" in target, enabled, `ontouchcancel on target [${enabled}].`);
+    }
+
+    is("createTouch" in win.document, enabled, `createTouch on Document [${enabled}].`);
+    is("createTouchList" in win.document, enabled, `createTouchList on Document [${enabled}].`);
+  }
+
+  function test() {
+    // Test the defaults.
+    testExistenceOfLegacyTouchAPIs(window,
+                                   navigator.userAgent.includes("Android"));
+
+    // Test explicitly enabling touch APIs.
+    SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 1],
+                                       ["dom.w3c_touch_events.legacy_apis.enabled", true]]},
+      function() {
+        var iframe = document.createElement("iframe");
+        document.body.appendChild(iframe);
+        iframe.onload = function() {
+          testExistenceOfLegacyTouchAPIs(iframe.contentWindow, true);
+          SimpleTest.finish();
+        }
+      });
+  }
+
+  </script>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1412485">Mozilla Bug 1412485</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1477,19 +1477,19 @@ already_AddRefed<nsINodeList> nsGenericH
 
 bool nsGenericHTMLElement::IsInteractiveHTMLContent(
     bool aIgnoreTabindex) const {
   return IsAnyOfHTMLElements(nsGkAtoms::embed, nsGkAtoms::keygen) ||
          (!aIgnoreTabindex && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
 }
 
 // static
-bool nsGenericHTMLElement::TouchEventsEnabled(JSContext* aCx,
-                                              JSObject* aGlobal) {
-  return TouchEvent::PrefEnabled(aCx, aGlobal);
+bool nsGenericHTMLElement::LegacyTouchAPIEnabled(JSContext* aCx,
+                                                 JSObject* aGlobal) {
+  return TouchEvent::LegacyAPIEnabled(aCx, aGlobal);
 }
 
 //----------------------------------------------------------------------
 
 nsGenericHTMLFormElement::nsGenericHTMLFormElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, uint8_t aType)
     : nsGenericHTMLElement(std::move(aNodeInfo)),
       nsIFormControl(aType),
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -614,18 +614,18 @@ class nsGenericHTMLElement : public nsGe
 
   static bool MatchLabelsElement(Element* aElement, int32_t aNamespaceID,
                                  nsAtom* aAtom, void* aData);
 
   already_AddRefed<nsINodeList> Labels();
 
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
 
-  static bool TouchEventsEnabled(JSContext* /* unused */,
-                                 JSObject* /* unused */);
+  static bool LegacyTouchAPIEnabled(JSContext* aCx,
+                                    JSObject* aObj);
 
   static inline bool CanHaveName(nsAtom* aTag) {
     return aTag == nsGkAtoms::img || aTag == nsGkAtoms::form ||
            aTag == nsGkAtoms::embed || aTag == nsGkAtoms::object;
   }
   static inline bool ShouldExposeNameAsHTMLDocumentProperty(Element* aElement) {
     return aElement->IsHTMLElement() &&
            CanHaveName(aElement->NodeInfo()->NameAtom());
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -356,17 +356,17 @@ partial interface Document {
   void loadBindingDocument(DOMString documentURL);
   // Creates a new XUL element regardless of the document's default type.
   [CEReactions, NewObject, Throws, Func="IsChromeOrXBL"]
   Element createXULElement(DOMString localName, optional (ElementCreationOptions or DOMString) options);
 
   // Touch bits
   // XXXbz I can't find the sane spec for this stuff, so just cribbing
   // from our xpidl for now.
-  [NewObject, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [NewObject, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   Touch createTouch(optional Window? view = null,
                     optional EventTarget? target = null,
                     optional long identifier = 0,
                     optional long pageX = 0,
                     optional long pageY = 0,
                     optional long screenX = 0,
                     optional long screenY = 0,
                     optional long clientX = 0,
@@ -374,24 +374,24 @@ partial interface Document {
                     optional long radiusX = 0,
                     optional long radiusY = 0,
                     optional float rotationAngle = 0,
                     optional float force = 0);
   // XXXbz a hack to get around the fact that we don't support variadics as
   // distinguishing arguments yet.  Once this hack is removed. we can also
   // remove the corresponding overload on Document, since Touch... and
   // sequence<Touch> look the same in the C++.
-  [NewObject, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [NewObject, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   TouchList createTouchList(Touch touch, Touch... touches);
   // XXXbz and another hack for the fact that we can't usefully have optional
   // distinguishing arguments but need a working zero-arg form of
   // createTouchList().
-  [NewObject, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [NewObject, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   TouchList createTouchList();
-  [NewObject, Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [NewObject, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
   TouchList createTouchList(sequence<Touch> touches);
 
   [ChromeOnly]
   attribute boolean styleSheetChangeEventsEnabled;
 
   [ChromeOnly] readonly attribute DOMString contentLanguage;
 
   [ChromeOnly] readonly attribute nsILoadGroup? documentLoadGroup;
--- a/dom/webidl/HTMLElement.webidl
+++ b/dom/webidl/HTMLElement.webidl
@@ -77,23 +77,23 @@ partial interface HTMLElement {
   readonly attribute long offsetTop;
   readonly attribute long offsetLeft;
   readonly attribute long offsetWidth;
   readonly attribute long offsetHeight;
 };
 
 [NoInterfaceObject]
 interface TouchEventHandlers {
-  [Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchstart;
-  [Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchend;
-  [Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchmove;
-  [Func="nsGenericHTMLElement::TouchEventsEnabled"]
+  [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchcancel;
 };
 
 HTMLElement implements GlobalEventHandlers;
 HTMLElement implements DocumentAndElementEventHandlers;
 HTMLElement implements TouchEventHandlers;
 HTMLElement implements OnErrorEventHandlerForNodes;
 
--- a/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
@@ -9,16 +9,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
 var isWindows = getPlatform() == "windows";
 
 const shared_prefs = [
   ["apz.test.fails_with_native_injection", isWindows],
   ["dom.meta-viewport.enabled", true],
+  ["dom.w3c_touch_events.legacy_apis.enabled", true],
 ];
 
 var subtests = [
   // Taps on media elements to make sure the touchend event is delivered
   // properly. We increase the long-tap timeout to ensure it doesn't get trip
   // during the tap.
   // Also this test (on Windows) cannot satisfy the OS requirement of providing
   // an injected touch event every 100ms, because it waits for a paint between
--- a/js/xpconnect/tests/chrome/test_bug760131.html
+++ b/js/xpconnect/tests/chrome/test_bug760131.html
@@ -30,15 +30,19 @@ function runTest()
   var event = doc.createEvent("touchevent");
   event.initTouchEvent("touchstart", true, true, win, 0, false, false, false, false, empty, empty, empty);
   doc.getElementById("target").dispatchEvent(event);
 
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-frame.onload = runTest;
-frame.src = "http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug760131.html";
-
+SpecialPowers.pushPrefEnv(
+  {"set": [["dom.w3c_touch_events.legacy_apis.enabled", true]]},
+  function() {
+    frame.onload = runTest;
+    frame.src = "http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_bug760131.html";
+  }
+);
 </script>
 </pre>
 </body>
 </html>
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2212,16 +2212,31 @@ VARCACHE_PREF(
 
 // How many reports should be stored in the report queue before being delivered.
 VARCACHE_PREF(
   "dom.reporting.delivering.maxReports",
    dom_reporting_delivering_maxReports,
   uint32_t, 100
 )
 
+// In case Touch API is enabled, this pref controls whether to support
+// ontouch* event handlers, document.createTouch, document.createTouchList and
+// document.createEvent("TouchEvent").
+#ifdef ANDROID
+# define PREF_VALUE true
+#else
+# define PREF_VALUE false
+#endif
+VARCACHE_PREF(
+  "dom.w3c_touch_events.legacy_apis.enabled",
+   dom_w3c_touch_events_legacy_apis_enabled,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
+
 VARCACHE_PREF(
   "medium_high_event_queue.enabled",
    medium_high_event_queue_enabled,
   RelaxedAtomicBool, true
 )
 
 //---------------------------------------------------------------------------
 // End of prefs
--- a/testing/web-platform/meta/dom/events/__dir__.ini
+++ b/testing/web-platform/meta/dom/events/__dir__.ini
@@ -1,1 +1,1 @@
-prefs: [privacy.reduceTimerPrecision:false]
+prefs: [privacy.reduceTimerPrecision:false, dom.w3c_touch_events.legacy_apis.enabled:true]
--- a/testing/web-platform/meta/dom/nodes/Document-createEvent.html.ini
+++ b/testing/web-platform/meta/dom/nodes/Document-createEvent.html.ini
@@ -1,9 +1,10 @@
 [Document-createEvent.html]
+  prefs: [dom.w3c_touch_events.legacy_apis.enabled:true]
   [Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "KeyEvents"]
     expected: FAIL
     bug: 1251198
 
   [Should throw NOT_SUPPORTED_ERR for pluralized non-legacy event interface "MouseScrollEvents"]
     expected: FAIL
     bug: 1251198
 
--- a/testing/web-platform/meta/touch-events/historical.html.ini
+++ b/testing/web-platform/meta/touch-events/historical.html.ini
@@ -1,10 +1,11 @@
 [historical.html]
   [TouchEvent::initTouchEvent]
     expected: FAIL
 
   [Document::createTouch]
-    expected: FAIL
+    expected:
+      if os == "android": FAIL
 
   [Document::createTouchList]
-    expected: FAIL
-
+    expected:
+      if os == "android": FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/touch-events/touch-globaleventhandler-interface.html.ini
@@ -0,0 +1,2 @@
+[touch-globaleventhandler-interface.html]
+  prefs: [dom.w3c_touch_events.legacy_apis.enabled:true]