APZEventState.cpp (22167B)
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 "APZEventState.h" 8 9 #include <utility> 10 11 #include "APZCCallbackHelper.h" 12 #include "ElementStateManager.h" 13 #include "TouchManager.h" 14 #include "mozilla/Assertions.h" 15 #include "mozilla/BasicEvents.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/EventForwards.h" 18 #include "mozilla/IntegerPrintfMacros.h" 19 #include "mozilla/PositionedEventTargeting.h" 20 #include "mozilla/Preferences.h" 21 #include "mozilla/PresShell.h" 22 #include "mozilla/ScrollContainerFrame.h" 23 #include "mozilla/StaticPrefs_dom.h" 24 #include "mozilla/StaticPrefs_ui.h" 25 #include "mozilla/ToString.h" 26 #include "mozilla/TouchEvents.h" 27 #include "mozilla/ViewportUtils.h" 28 #include "mozilla/dom/BrowserChild.h" 29 #include "mozilla/dom/MouseEventBinding.h" 30 #include "mozilla/dom/PointerEventHandler.h" 31 #include "mozilla/layers/APZCCallbackHelper.h" 32 #include "mozilla/layers/APZUtils.h" 33 #include "mozilla/layers/IAPZCTreeManager.h" 34 #include "mozilla/widget/nsAutoRollup.h" 35 #include "nsCOMPtr.h" 36 #include "nsContentUtils.h" 37 #include "nsDocShell.h" 38 #include "nsIDOMWindowUtils.h" 39 #include "nsINamed.h" 40 #include "nsIScrollbarMediator.h" 41 #include "nsIWeakReferenceUtils.h" 42 #include "nsIWidget.h" 43 #include "nsLayoutUtils.h" 44 #include "nsQueryFrame.h" 45 46 static mozilla::LazyLogModule sApzEvtLog("apz.eventstate"); 47 #define APZES_LOG(...) MOZ_LOG(sApzEvtLog, LogLevel::Debug, (__VA_ARGS__)) 48 49 namespace mozilla { 50 namespace layers { 51 52 APZEventState::APZEventState(nsIWidget* aWidget, 53 ContentReceivedInputBlockCallback&& aCallback) 54 : mWidget(nullptr) // initialized in constructor body 55 , 56 mElementStateManager(new ElementStateManager()), 57 mContentReceivedInputBlockCallback(std::move(aCallback)), 58 mPendingTouchPreventedBlockId(0), 59 mEndTouchState(apz::SingleTapState::NotClick) { 60 nsresult rv; 61 mWidget = do_GetWeakReference(aWidget, &rv); 62 MOZ_ASSERT(NS_SUCCEEDED(rv), 63 "APZEventState constructed with a widget that" 64 " does not support weak references. APZ will NOT work!"); 65 } 66 67 APZEventState::~APZEventState() = default; 68 69 void APZEventState::ProcessSingleTap(const CSSPoint& aPoint, 70 const CSSToLayoutDeviceScale& aScale, 71 Modifiers aModifiers, int32_t aClickCount, 72 uint64_t aInputBlockId) { 73 APZES_LOG("Handling single tap at %s with %d\n", ToString(aPoint).c_str(), 74 mTouchEndCancelled); 75 76 RefPtr<nsIContent> touchRollup = GetTouchRollup(); 77 mTouchRollup = nullptr; 78 79 nsCOMPtr<nsIWidget> widget = GetWidget(); 80 if (!widget) { 81 return; 82 } 83 84 if (mTouchEndCancelled) { 85 return; 86 } 87 88 nsCOMPtr<nsIWidget> localWidget = do_QueryReferent(mWidget); 89 if (localWidget) { 90 widget::nsAutoRollup rollup(touchRollup); 91 APZCCallbackHelper::FireSingleTapEvent( 92 aPoint * aScale, mLastTouchIdentifier, aModifiers, aClickCount, 93 mPrecedingPointerDownState, localWidget, mLastTouchSynthesizedForTests); 94 } 95 96 mElementStateManager->ProcessSingleTap(); 97 } 98 99 PreventDefaultResult APZEventState::FireContextmenuEvents( 100 PresShell* aPresShell, const CSSPoint& aPoint, 101 const CSSToLayoutDeviceScale& aScale, uint32_t aPointerId, 102 Modifiers aModifiers, const nsCOMPtr<nsIWidget>& aWidget, 103 SynthesizeForTests aSynthesizeForTests) { 104 // Suppress retargeting for mouse events generated by a long-press 105 EventRetargetSuppression suppression; 106 107 // Synthesize mousemove event for allowing users to emulate to move mouse 108 // cursor over the element. As a result, users can open submenu UI which 109 // is opened when mouse cursor is moved over a link (i.e., it's a case that 110 // users cannot stay in the page after tapping it). So, this improves 111 // accessibility in websites which are designed for desktop. 112 // Note that we don't need to check whether mousemove event is consumed or 113 // not because Chrome also ignores the result. 114 APZCCallbackHelper::DispatchSynthesizedMouseEvent( 115 eMouseMove, aPoint * aScale, aPointerId, aModifiers, 0 /* clickCount */, 116 mPrecedingPointerDownState, aWidget, aSynthesizeForTests); 117 118 PreventDefaultResult preventDefaultResult = 119 APZCCallbackHelper::DispatchSynthesizedContextmenuEvent( 120 aPoint * aScale, aPointerId, aModifiers, aWidget, 121 aSynthesizeForTests); 122 123 APZES_LOG("Contextmenu event %s\n", ToString(preventDefaultResult).c_str()); 124 if (preventDefaultResult != PreventDefaultResult::No) { 125 // If the contextmenu event was handled then we're showing a contextmenu, 126 // and so we should remove any activation 127 mElementStateManager->ClearActivation(); 128 #ifndef XP_WIN 129 } else { 130 // If the contextmenu wasn't consumed, fire the eMouseLongTap event. 131 nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent( 132 eMouseLongTap, aPoint * aScale, aPointerId, aModifiers, 133 /*clickCount*/ 1, mPrecedingPointerDownState, aWidget, 134 aSynthesizeForTests); 135 APZES_LOG("eMouseLongTap event %s\n", ToString(status).c_str()); 136 #endif 137 } 138 139 return preventDefaultResult; 140 } 141 142 void APZEventState::ProcessLongTap(PresShell* aPresShell, 143 const CSSPoint& aPoint, 144 const CSSToLayoutDeviceScale& aScale, 145 Modifiers aModifiers, 146 uint64_t aInputBlockId) { 147 APZES_LOG("Handling long tap at %s block id %" PRIu64 "\n", 148 ToString(aPoint).c_str(), aInputBlockId); 149 150 nsCOMPtr<nsIWidget> widget = GetWidget(); 151 if (!widget) { 152 return; 153 } 154 155 // If the touch block is waiting for a content response, send one now. 156 // Bug 1848736: Why is a content response needed here? Can it be removed? 157 // However, do not clear |mPendingTouchPreventedResponse|, because APZ will 158 // wait for an additional content response before processing touch-move 159 // events (since the first touch-move could still be prevented, and that 160 // should prevent the touch block from being processed). 161 if (mPendingTouchPreventedResponse) { 162 APZES_LOG("Sending response %d for pending guid: %s block id: %" PRIu64 163 " due to long tap\n", 164 false, ToString(mPendingTouchPreventedGuid).c_str(), 165 mPendingTouchPreventedBlockId); 166 mContentReceivedInputBlockCallback(mPendingTouchPreventedBlockId, false); 167 } 168 169 #ifdef XP_WIN 170 // On Windows, we fire the contextmenu events when the user lifts their 171 // finger, in keeping with the platform convention. This happens in the 172 // ProcessLongTapUp function. However, we still fire the eMouseLongTap event 173 // at this time, because things like text selection or dragging may want 174 // to know about it. 175 APZCCallbackHelper::DispatchSynthesizedMouseEvent( 176 eMouseLongTap, aPoint * aScale, mLastTouchIdentifier, aModifiers, 177 /*clickCount*/ 1, mPrecedingPointerDownState, widget, 178 mLastTouchSynthesizedForTests); 179 #else 180 PreventDefaultResult preventDefaultResult = 181 FireContextmenuEvents(aPresShell, aPoint, aScale, mLastTouchIdentifier, 182 aModifiers, widget, mLastTouchSynthesizedForTests); 183 #endif 184 185 const bool contextmenuOpen = 186 #ifdef XP_WIN 187 // On Windows context menu will never be opened by long tap events, the 188 // menu will open after the user lifts their finger. 189 false; 190 #elif defined(MOZ_WIDGET_ANDROID) 191 // On Android, GeckoView calls preventDefault() in a JSActor 192 // (ContentDelegateChild.sys.mjs) when opening context menu so that we can 193 // tell whether contextmenu opens in response to the contextmenu event by 194 // checking where preventDefault() got called. 195 preventDefaultResult == PreventDefaultResult::ByChrome; 196 #else 197 // On desktop platforms (other than Windows) unlike Android, context menu 198 // can be opened anywhere even if, for example, there's no link under the 199 // touch point. So we can assume that "not preventDefault" means a context 200 // menu is open. 201 preventDefaultResult == PreventDefaultResult::No; 202 #endif 203 // Assuming that contextmenuOpen=true here means a context menu was opened, it 204 // will be treated as "preventDefaulted" in APZ. 205 mContentReceivedInputBlockCallback(aInputBlockId, contextmenuOpen); 206 207 if (contextmenuOpen) { 208 // Also send a touchcancel to content 209 // a) on Android if browser's contextmenu is open 210 // b) on desktop platforms other than Windows if browser's contextmenu is 211 // open 212 // so that listeners that might be waiting for a touchend don't trigger. 213 WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get()); 214 cancelTouchEvent.mFlags.mIsSynthesizedForTests = 215 static_cast<bool>(mLastTouchSynthesizedForTests); 216 cancelTouchEvent.mModifiers = aModifiers; 217 auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale); 218 cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch( 219 mLastTouchIdentifier, ldPoint, LayoutDeviceIntPoint(), 0, 0)); 220 APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent); 221 } 222 } 223 224 void APZEventState::ProcessLongTapUp(PresShell* aPresShell, 225 const CSSPoint& aPoint, 226 const CSSToLayoutDeviceScale& aScale, 227 Modifiers aModifiers) { 228 #ifdef XP_WIN 229 nsCOMPtr<nsIWidget> widget = GetWidget(); 230 if (widget) { 231 FireContextmenuEvents(aPresShell, aPoint, aScale, mLastTouchIdentifier, 232 aModifiers, widget, mLastTouchSynthesizedForTests); 233 } 234 #endif 235 } 236 237 void APZEventState::ProcessTouchEvent( 238 const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, 239 uint64_t aInputBlockId, nsEventStatus aApzResponse, 240 nsEventStatus aContentResponse, 241 nsTArray<TouchBehaviorFlags>&& aAllowedTouchBehaviors) { 242 bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault; 243 if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) { 244 mElementStateManager->SetTargetElement( 245 aEvent.mTouches[0]->GetOriginalTarget(), 246 ElementStateManager::PreventDefault{isTouchPrevented}); 247 mLastTouchIdentifier = aEvent.mTouches[0]->Identifier(); 248 mLastTouchSynthesizedForTests = 249 static_cast<SynthesizeForTests>(aEvent.mFlags.mIsSynthesizedForTests); 250 } 251 if (aEvent.mMessage == eTouchStart) { 252 // We get the allowed touch behaviors on a touchstart, but may not actually 253 // use them until the first touchmove, so we stash them in a member 254 // variable. 255 mTouchBlockAllowedBehaviors = std::move(aAllowedTouchBehaviors); 256 } 257 258 bool mayNeedPointerCancelEvent = false; 259 APZES_LOG("Handling event type %d isPrevented=%d\n", aEvent.mMessage, 260 isTouchPrevented); 261 switch (aEvent.mMessage) { 262 case eTouchStart: { 263 mTouchEndCancelled = false; 264 mReceivedNonTouchStart = false; 265 mTouchRollup = do_GetWeakReference(widget::nsAutoRollup::GetLastRollup()); 266 267 SendPendingTouchPreventedResponse(false); 268 // The above call may have sent a message to APZ if we get two 269 // TOUCH_STARTs in a row and just responded to the first one. 270 271 // We're about to send a response back to APZ, but we should only do it 272 // for events that went through APZ (which should be all of them). 273 MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); 274 275 // If the first touchstart event was preventDefaulted, ensure that any 276 // subsequent additional touchstart events also get preventDefaulted. This 277 // ensures that e.g. pinch zooming is prevented even if just the first 278 // touchstart was prevented by content. 279 if (mTouchCounter.GetActiveTouchCount() == 0) { 280 mFirstTouchCancelled = isTouchPrevented; 281 const PointerInfo* pointerInfo = 282 !aEvent.mTouches.IsEmpty() ? PointerEventHandler::GetPointerInfo( 283 aEvent.mTouches[0]->Identifier()) 284 : nullptr; 285 mPrecedingPointerDownState = 286 pointerInfo && pointerInfo->mPreventMouseEventByContent 287 ? PrecedingPointerDown::ConsumedByContent 288 : PrecedingPointerDown::NotConsumed; 289 } else { 290 if (mFirstTouchCancelled && !isTouchPrevented) { 291 APZES_LOG( 292 "Propagating prevent-default from first-touch for block %" PRIu64 293 "\n", 294 aInputBlockId); 295 } 296 isTouchPrevented |= mFirstTouchCancelled; 297 } 298 299 mTouchStartPrevented = isTouchPrevented; 300 if (isTouchPrevented) { 301 mContentReceivedInputBlockCallback(aInputBlockId, isTouchPrevented); 302 } else { 303 APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n", 304 aInputBlockId, ToString(aGuid).c_str()); 305 mPendingTouchPreventedResponse = true; 306 mPendingTouchPreventedGuid = aGuid; 307 mPendingTouchPreventedBlockId = aInputBlockId; 308 } 309 break; 310 } 311 312 case eTouchEnd: 313 if (isTouchPrevented) { 314 mTouchEndCancelled = true; 315 mEndTouchState = apz::SingleTapState::NotClick; 316 } 317 [[fallthrough]]; 318 case eTouchCancel: 319 if (mElementStateManager->HandleTouchEndEvent(mEndTouchState)) { 320 mEndTouchState = apz::SingleTapState::NotClick; 321 } 322 [[fallthrough]]; 323 case eTouchMove: { 324 if (!mReceivedNonTouchStart) { 325 // In the case where `touchstart` was preventDefaulted, 326 // pointercancel event should NOT be fired. 327 mayNeedPointerCancelEvent = !isTouchPrevented && !mTouchStartPrevented; 328 mReceivedNonTouchStart = true; 329 } 330 331 if (mPendingTouchPreventedResponse) { 332 MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid); 333 if (aEvent.mMessage == eTouchCancel) { 334 // If we received a touch-cancel and we were waiting for the 335 // first touch-move to send a content response, make the content 336 // response be preventDefault=true. This is the safer choice 337 // because content might have prevented the first touch-move, 338 // and even though the touch-cancel means any subsequent touch-moves 339 // will not be processed, the content response still influences 340 // the InputResult sent to GeckoView. 341 isTouchPrevented = true; 342 } 343 mContentReceivedInputBlockCallback(aInputBlockId, isTouchPrevented); 344 mPendingTouchPreventedResponse = false; 345 } 346 break; 347 } 348 349 case eTouchRawUpdate: 350 break; 351 352 default: 353 MOZ_ASSERT_UNREACHABLE("Unknown touch event type"); 354 break; 355 } 356 357 mTouchCounter.Update(aEvent); 358 if (mTouchCounter.GetActiveTouchCount() == 0) { 359 mFirstTouchCancelled = false; 360 } 361 362 APZES_LOG("Pointercancel if %d %d %d %d\n", mayNeedPointerCancelEvent, 363 !isTouchPrevented, aApzResponse == nsEventStatus_eConsumeDoDefault, 364 MainThreadAgreesEventsAreConsumableByAPZ()); 365 // From https://w3c.github.io/pointerevents/#the-pointercancel-event; 366 // The user agent MUST fire a pointer event named pointercancel when it 367 // detects a scenario to suppress a pointer event stream. 368 // 369 // And "suppress a pointer event steam" is defined in 370 // https://w3c.github.io/pointerevents/#suppressing-a-pointer-event-stream . 371 // 372 // There are four scenarios when the user agent fires a pointercancel event in 373 // the spec. Below code corresponds to one of the scenarios (the third bullet 374 // point); 375 // The pointer is subsequently used by the user agent to manipulate the page 376 // viewport (e.g. panning or zooming). 377 if (mayNeedPointerCancelEvent && 378 aApzResponse == nsEventStatus_eConsumeDoDefault && 379 MainThreadAgreesEventsAreConsumableByAPZ()) { 380 WidgetTouchEvent cancelEvent(aEvent); 381 cancelEvent.mFlags.mIsSynthesizedForTests = 382 aEvent.mFlags.mIsSynthesizedForTests; 383 cancelEvent.mMessage = eTouchPointerCancel; 384 cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel; 385 for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) { 386 if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) { 387 touch->convertToPointer = true; 388 } 389 } 390 cancelEvent.mWidget->DispatchEvent(&cancelEvent); 391 } 392 } 393 394 bool APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() const { 395 // APZ errs on the side of saying it can consume touch events to perform 396 // default user-agent behaviours. In particular it may say this if it hasn't 397 // received accurate touch-action information. Here we double-check using 398 // accurate touch-action information. This code is kinda-sorta the main 399 // thread equivalent of AsyncPanZoomController::ArePointerEventsConsumable(). 400 401 switch (mTouchBlockAllowedBehaviors.Length()) { 402 case 0: 403 // If we don't have any touch-action (e.g. because it is disabled) then 404 // APZ has no restrictions. 405 return true; 406 407 case 1: { 408 // If there's one touch point in this touch block, then check the pan-x 409 // and pan-y flags. If neither is allowed, then we disagree with APZ and 410 // say that it can't do anything with this touch block. Note that it would 411 // be even better if we could check the allowed scroll directions of the 412 // scrollframe at this point and refine this further. 413 TouchBehaviorFlags flags = mTouchBlockAllowedBehaviors[0]; 414 return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) || 415 (flags & AllowedTouchBehavior::VERTICAL_PAN); 416 } 417 418 case 2: { 419 // If there's two touch points in this touch block, check that they both 420 // allow zooming. 421 for (const auto& allowed : mTouchBlockAllowedBehaviors) { 422 if (!(allowed & AllowedTouchBehavior::PINCH_ZOOM)) { 423 return false; 424 } 425 } 426 return true; 427 } 428 429 default: 430 // More than two touch points? APZ shouldn't be doing anything with this, 431 // so APZ shouldn't be consuming them. 432 return false; 433 } 434 } 435 436 void APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent, 437 uint64_t aInputBlockId) { 438 // If this event starts a swipe, indicate that it shouldn't result in a 439 // scroll by setting defaultPrevented to true. 440 bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe(); 441 mContentReceivedInputBlockCallback(aInputBlockId, defaultPrevented); 442 } 443 444 void APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent, 445 uint64_t aInputBlockId) { 446 bool defaultPrevented = false; 447 mContentReceivedInputBlockCallback(aInputBlockId, defaultPrevented); 448 } 449 450 void APZEventState::ProcessAPZStateChange(ViewID aViewId, 451 APZStateChange aChange, int aArg, 452 Maybe<uint64_t> aInputBlockId) { 453 switch (aChange) { 454 case APZStateChange::eTransformBegin: { 455 ScrollContainerFrame* sf = 456 nsLayoutUtils::FindScrollContainerFrameFor(aViewId); 457 if (sf) { 458 sf->SetTransformingByAPZ(true); 459 } 460 461 nsIContent* content = nsLayoutUtils::FindContentFor(aViewId); 462 dom::Document* doc = content ? content->GetComposedDoc() : nullptr; 463 nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr); 464 if (docshell && sf) { 465 nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get()); 466 nsdocshell->NotifyAsyncPanZoomStarted(); 467 } 468 break; 469 } 470 case APZStateChange::eTransformEnd: { 471 ScrollContainerFrame* sf = 472 nsLayoutUtils::FindScrollContainerFrameFor(aViewId); 473 if (sf) { 474 sf->SetTransformingByAPZ(false); 475 } 476 477 nsIContent* content = nsLayoutUtils::FindContentFor(aViewId); 478 dom::Document* doc = content ? content->GetComposedDoc() : nullptr; 479 nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr); 480 if (docshell && sf) { 481 nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get()); 482 nsdocshell->NotifyAsyncPanZoomStopped(); 483 } 484 break; 485 } 486 case APZStateChange::eStartTouch: { 487 bool canBePanOrZoom = aArg; 488 mElementStateManager->HandleTouchStart(canBePanOrZoom); 489 // If this is a non-scrollable content, set a timer for the amount of 490 // time specified by ui.touch_activation.duration_ms to clear the 491 // active element state. 492 APZES_LOG("%s: can-be-pan-or-zoom=%d", __FUNCTION__, aArg); 493 if (!canBePanOrZoom) { 494 MOZ_ASSERT(aInputBlockId.isSome()); 495 } 496 break; 497 } 498 case APZStateChange::eStartPanning: { 499 // The user started to pan, so we don't want anything to be :active. 500 mElementStateManager->HandleStartPanning(); 501 break; 502 } 503 case APZStateChange::eEndTouch: { 504 mEndTouchState = static_cast<apz::SingleTapState>(aArg); 505 if (mElementStateManager->HandleTouchEnd(mEndTouchState)) { 506 mEndTouchState = apz::SingleTapState::NotClick; 507 } 508 break; 509 } 510 } 511 } 512 513 void APZEventState::Destroy() { mElementStateManager->Destroy(); } 514 515 void APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault) { 516 if (mPendingTouchPreventedResponse) { 517 APZES_LOG("Sending response %d for pending guid: %s block id: %" PRIu64 518 "\n", 519 aPreventDefault, ToString(mPendingTouchPreventedGuid).c_str(), 520 mPendingTouchPreventedBlockId); 521 mContentReceivedInputBlockCallback(mPendingTouchPreventedBlockId, 522 aPreventDefault); 523 mPendingTouchPreventedResponse = false; 524 } 525 } 526 527 already_AddRefed<nsIWidget> APZEventState::GetWidget() const { 528 nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget); 529 return result.forget(); 530 } 531 532 already_AddRefed<nsIContent> APZEventState::GetTouchRollup() const { 533 nsCOMPtr<nsIContent> result = do_QueryReferent(mTouchRollup); 534 return result.forget(); 535 } 536 537 } // namespace layers 538 } // namespace mozilla