Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "APZEventState.h"
7 :
8 : #include "ActiveElementManager.h"
9 : #include "APZCCallbackHelper.h"
10 : #include "gfxPrefs.h"
11 : #include "LayersLogging.h"
12 : #include "mozilla/BasicEvents.h"
13 : #include "mozilla/dom/TabChild.h"
14 : #include "mozilla/dom/TabGroup.h"
15 : #include "mozilla/IntegerPrintfMacros.h"
16 : #include "mozilla/Move.h"
17 : #include "mozilla/Preferences.h"
18 : #include "mozilla/TouchEvents.h"
19 : #include "mozilla/layers/APZCCallbackHelper.h"
20 : #include "nsCOMPtr.h"
21 : #include "nsDocShell.h"
22 : #include "nsIDOMMouseEvent.h"
23 : #include "nsIDOMWindowUtils.h"
24 : #include "nsIScrollableFrame.h"
25 : #include "nsIScrollbarMediator.h"
26 : #include "nsITimer.h"
27 : #include "nsIWeakReferenceUtils.h"
28 : #include "nsIWidget.h"
29 : #include "nsLayoutUtils.h"
30 : #include "nsQueryFrame.h"
31 : #include "TouchManager.h"
32 : #include "nsIDOMMouseEvent.h"
33 : #include "nsLayoutUtils.h"
34 : #include "nsIScrollableFrame.h"
35 : #include "nsIScrollbarMediator.h"
36 : #include "mozilla/TouchEvents.h"
37 : #include "mozilla/widget/nsAutoRollup.h"
38 :
39 : #define APZES_LOG(...)
40 : // #define APZES_LOG(...) printf_stderr("APZES: " __VA_ARGS__)
41 :
42 : // Static helper functions
43 : namespace {
44 :
45 : int32_t
46 0 : WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers)
47 : {
48 0 : int32_t result = 0;
49 0 : if (aModifiers & mozilla::MODIFIER_SHIFT) {
50 0 : result |= nsIDOMWindowUtils::MODIFIER_SHIFT;
51 : }
52 0 : if (aModifiers & mozilla::MODIFIER_CONTROL) {
53 0 : result |= nsIDOMWindowUtils::MODIFIER_CONTROL;
54 : }
55 0 : if (aModifiers & mozilla::MODIFIER_ALT) {
56 0 : result |= nsIDOMWindowUtils::MODIFIER_ALT;
57 : }
58 0 : if (aModifiers & mozilla::MODIFIER_META) {
59 0 : result |= nsIDOMWindowUtils::MODIFIER_META;
60 : }
61 0 : if (aModifiers & mozilla::MODIFIER_ALTGRAPH) {
62 0 : result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH;
63 : }
64 0 : if (aModifiers & mozilla::MODIFIER_CAPSLOCK) {
65 0 : result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK;
66 : }
67 0 : if (aModifiers & mozilla::MODIFIER_FN) {
68 0 : result |= nsIDOMWindowUtils::MODIFIER_FN;
69 : }
70 0 : if (aModifiers & mozilla::MODIFIER_FNLOCK) {
71 0 : result |= nsIDOMWindowUtils::MODIFIER_FNLOCK;
72 : }
73 0 : if (aModifiers & mozilla::MODIFIER_NUMLOCK) {
74 0 : result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK;
75 : }
76 0 : if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) {
77 0 : result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK;
78 : }
79 0 : if (aModifiers & mozilla::MODIFIER_SYMBOL) {
80 0 : result |= nsIDOMWindowUtils::MODIFIER_SYMBOL;
81 : }
82 0 : if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) {
83 0 : result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK;
84 : }
85 0 : if (aModifiers & mozilla::MODIFIER_OS) {
86 0 : result |= nsIDOMWindowUtils::MODIFIER_OS;
87 : }
88 0 : return result;
89 : }
90 :
91 : } // namespace
92 :
93 : namespace mozilla {
94 : namespace layers {
95 :
96 : static int32_t sActiveDurationMs = 10;
97 : static bool sActiveDurationMsSet = false;
98 :
99 2 : APZEventState::APZEventState(nsIWidget* aWidget,
100 2 : ContentReceivedInputBlockCallback&& aCallback)
101 : : mWidget(nullptr) // initialized in constructor body
102 2 : , mActiveElementManager(new ActiveElementManager())
103 2 : , mContentReceivedInputBlockCallback(Move(aCallback))
104 : , mPendingTouchPreventedResponse(false)
105 : , mPendingTouchPreventedBlockId(0)
106 : , mEndTouchIsClick(false)
107 : , mTouchEndCancelled(false)
108 6 : , mLastTouchIdentifier(0)
109 : {
110 : nsresult rv;
111 2 : mWidget = do_GetWeakReference(aWidget, &rv);
112 2 : MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that"
113 : " does not support weak references. APZ will NOT work!");
114 :
115 2 : if (!sActiveDurationMsSet) {
116 : Preferences::AddIntVarCache(&sActiveDurationMs,
117 : "ui.touch_activation.duration_ms",
118 2 : sActiveDurationMs);
119 2 : sActiveDurationMsSet = true;
120 : }
121 2 : }
122 :
123 0 : APZEventState::~APZEventState()
124 0 : {}
125 :
126 : class DelayedFireSingleTapEvent final : public nsITimerCallback
127 : {
128 : public:
129 : NS_DECL_ISUPPORTS
130 :
131 0 : DelayedFireSingleTapEvent(nsWeakPtr aWidget,
132 : LayoutDevicePoint& aPoint,
133 : Modifiers aModifiers,
134 : int32_t aClickCount,
135 : nsITimer* aTimer,
136 : RefPtr<nsIContent>& aTouchRollup)
137 0 : : mWidget(aWidget)
138 : , mPoint(aPoint)
139 : , mModifiers(aModifiers)
140 : , mClickCount(aClickCount)
141 : // Hold the reference count until we are called back.
142 : , mTimer(aTimer)
143 0 : , mTouchRollup(aTouchRollup)
144 : {
145 0 : }
146 :
147 0 : NS_IMETHOD Notify(nsITimer*) override
148 : {
149 0 : if (nsCOMPtr<nsIWidget> widget = do_QueryReferent(mWidget)) {
150 0 : widget::nsAutoRollup rollup(mTouchRollup.get());
151 0 : APZCCallbackHelper::FireSingleTapEvent(mPoint, mModifiers, mClickCount, widget);
152 : }
153 0 : mTimer = nullptr;
154 0 : return NS_OK;
155 : }
156 :
157 0 : void ClearTimer() {
158 0 : mTimer = nullptr;
159 0 : }
160 :
161 : private:
162 0 : ~DelayedFireSingleTapEvent()
163 0 : {
164 0 : }
165 :
166 : nsWeakPtr mWidget;
167 : LayoutDevicePoint mPoint;
168 : Modifiers mModifiers;
169 : int32_t mClickCount;
170 : nsCOMPtr<nsITimer> mTimer;
171 : RefPtr<nsIContent> mTouchRollup;
172 : };
173 :
174 0 : NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback)
175 :
176 : void
177 0 : APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
178 : const CSSToLayoutDeviceScale& aScale,
179 : Modifiers aModifiers,
180 : const ScrollableLayerGuid& aGuid,
181 : int32_t aClickCount)
182 : {
183 : APZES_LOG("Handling single tap at %s on %s with %d\n",
184 : Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mTouchEndCancelled);
185 :
186 0 : RefPtr<nsIContent> touchRollup = GetTouchRollup();
187 0 : mTouchRollup = nullptr;
188 :
189 0 : nsCOMPtr<nsIWidget> widget = GetWidget();
190 0 : if (!widget) {
191 0 : return;
192 : }
193 :
194 0 : if (mTouchEndCancelled) {
195 0 : return;
196 : }
197 :
198 0 : LayoutDevicePoint ldPoint = aPoint * aScale;
199 0 : if (!mActiveElementManager->ActiveElementUsesStyle()) {
200 : // If the active element isn't visually affected by the :active style, we
201 : // have no need to wait the extra sActiveDurationMs to make the activation
202 : // visually obvious to the user.
203 0 : widget::nsAutoRollup rollup(touchRollup.get());
204 0 : APZCCallbackHelper::FireSingleTapEvent(ldPoint, aModifiers, aClickCount, widget);
205 0 : return;
206 : }
207 :
208 : APZES_LOG("Active element uses style, scheduling timer for click event\n");
209 0 : nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
210 0 : dom::TabChild* tabChild = widget->GetOwningTabChild();
211 :
212 0 : if (tabChild && XRE_IsContentProcess()) {
213 0 : timer->SetTarget(
214 0 : tabChild->TabGroup()->EventTargetFor(TaskCategory::Other));
215 : }
216 : RefPtr<DelayedFireSingleTapEvent> callback =
217 : new DelayedFireSingleTapEvent(mWidget, ldPoint, aModifiers, aClickCount,
218 0 : timer, touchRollup);
219 0 : nsresult rv = timer->InitWithCallback(callback,
220 : sActiveDurationMs,
221 0 : nsITimer::TYPE_ONE_SHOT);
222 0 : if (NS_FAILED(rv)) {
223 : // Make |callback| not hold the timer, so they will both be destructed when
224 : // we leave the scope of this function.
225 0 : callback->ClearTimer();
226 : }
227 : }
228 :
229 : bool
230 0 : APZEventState::FireContextmenuEvents(const nsCOMPtr<nsIPresShell>& aPresShell,
231 : const CSSPoint& aPoint,
232 : const CSSToLayoutDeviceScale& aScale,
233 : Modifiers aModifiers,
234 : const nsCOMPtr<nsIWidget>& aWidget)
235 : {
236 : // Converting the modifiers to DOM format for the DispatchMouseEvent call
237 : // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
238 : // just converts them back to widget format, but that API has many callers,
239 : // including in JS code, so it's not trivial to change.
240 : bool eventHandled =
241 0 : APZCCallbackHelper::DispatchMouseEvent(aPresShell, NS_LITERAL_STRING("contextmenu"),
242 : aPoint, 2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
243 : nsIDOMMouseEvent::MOZ_SOURCE_TOUCH,
244 0 : 0 /* Use the default value here. */);
245 :
246 : APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
247 0 : if (eventHandled) {
248 : // If the contextmenu event was handled then we're showing a contextmenu,
249 : // and so we should remove any activation
250 0 : mActiveElementManager->ClearActivation();
251 : #ifndef XP_WIN
252 : } else {
253 : // If the contextmenu wasn't consumed, fire the eMouseLongTap event.
254 0 : nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
255 0 : eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers,
256 0 : /*clickCount*/ 1, aWidget);
257 0 : eventHandled = (status == nsEventStatus_eConsumeNoDefault);
258 : APZES_LOG("eMouseLongTap event handled: %d\n", eventHandled);
259 : #endif
260 : }
261 :
262 0 : return eventHandled;
263 : }
264 :
265 : void
266 0 : APZEventState::ProcessLongTap(const nsCOMPtr<nsIPresShell>& aPresShell,
267 : const CSSPoint& aPoint,
268 : const CSSToLayoutDeviceScale& aScale,
269 : Modifiers aModifiers,
270 : const ScrollableLayerGuid& aGuid,
271 : uint64_t aInputBlockId)
272 : {
273 : APZES_LOG("Handling long tap at %s\n", Stringify(aPoint).c_str());
274 :
275 0 : nsCOMPtr<nsIWidget> widget = GetWidget();
276 0 : if (!widget) {
277 0 : return;
278 : }
279 :
280 0 : SendPendingTouchPreventedResponse(false);
281 :
282 : #ifdef XP_WIN
283 : // On Windows, we fire the contextmenu events when the user lifts their
284 : // finger, in keeping with the platform convention. This happens in the
285 : // ProcessLongTapUp function. However, we still fire the eMouseLongTap event
286 : // at this time, because things like text selection or dragging may want
287 : // to know about it.
288 : nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
289 : eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, /*clickCount*/ 1,
290 : widget);
291 :
292 : bool eventHandled = (status == nsEventStatus_eConsumeNoDefault);
293 : #else
294 0 : bool eventHandled = FireContextmenuEvents(aPresShell, aPoint, aScale,
295 0 : aModifiers, widget);
296 : #endif
297 0 : mContentReceivedInputBlockCallback(aGuid, aInputBlockId, eventHandled);
298 :
299 0 : if (eventHandled) {
300 : // Also send a touchcancel to content, so that listeners that might be
301 : // waiting for a touchend don't trigger.
302 0 : WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get());
303 0 : cancelTouchEvent.mModifiers = aModifiers;
304 0 : auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale);
305 0 : cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch(mLastTouchIdentifier,
306 0 : ldPoint, LayoutDeviceIntPoint(), 0, 0));
307 0 : APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent);
308 : }
309 : }
310 :
311 : void
312 0 : APZEventState::ProcessLongTapUp(const nsCOMPtr<nsIPresShell>& aPresShell,
313 : const CSSPoint& aPoint,
314 : const CSSToLayoutDeviceScale& aScale,
315 : Modifiers aModifiers)
316 : {
317 : #ifdef XP_WIN
318 : nsCOMPtr<nsIWidget> widget = GetWidget();
319 : if (widget) {
320 : FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget);
321 : }
322 : #endif
323 0 : }
324 :
325 : void
326 0 : APZEventState::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
327 : const ScrollableLayerGuid& aGuid,
328 : uint64_t aInputBlockId,
329 : nsEventStatus aApzResponse,
330 : nsEventStatus aContentResponse)
331 : {
332 0 : if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) {
333 0 : mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget());
334 0 : mLastTouchIdentifier = aEvent.mTouches[0]->Identifier();
335 : }
336 :
337 0 : bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault;
338 0 : bool sentContentResponse = false;
339 : APZES_LOG("Handling event type %d\n", aEvent.mMessage);
340 0 : switch (aEvent.mMessage) {
341 : case eTouchStart: {
342 0 : mTouchEndCancelled = false;
343 0 : mTouchRollup = do_GetWeakReference(widget::nsAutoRollup::GetLastRollup());
344 :
345 0 : sentContentResponse = SendPendingTouchPreventedResponse(false);
346 : // sentContentResponse can be true here if we get two TOUCH_STARTs in a row
347 : // and just responded to the first one.
348 :
349 : // We're about to send a response back to APZ, but we should only do it
350 : // for events that went through APZ (which should be all of them).
351 0 : MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ);
352 :
353 0 : if (isTouchPrevented) {
354 0 : mContentReceivedInputBlockCallback(aGuid, aInputBlockId, isTouchPrevented);
355 0 : sentContentResponse = true;
356 : } else {
357 : APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n",
358 : aInputBlockId, Stringify(aGuid).c_str());
359 0 : mPendingTouchPreventedResponse = true;
360 0 : mPendingTouchPreventedGuid = aGuid;
361 0 : mPendingTouchPreventedBlockId = aInputBlockId;
362 : }
363 0 : break;
364 : }
365 :
366 : case eTouchEnd:
367 0 : if (isTouchPrevented) {
368 0 : mTouchEndCancelled = true;
369 0 : mEndTouchIsClick = false;
370 : }
371 : MOZ_FALLTHROUGH;
372 : case eTouchCancel:
373 0 : mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick);
374 : MOZ_FALLTHROUGH;
375 : case eTouchMove: {
376 0 : if (mPendingTouchPreventedResponse) {
377 0 : MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
378 : }
379 0 : sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented);
380 0 : break;
381 : }
382 :
383 : default:
384 0 : MOZ_ASSERT_UNREACHABLE("Unknown touch event type");
385 : break;
386 : }
387 :
388 0 : if (sentContentResponse &&
389 0 : aApzResponse == nsEventStatus_eConsumeDoDefault &&
390 0 : gfxPrefs::PointerEventsEnabled()) {
391 0 : WidgetTouchEvent cancelEvent(aEvent);
392 0 : cancelEvent.mMessage = eTouchPointerCancel;
393 0 : cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel;
394 0 : for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
395 0 : if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
396 0 : touch->convertToPointer = true;
397 : }
398 : }
399 : nsEventStatus status;
400 0 : cancelEvent.mWidget->DispatchEvent(&cancelEvent, status);
401 : }
402 0 : }
403 :
404 : void
405 0 : APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent,
406 : const ScrollableLayerGuid& aGuid,
407 : uint64_t aInputBlockId)
408 : {
409 : // If this event starts a swipe, indicate that it shouldn't result in a
410 : // scroll by setting defaultPrevented to true.
411 0 : bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe();
412 0 : mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
413 0 : }
414 :
415 : void
416 0 : APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent,
417 : const ScrollableLayerGuid& aGuid,
418 : uint64_t aInputBlockId)
419 : {
420 : // If we get here and the drag block has not been confirmed by the code in
421 : // nsSliderFrame, then no scrollbar reacted to the event thus APZC will
422 : // ignore this drag block. We can send defaultPrevented as either true or
423 : // false, it doesn't matter, because APZ won't have the scrollbar metrics
424 : // anyway, and will know to drop the block.
425 0 : bool defaultPrevented = false;
426 0 : mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
427 0 : }
428 :
429 : void
430 0 : APZEventState::ProcessAPZStateChange(ViewID aViewId,
431 : APZStateChange aChange,
432 : int aArg)
433 : {
434 0 : switch (aChange)
435 : {
436 : case APZStateChange::eTransformBegin:
437 : {
438 0 : nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
439 0 : if (sf) {
440 0 : sf->SetTransformingByAPZ(true);
441 : }
442 0 : nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
443 0 : if (scrollbarMediator) {
444 0 : scrollbarMediator->ScrollbarActivityStarted();
445 : }
446 :
447 0 : nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
448 0 : nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
449 0 : nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
450 0 : if (docshell && sf) {
451 0 : nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
452 0 : nsdocshell->NotifyAsyncPanZoomStarted();
453 : }
454 0 : break;
455 : }
456 : case APZStateChange::eTransformEnd:
457 : {
458 0 : nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
459 0 : if (sf) {
460 0 : sf->SetTransformingByAPZ(false);
461 : }
462 0 : nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
463 0 : if (scrollbarMediator) {
464 0 : scrollbarMediator->ScrollbarActivityStopped();
465 : }
466 :
467 0 : nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
468 0 : nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
469 0 : nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
470 0 : if (docshell && sf) {
471 0 : nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
472 0 : nsdocshell->NotifyAsyncPanZoomStopped();
473 : }
474 0 : break;
475 : }
476 : case APZStateChange::eStartTouch:
477 : {
478 0 : mActiveElementManager->HandleTouchStart(aArg);
479 0 : break;
480 : }
481 : case APZStateChange::eStartPanning:
482 : {
483 : // The user started to pan, so we don't want anything to be :active.
484 0 : mActiveElementManager->ClearActivation();
485 0 : break;
486 : }
487 : case APZStateChange::eEndTouch:
488 : {
489 0 : mEndTouchIsClick = aArg;
490 0 : mActiveElementManager->HandleTouchEnd();
491 0 : break;
492 : }
493 : }
494 0 : }
495 :
496 : void
497 0 : APZEventState::ProcessClusterHit()
498 : {
499 : // If we hit a cluster of links then we shouldn't activate any of them,
500 : // as we will be showing the zoomed view. (This is only called on Fennec).
501 : #ifndef MOZ_WIDGET_ANDROID
502 0 : MOZ_ASSERT(false);
503 : #endif
504 : mActiveElementManager->ClearActivation();
505 : }
506 :
507 : bool
508 0 : APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault)
509 : {
510 0 : if (mPendingTouchPreventedResponse) {
511 : APZES_LOG("Sending response %d for pending guid: %s\n", aPreventDefault,
512 : Stringify(mPendingTouchPreventedGuid).c_str());
513 0 : mContentReceivedInputBlockCallback(mPendingTouchPreventedGuid,
514 0 : mPendingTouchPreventedBlockId, aPreventDefault);
515 0 : mPendingTouchPreventedResponse = false;
516 0 : return true;
517 : }
518 0 : return false;
519 : }
520 :
521 : already_AddRefed<nsIWidget>
522 0 : APZEventState::GetWidget() const
523 : {
524 0 : nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget);
525 0 : return result.forget();
526 : }
527 :
528 : already_AddRefed<nsIContent>
529 0 : APZEventState::GetTouchRollup() const
530 : {
531 0 : nsCOMPtr<nsIContent> result = do_QueryReferent(mTouchRollup);
532 0 : return result.forget();
533 : }
534 :
535 : } // namespace layers
536 : } // namespace mozilla
|