TouchEvent.cpp (12454B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/TouchEvent.h" 8 9 #include "gfxPlatform.h" 10 #include "mozilla/BasePrincipal.h" 11 #include "mozilla/LookAndFeel.h" 12 #include "mozilla/Preferences.h" 13 #include "mozilla/StaticPrefs_dom.h" 14 #include "mozilla/TouchEvents.h" 15 #include "mozilla/dom/Navigator.h" 16 #include "mozilla/dom/Touch.h" 17 #include "mozilla/dom/TouchListBinding.h" 18 #include "nsContentUtils.h" 19 #include "nsExceptionHandler.h" 20 #include "nsIDocShell.h" 21 22 namespace mozilla::dom { 23 24 /****************************************************************************** 25 * TouchList 26 *****************************************************************************/ 27 28 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TouchList) 29 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 30 NS_INTERFACE_MAP_ENTRY(nsISupports) 31 NS_INTERFACE_MAP_END 32 33 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TouchList, mParent, mPoints) 34 35 NS_IMPL_CYCLE_COLLECTING_ADDREF(TouchList) 36 NS_IMPL_CYCLE_COLLECTING_RELEASE(TouchList) 37 38 JSObject* TouchList::WrapObject(JSContext* aCx, 39 JS::Handle<JSObject*> aGivenProto) { 40 return TouchList_Binding::Wrap(aCx, this, aGivenProto); 41 } 42 43 // static 44 bool TouchList::PrefEnabled(JSContext* aCx, JSObject* aGlobal) { 45 return TouchEvent::PrefEnabled(aCx, aGlobal); 46 } 47 48 /****************************************************************************** 49 * TouchEvent 50 *****************************************************************************/ 51 52 TouchEvent::TouchEvent(EventTarget* aOwner, nsPresContext* aPresContext, 53 WidgetTouchEvent* aEvent) 54 : UIEvent( 55 aOwner, aPresContext, 56 aEvent ? aEvent : new WidgetTouchEvent(false, eVoidEvent, nullptr)) { 57 if (aEvent) { 58 mEventIsInternal = false; 59 60 for (uint32_t i = 0; i < aEvent->mTouches.Length(); ++i) { 61 Touch* touch = aEvent->mTouches[i]; 62 touch->InitializePoints(mPresContext, aEvent); 63 } 64 } else { 65 mEventIsInternal = true; 66 } 67 } 68 69 NS_IMPL_CYCLE_COLLECTION_INHERITED(TouchEvent, UIEvent, 70 mEvent->AsTouchEvent()->mTouches, mTouches, 71 mTargetTouches, mChangedTouches) 72 73 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TouchEvent) 74 NS_INTERFACE_MAP_END_INHERITING(UIEvent) 75 76 NS_IMPL_ADDREF_INHERITED(TouchEvent, UIEvent) 77 NS_IMPL_RELEASE_INHERITED(TouchEvent, UIEvent) 78 79 void TouchEvent::InitTouchEvent(const nsAString& aType, bool aCanBubble, 80 bool aCancelable, nsGlobalWindowInner* aView, 81 int32_t aDetail, bool aCtrlKey, bool aAltKey, 82 bool aShiftKey, bool aMetaKey, 83 TouchList* aTouches, TouchList* aTargetTouches, 84 TouchList* aChangedTouches) { 85 NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched); 86 87 UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail); 88 mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, 89 aMetaKey); 90 91 mEvent->AsTouchEvent()->mTouches.Clear(); 92 93 // To support touch.target retargeting also when the event is 94 // created by JS, we need to copy Touch objects to the widget event. 95 // In order to not affect targetTouches, we don't check duplicates in that 96 // list. 97 mTargetTouches = aTargetTouches; 98 AssignTouchesToWidgetEvent(mTargetTouches, false); 99 mTouches = aTouches; 100 AssignTouchesToWidgetEvent(mTouches, true); 101 mChangedTouches = aChangedTouches; 102 AssignTouchesToWidgetEvent(mChangedTouches, true); 103 } 104 105 void TouchEvent::AssignTouchesToWidgetEvent(TouchList* aList, 106 bool aCheckDuplicates) { 107 if (!aList) { 108 return; 109 } 110 WidgetTouchEvent* widgetTouchEvent = mEvent->AsTouchEvent(); 111 for (uint32_t i = 0; i < aList->Length(); ++i) { 112 Touch* touch = aList->Item(i); 113 if (touch && 114 (!aCheckDuplicates || !widgetTouchEvent->mTouches.Contains(touch))) { 115 widgetTouchEvent->mTouches.AppendElement(touch); 116 } 117 } 118 } 119 120 TouchList* TouchEvent::Touches() { 121 if (!mTouches) { 122 WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); 123 if (mEvent->mMessage == eTouchEnd || mEvent->mMessage == eTouchCancel) { 124 // for touchend events, remove any changed touches from mTouches 125 WidgetTouchEvent::AutoTouchArray unchangedTouches; 126 const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; 127 for (uint32_t i = 0; i < touches.Length(); ++i) { 128 if (!touches[i]->mChanged) { 129 unchangedTouches.AppendElement(touches[i]); 130 } 131 } 132 mTouches = new TouchList(ToSupports(this), unchangedTouches); 133 } else { 134 mTouches = new TouchList(ToSupports(this), touchEvent->mTouches); 135 } 136 } 137 return mTouches; 138 } 139 140 TouchList* TouchEvent::TargetTouches() { 141 if (!mTargetTouches || !mTargetTouches->Length()) { 142 WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); 143 if (!mTargetTouches) { 144 mTargetTouches = new TouchList(ToSupports(this)); 145 } 146 const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; 147 for (uint32_t i = 0; i < touches.Length(); ++i) { 148 // for touchend/cancel events, don't append to the target list if this is 149 // a touch that is ending 150 if ((mEvent->mMessage != eTouchEnd && mEvent->mMessage != eTouchCancel) || 151 !touches[i]->mChanged) { 152 bool equalTarget = touches[i]->mTarget == mEvent->mTarget; 153 if (!equalTarget) { 154 // Need to still check if we're inside native anonymous content 155 // and the non-NAC target would be the same. 156 nsIContent* touchTarget = 157 nsIContent::FromEventTargetOrNull(touches[i]->mTarget); 158 nsIContent* eventTarget = 159 nsIContent::FromEventTargetOrNull(mEvent->mTarget); 160 equalTarget = touchTarget && eventTarget && 161 touchTarget->FindFirstNonChromeOnlyAccessContent() == 162 eventTarget->FindFirstNonChromeOnlyAccessContent(); 163 } 164 if (equalTarget) { 165 mTargetTouches->Append(touches[i]); 166 } 167 } 168 } 169 } 170 return mTargetTouches; 171 } 172 173 TouchList* TouchEvent::ChangedTouches() { 174 if (!mChangedTouches) { 175 WidgetTouchEvent::AutoTouchArray changedTouches; 176 WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent(); 177 const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; 178 for (uint32_t i = 0; i < touches.Length(); ++i) { 179 if (touches[i]->mChanged) { 180 changedTouches.AppendElement(touches[i]); 181 } 182 } 183 mChangedTouches = new TouchList(ToSupports(this), changedTouches); 184 } 185 return mChangedTouches; 186 } 187 188 // static 189 bool TouchEvent::PrefEnabled(JSContext* aCx, JSObject* aGlobal) { 190 nsIDocShell* docShell = nullptr; 191 if (aGlobal) { 192 nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobal); 193 if (win) { 194 docShell = win->GetDocShell(); 195 } 196 } 197 return PrefEnabled(docShell); 198 } 199 200 static bool PlatformSupportsTouch() { 201 // Touch events are only actually supported if APZ is enabled. If APZ is 202 // disabled globally, we can check that once and incorporate that into the 203 // cached state. If APZ is enabled, we need to further check based on the 204 // widget, which we do in PrefEnabled (and don't cache that result). 205 static bool sIsTouchDeviceSupportPresent = 206 !!LookAndFeel::GetInt(LookAndFeel::IntID::TouchDeviceSupportPresent) && 207 gfxPlatform::AsyncPanZoomEnabled(); 208 209 return sIsTouchDeviceSupportPresent; 210 } 211 212 // static 213 bool TouchEvent::PrefEnabled(nsIDocShell* aDocShell) { 214 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); 215 216 auto touchEventsOverride = mozilla::dom::TouchEventsOverride::None; 217 if (aDocShell) { 218 if (BrowsingContext* bc = aDocShell->GetBrowsingContext()) { 219 touchEventsOverride = bc->TouchEventsOverride(); 220 } 221 } 222 223 bool enabled = false; 224 if (touchEventsOverride == mozilla::dom::TouchEventsOverride::Enabled) { 225 enabled = true; 226 } else if (touchEventsOverride == 227 mozilla::dom::TouchEventsOverride::Disabled) { 228 enabled = false; 229 } else if (nsContentUtils::ShouldResistFingerprinting( 230 aDocShell, RFPTarget::PointerEvents)) { 231 #ifdef MOZ_WIDGET_COCOA 232 enabled = false; 233 #else 234 enabled = true; 235 #endif 236 } else { 237 const int32_t prefValue = StaticPrefs::dom_w3c_touch_events_enabled(); 238 if (prefValue == 2) { 239 enabled = PlatformSupportsTouch(); 240 241 static bool firstTime = true; 242 // The touch screen data seems to be inaccurate in the parent process, 243 // and we really need the crash annotation in child processes. 244 if (firstTime && !XRE_IsParentProcess()) { 245 CrashReporter::RecordAnnotationBool( 246 CrashReporter::Annotation::HasDeviceTouchScreen, enabled); 247 firstTime = false; 248 } 249 250 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) 251 if (enabled && aDocShell) { 252 // APZ might be disabled on this particular widget, in which case 253 // TouchEvent support will also be disabled. Try to detect that. 254 if (RefPtr<nsPresContext> pc = aDocShell->GetPresContext()) { 255 if (nsCOMPtr<nsIWidget> widget = pc->GetRootWidget()) { 256 enabled &= widget->AsyncPanZoomEnabled(); 257 } 258 } 259 } 260 #endif 261 } else { 262 enabled = !!prefValue; 263 } 264 } 265 266 if (enabled) { 267 nsContentUtils::InitializeTouchEventTable(); 268 } 269 return enabled; 270 } 271 272 // static 273 bool TouchEvent::LegacyAPIEnabled(JSContext* aCx, JSObject* aGlobal) { 274 nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx); 275 bool isSystem = principal && principal->IsSystemPrincipal(); 276 277 nsIDocShell* docShell = nullptr; 278 if (aGlobal) { 279 nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobal); 280 if (win) { 281 docShell = win->GetDocShell(); 282 } 283 } 284 return LegacyAPIEnabled(docShell, isSystem); 285 } 286 287 // static 288 bool TouchEvent::LegacyAPIEnabled(nsIDocShell* aDocShell, 289 bool aCallerIsSystem) { 290 return (aCallerIsSystem || 291 StaticPrefs::dom_w3c_touch_events_legacy_apis_enabled() || 292 (aDocShell && aDocShell->GetBrowsingContext() && 293 aDocShell->GetBrowsingContext()->TouchEventsOverride() == 294 mozilla::dom::TouchEventsOverride::Enabled)) && 295 PrefEnabled(aDocShell); 296 } 297 298 // static 299 already_AddRefed<TouchEvent> TouchEvent::Constructor( 300 const GlobalObject& aGlobal, const nsAString& aType, 301 const TouchEventInit& aParam) { 302 nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports()); 303 RefPtr<TouchEvent> e = new TouchEvent(t, nullptr, nullptr); 304 bool trusted = e->Init(t); 305 RefPtr<TouchList> touches = e->CopyTouches(aParam.mTouches); 306 RefPtr<TouchList> targetTouches = e->CopyTouches(aParam.mTargetTouches); 307 RefPtr<TouchList> changedTouches = e->CopyTouches(aParam.mChangedTouches); 308 e->InitTouchEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView, 309 aParam.mDetail, aParam.mCtrlKey, aParam.mAltKey, 310 aParam.mShiftKey, aParam.mMetaKey, touches, targetTouches, 311 changedTouches); 312 e->SetTrusted(trusted); 313 e->SetComposed(aParam.mComposed); 314 return e.forget(); 315 } 316 317 already_AddRefed<TouchList> TouchEvent::CopyTouches( 318 const Sequence<OwningNonNull<Touch>>& aTouches) { 319 RefPtr<TouchList> list = new TouchList(GetParentObject()); 320 size_t len = aTouches.Length(); 321 for (size_t i = 0; i < len; ++i) { 322 list->Append(aTouches[i]); 323 } 324 return list.forget(); 325 } 326 327 bool TouchEvent::AltKey() { return mEvent->AsTouchEvent()->IsAlt(); } 328 329 bool TouchEvent::MetaKey() { return mEvent->AsTouchEvent()->IsMeta(); } 330 331 bool TouchEvent::CtrlKey() { return mEvent->AsTouchEvent()->IsControl(); } 332 333 bool TouchEvent::ShiftKey() { return mEvent->AsTouchEvent()->IsShift(); } 334 335 } // namespace mozilla::dom 336 337 using namespace mozilla; 338 using namespace mozilla::dom; 339 340 already_AddRefed<TouchEvent> NS_NewDOMTouchEvent(EventTarget* aOwner, 341 nsPresContext* aPresContext, 342 WidgetTouchEvent* aEvent) { 343 RefPtr<TouchEvent> it = new TouchEvent(aOwner, aPresContext, aEvent); 344 return it.forget(); 345 }