1 /*
2  * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 package java.awt;
27
28 import java.awt.event.*;
29
30 import java.awt.peer.ComponentPeer;
31
32 import java.lang.ref.WeakReference;
33 import java.lang.reflect.InvocationTargetException;
34
35 import java.security.AccessController;
36 import java.security.PrivilegedAction;
37
38 import java.util.EmptyStackException;
39
40 import sun.awt.*;
41 import sun.awt.dnd.SunDropTargetEvent;
42 import sun.util.logging.PlatformLogger;
43
44 import java.util.concurrent.locks.Condition;
45 import java.util.concurrent.locks.Lock;
46 import java.util.concurrent.atomic.AtomicInteger;
47
48 import java.security.AccessControlContext;
49
50 import jdk.internal.misc.SharedSecrets;
51 import jdk.internal.misc.JavaSecurityAccess;
52
53 /**
54  * {@code EventQueue} is a platform-independent class
55  * that queues events, both from the underlying peer classes
56  * and from trusted application classes.
57  * <p>
58  * It encapsulates asynchronous event dispatch machinery which
59  * extracts events from the queue and dispatches them by calling
60  * {@link #dispatchEvent(AWTEvent) dispatchEvent(AWTEvent)} method
61  * on this {@code EventQueue} with the event to be dispatched
62  * as an argument.  The particular behavior of this machinery is
63  * implementation-dependent.  The only requirements are that events
64  * which were actually enqueued to this queue (note that events
65  * being posted to the {@code EventQueue} can be coalesced)
66  * are dispatched:
67  * <dl>
68  *   <dt> Sequentially.
69  *   <dd> That is, it is not permitted that several events from
70  *        this queue are dispatched simultaneously.
71  *   <dt> In the same order as they are enqueued.
72  *   <dd> That is, if {@code AWTEvent}&nbsp;A is enqueued
73  *        to the {@code EventQueue} before
74  *        {@code AWTEvent}&nbsp;B then event B will not be
75  *        dispatched before event A.
76  * </dl>
77  * <p>
78  * Some browsers partition applets in different code bases into
79  * separate contexts, and establish walls between these contexts.
80  * In such a scenario, there will be one {@code EventQueue}
81  * per context. Other browsers place all applets into the same
82  * context, implying that there will be only a single, global
83  * {@code EventQueue} for all applets. This behavior is
84  * implementation-dependent.  Consult your browser's documentation
85  * for more information.
86  * <p>
87  * For information on the threading issues of the event dispatch
88  * machinery, see <a href="doc-files/AWTThreadIssues.html#Autoshutdown"
89  * >AWT Threading Issues</a>.
90  *
91  * @author Thomas Ball
92  * @author Fred Ecks
93  * @author David Mendenhall
94  *
95  * @since       1.1
96  */

97 public class EventQueue {
98     private static final AtomicInteger threadInitNumber = new AtomicInteger(0);
99
100     private static final int LOW_PRIORITY = 0;
101     private static final int NORM_PRIORITY = 1;
102     private static final int HIGH_PRIORITY = 2;
103     private static final int ULTIMATE_PRIORITY = 3;
104
105     private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;
106
107     /*
108      * We maintain one Queue for each priority that the EventQueue supports.
109      * That is, the EventQueue object is actually implemented as
110      * NUM_PRIORITIES queues and all Events on a particular internal Queue
111      * have identical priority. Events are pulled off the EventQueue starting
112      * with the Queue of highest priority. We progress in decreasing order
113      * across all Queues.
114      */

115     private Queue[] queues = new Queue[NUM_PRIORITIES];
116
117     /*
118      * The next EventQueue on the stack, or null if this EventQueue is
119      * on the top of the stack.  If nextQueue is non-null, requests to post
120      * an event are forwarded to nextQueue.
121      */

122     private EventQueue nextQueue;
123
124     /*
125      * The previous EventQueue on the stack, or null if this is the
126      * "base" EventQueue.
127      */

128     private EventQueue previousQueue;
129
130     /*
131      * A single lock to synchronize the push()/pop() and related operations with
132      * all the EventQueues from the AppContext. Synchronization on any particular
133      * event queue(s) is not enough: we should lock the whole stack.
134      */

135     private final Lock pushPopLock;
136     private final Condition pushPopCond;
137
138     /*
139      * Dummy runnable to wake up EDT from getNextEvent() after
140      push/pop is performed
141      */

142     private static final Runnable dummyRunnable = new Runnable() {
143         public void run() {
144         }
145     };
146
147     private EventDispatchThread dispatchThread;
148
149     private final ThreadGroup threadGroup =
150         Thread.currentThread().getThreadGroup();
151     private final ClassLoader classLoader =
152         Thread.currentThread().getContextClassLoader();
153
154     /*
155      * The time stamp of the last dispatched InputEvent or ActionEvent.
156      */

157     private long mostRecentEventTime = System.currentTimeMillis();
158
159     /*
160      * The time stamp of the last KeyEvent .
161      */

162     private long mostRecentKeyEventTime = System.currentTimeMillis();
163
164     /**
165      * The modifiers field of the current event, if the current event is an
166      * InputEvent or ActionEvent.
167      */

168     private WeakReference<AWTEvent> currentEvent;
169
170     /*
171      * Non-zero if a thread is waiting in getNextEvent(intfor an event of
172      * a particular ID to be posted to the queue.
173      */

174     private volatile int waitForID;
175
176     /*
177      * AppContext corresponding to the queue.
178      */

179     private final AppContext appContext;
180
181     private final String name = "AWT-EventQueue-" + threadInitNumber.getAndIncrement();
182
183     private FwDispatcher fwDispatcher;
184
185     private static volatile PlatformLogger eventLog;
186
187     private static final PlatformLogger getEventLog() {
188         if(eventLog == null) {
189             eventLog = PlatformLogger.getLogger("java.awt.event.EventQueue");
190         }
191         return eventLog;
192     }
193
194     private static boolean fxAppThreadIsDispatchThread;
195
196     static {
197         AWTAccessor.setEventQueueAccessor(
198             new AWTAccessor.EventQueueAccessor() {
199                 public Thread getDispatchThread(EventQueue eventQueue) {
200                     return eventQueue.getDispatchThread();
201                 }
202                 public boolean isDispatchThreadImpl(EventQueue eventQueue) {
203                     return eventQueue.isDispatchThreadImpl();
204                 }
205                 public void removeSourceEvents(EventQueue eventQueue,
206                                                Object source,
207                                                boolean removeAllEvents)
208                 {
209                     eventQueue.removeSourceEvents(source, removeAllEvents);
210                 }
211                 public boolean noEvents(EventQueue eventQueue) {
212                     return eventQueue.noEvents();
213                 }
214                 public void wakeup(EventQueue eventQueue, boolean isShutdown) {
215                     eventQueue.wakeup(isShutdown);
216                 }
217                 public void invokeAndWait(Object source, Runnable r)
218                     throws InterruptedException, InvocationTargetException
219                 {
220                     EventQueue.invokeAndWait(source, r);
221                 }
222                 public void setFwDispatcher(EventQueue eventQueue,
223                                             FwDispatcher dispatcher) {
224                     eventQueue.setFwDispatcher(dispatcher);
225                 }
226
227                 @Override
228                 public long getMostRecentEventTime(EventQueue eventQueue) {
229                     return eventQueue.getMostRecentEventTimeImpl();
230                 }
231             });
232         AccessController.doPrivileged(new PrivilegedAction<Object>() {
233             public Object run() {
234                 fxAppThreadIsDispatchThread =
235                         "true".equals(System.getProperty("javafx.embed.singleThread"));
236                 return null;
237             }
238         });
239     }
240
241     /**
242      * Initializes a new instance of {@code EventQueue}.
243      */

244     public EventQueue() {
245         for (int i = 0; i < NUM_PRIORITIES; i++) {
246             queues[i] = new Queue();
247         }
248         /*
249          * NOTE: if you ever have to start the associated event dispatch
250          * thread at this point, be aware of the following problem:
251          * If this EventQueue instance is created in
252          * SunToolkit.createNewAppContext() the started dispatch thread
253          * may call AppContext.getAppContext() before createNewAppContext()
254          * completes thus causing mess in thread group to appcontext mapping.
255          */

256
257         appContext = AppContext.getAppContext();
258         pushPopLock = (Lock)appContext.get(AppContext.EVENT_QUEUE_LOCK_KEY);
259         pushPopCond = (Condition)appContext.get(AppContext.EVENT_QUEUE_COND_KEY);
260     }
261
262     /**
263      * Posts a 1.1-style event to the {@code EventQueue}.
264      * If there is an existing event on the queue with the same ID
265      * and event source, the source {@code Component}'s
266      * {@code coalesceEvents} method will be called.
267      *
268      * @param theEvent an instance of {@code java.awt.AWTEvent},
269      *          or a subclass of it
270      * @throws NullPointerException if {@code theEvent} is {@code null}
271      */

272     public void postEvent(AWTEvent theEvent) {
273         SunToolkit.flushPendingEvents(appContext);
274         postEventPrivate(theEvent);
275     }
276
277     /**
278      * Posts a 1.1-style event to the {@code EventQueue}.
279      * If there is an existing event on the queue with the same ID
280      * and event source, the source {@code Component}'s
281      * {@code coalesceEvents} method will be called.
282      *
283      * @param theEvent an instance of {@code java.awt.AWTEvent},
284      *          or a subclass of it
285      */

286     private void postEventPrivate(AWTEvent theEvent) {
287         theEvent.isPosted = true;
288         pushPopLock.lock();
289         try {
290             if (nextQueue != null) {
291                 // Forward the event to the top of EventQueue stack
292                 nextQueue.postEventPrivate(theEvent);
293                 return;
294             }
295             if (dispatchThread == null) {
296                 if (theEvent.getSource() == AWTAutoShutdown.getInstance()) {
297                     return;
298                 } else {
299                     initDispatchThread();
300                 }
301             }
302             postEvent(theEvent, getPriority(theEvent));
303         } finally {
304             pushPopLock.unlock();
305         }
306     }
307
308     private static int getPriority(AWTEvent theEvent) {
309         if (theEvent instanceof PeerEvent) {
310             PeerEvent peerEvent = (PeerEvent)theEvent;
311             if ((peerEvent.getFlags() & PeerEvent.ULTIMATE_PRIORITY_EVENT) != 0) {
312                 return ULTIMATE_PRIORITY;
313             }
314             if ((peerEvent.getFlags() & PeerEvent.PRIORITY_EVENT) != 0) {
315                 return HIGH_PRIORITY;
316             }
317             if ((peerEvent.getFlags() & PeerEvent.LOW_PRIORITY_EVENT) != 0) {
318                 return LOW_PRIORITY;
319             }
320         }
321         int id = theEvent.getID();
322         if ((id >= PaintEvent.PAINT_FIRST) && (id <= PaintEvent.PAINT_LAST)) {
323             return LOW_PRIORITY;
324         }
325         return NORM_PRIORITY;
326     }
327
328     /**
329      * Posts the event to the internal Queue of specified priority,
330      * coalescing as appropriate.
331      *
332      * @param theEvent an instance of {@code java.awt.AWTEvent},
333      *          or a subclass of it
334      * @param priority  the desired priority of the event
335      */

336     private void postEvent(AWTEvent theEvent, int priority) {
337         if (coalesceEvent(theEvent, priority)) {
338             return;
339         }
340
341         EventQueueItem newItem = new EventQueueItem(theEvent);
342
343         cacheEQItem(newItem);
344
345         boolean notifyID = (theEvent.getID() == this.waitForID);
346
347         if (queues[priority].head == null) {
348             boolean shouldNotify = noEvents();
349             queues[priority].head = queues[priority].tail = newItem;
350
351             if (shouldNotify) {
352                 if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
353                     AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
354                 }
355                 pushPopCond.signalAll();
356             } else if (notifyID) {
357                 pushPopCond.signalAll();
358             }
359         } else {
360             // The event was not coalesced or has non-Component source.
361             // Insert it at the end of the appropriate Queue.
362             queues[priority].tail.next = newItem;
363             queues[priority].tail = newItem;
364             if (notifyID) {
365                 pushPopCond.signalAll();
366             }
367         }
368     }
369
370     private boolean coalescePaintEvent(PaintEvent e) {
371         ComponentPeer sourcePeer = ((Component)e.getSource()).peer;
372         if (sourcePeer != null) {
373             sourcePeer.coalescePaintEvent(e);
374         }
375         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
376         if (cache == null) {
377             return false;
378         }
379         int index = eventToCacheIndex(e);
380
381         if (index != -1 && cache[index] != null) {
382             PaintEvent merged = mergePaintEvents(e, (PaintEvent)cache[index].event);
383             if (merged != null) {
384                 cache[index].event = merged;
385                 return true;
386             }
387         }
388         return false;
389     }
390
391     private PaintEvent mergePaintEvents(PaintEvent a, PaintEvent b) {
392         Rectangle aRect = a.getUpdateRect();
393         Rectangle bRect = b.getUpdateRect();
394         if (bRect.contains(aRect)) {
395             return b;
396         }
397         if (aRect.contains(bRect)) {
398             return a;
399         }
400         return null;
401     }
402
403     private boolean coalesceMouseEvent(MouseEvent e) {
404         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
405         if (cache == null) {
406             return false;
407         }
408         int index = eventToCacheIndex(e);
409         if (index != -1 && cache[index] != null) {
410             cache[index].event = e;
411             return true;
412         }
413         return false;
414     }
415
416     private boolean coalescePeerEvent(PeerEvent e) {
417         EventQueueItem[] cache = ((Component)e.getSource()).eventCache;
418         if (cache == null) {
419             return false;
420         }
421         int index = eventToCacheIndex(e);
422         if (index != -1 && cache[index] != null) {
423             e = e.coalesceEvents((PeerEvent)cache[index].event);
424             if (e != null) {
425                 cache[index].event = e;
426                 return true;
427             } else {
428                 cache[index] = null;
429             }
430         }
431         return false;
432     }
433
434     /*
435      * Should avoid of calling this method by any means
436      * as it's working time is dependent on EQ length.
437      * In the worst case this method alone can slow down the entire application
438      * 10 times by stalling the Event processing.
439      * Only here by backward compatibility reasons.
440      */

441     private boolean coalesceOtherEvent(AWTEvent e, int priority) {
442         int id = e.getID();
443         Component source = (Component)e.getSource();
444         for (EventQueueItem entry = queues[priority].head;
445             entry != null; entry = entry.next)
446         {
447             // Give Component.coalesceEvents a chance
448             if (entry.event.getSource() == source && entry.event.getID() == id) {
449                 AWTEvent coalescedEvent = source.coalesceEvents(
450                     entry.event, e);
451                 if (coalescedEvent != null) {
452                     entry.event = coalescedEvent;
453                     return true;
454                 }
455             }
456         }
457         return false;
458     }
459
460     private boolean coalesceEvent(AWTEvent e, int priority) {
461         if (!(e.getSource() instanceof Component)) {
462             return false;
463         }
464         if (e instanceof PeerEvent) {
465             return coalescePeerEvent((PeerEvent)e);
466         }
467         // The worst case
468         if (((Component)e.getSource()).isCoalescingEnabled()
469             && coalesceOtherEvent(e, priority))
470         {
471             return true;
472         }
473         if (e instanceof PaintEvent) {
474             return coalescePaintEvent((PaintEvent)e);
475         }
476         if (e instanceof MouseEvent) {
477             return coalesceMouseEvent((MouseEvent)e);
478         }
479         return false;
480     }
481
482     private void cacheEQItem(EventQueueItem entry) {
483         int index = eventToCacheIndex(entry.event);
484         if (index != -1 && entry.event.getSource() instanceof Component) {
485             Component source = (Component)entry.event.getSource();
486             if (source.eventCache == null) {
487                 source.eventCache = new EventQueueItem[CACHE_LENGTH];
488             }
489             source.eventCache[index] = entry;
490         }
491     }
492
493     private void uncacheEQItem(EventQueueItem entry) {
494         int index = eventToCacheIndex(entry.event);
495         if (index != -1 && entry.event.getSource() instanceof Component) {
496             Component source = (Component)entry.event.getSource();
497             if (source.eventCache == null) {
498                 return;
499             }
500             source.eventCache[index] = null;
501         }
502     }
503
504     private static final int PAINT = 0;
505     private static final int UPDATE = 1;
506     private static final int MOVE = 2;
507     private static final int DRAG = 3;
508     private static final int PEER = 4;
509     private static final int CACHE_LENGTH = 5;
510
511     private static int eventToCacheIndex(AWTEvent e) {
512         switch(e.getID()) {
513         case PaintEvent.PAINT:
514             return PAINT;
515         case PaintEvent.UPDATE:
516             return UPDATE;
517         case MouseEvent.MOUSE_MOVED:
518             return MOVE;
519         case MouseEvent.MOUSE_DRAGGED:
520             // Return -1 for SunDropTargetEvent since they are usually synchronous
521             // and we don't want to skip them by coalescing with MouseEvent or other drag events
522             return e instanceof SunDropTargetEvent ? -1 : DRAG;
523         default:
524             return e instanceof PeerEvent ? PEER : -1;
525         }
526     }
527
528     /**
529      * Returns whether an event is pending on any of the separate
530      * Queues.
531      * @return whether an event is pending on any of the separate Queues
532      */

533     private boolean noEvents() {
534         for (int i = 0; i < NUM_PRIORITIES; i++) {
535             if (queues[i].head != null) {
536                 return false;
537             }
538         }
539
540         return true;
541     }
542
543     /**
544      * Removes an event from the {@code EventQueue} and
545      * returns it.  This method will block until an event has
546      * been posted by another thread.
547      * @return the next {@code AWTEvent}
548      * @exception InterruptedException
549      *            if any thread has interrupted this thread
550      */

551     public AWTEvent getNextEvent() throws InterruptedException {
552         do {
553             /*
554              * SunToolkit.flushPendingEvents must be called outside
555              * of the synchronized block to avoid deadlock when
556              * event queues are nested with push()/pop().
557              */

558             SunToolkit.flushPendingEvents(appContext);
559             pushPopLock.lock();
560             try {
561                 AWTEvent event = getNextEventPrivate();
562                 if (event != null) {
563                     return event;
564                 }
565                 AWTAutoShutdown.getInstance().notifyThreadFree(dispatchThread);
566                 pushPopCond.await();
567             } finally {
568                 pushPopLock.unlock();
569             }
570         } while(true);
571     }
572
573     /*
574      * Must be called under the lock. Doesn't call flushPendingEvents()
575      */

576     AWTEvent getNextEventPrivate() throws InterruptedException {
577         for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
578             if (queues[i].head != null) {
579                 EventQueueItem entry = queues[i].head;
580                 queues[i].head = entry.next;
581                 if (entry.next == null) {
582                     queues[i].tail = null;
583                 }
584                 uncacheEQItem(entry);
585                 return entry.event;
586             }
587         }
588         return null;
589     }
590
591     AWTEvent getNextEvent(int id) throws InterruptedException {
592         do {
593             /*
594              * SunToolkit.flushPendingEvents must be called outside
595              * of the synchronized block to avoid deadlock when
596              * event queues are nested with push()/pop().
597              */

598             SunToolkit.flushPendingEvents(appContext);
599             pushPopLock.lock();
600             try {
601                 for (int i = 0; i < NUM_PRIORITIES; i++) {
602                     for (EventQueueItem entry = queues[i].head, prev = null;
603                          entry != null; prev = entry, entry = entry.next)
604                     {
605                         if (entry.event.getID() == id) {
606                             if (prev == null) {
607                                 queues[i].head = entry.next;
608                             } else {
609                                 prev.next = entry.next;
610                             }
611                             if (queues[i].tail == entry) {
612                                 queues[i].tail = prev;
613                             }
614                             uncacheEQItem(entry);
615                             return entry.event;
616                         }
617                     }
618                 }
619                 waitForID = id;
620                 pushPopCond.await();
621                 waitForID = 0;
622             } finally {
623                 pushPopLock.unlock();
624             }
625         } while(true);
626     }
627
628     /**
629      * Returns the first event on the {@code EventQueue}
630      * without removing it.
631      * @return the first event
632      */

633     public AWTEvent peekEvent() {
634         pushPopLock.lock();
635         try {
636             for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
637                 if (queues[i].head != null) {
638                     return queues[i].head.event;
639                 }
640             }
641         } finally {
642             pushPopLock.unlock();
643         }
644
645         return null;
646     }
647
648     /**
649      * Returns the first event with the specified id, if any.
650      * @param id the id of the type of event desired
651      * @return the first event of the specified id or {@code null}
652      *    if there is no such event
653      */

654     public AWTEvent peekEvent(int id) {
655         pushPopLock.lock();
656         try {
657             for (int i = NUM_PRIORITIES - 1; i >= 0; i--) {
658                 EventQueueItem q = queues[i].head;
659                 for (; q != null; q = q.next) {
660                     if (q.event.getID() == id) {
661                         return q.event;
662                     }
663                 }
664             }
665         } finally {
666             pushPopLock.unlock();
667         }
668
669         return null;
670     }
671
672     private static final JavaSecurityAccess javaSecurityAccess =
673         SharedSecrets.getJavaSecurityAccess();
674
675     /**
676      * Dispatches an event. The manner in which the event is
677      * dispatched depends upon the type of the event and the
678      * type of the event's source object:
679      *
680      * <table class="striped">
681      * <caption>Event types, source types, and dispatch methods</caption>
682      * <thead>
683      *   <tr>
684      *     <th scope="col">Event Type
685      *     <th scope="col">Source Type
686      *     <th scope="col">Dispatched To
687      * </thead>
688      * <tbody>
689      *   <tr>
690      *     <th scope="row">ActiveEvent
691      *     <td>Any
692      *     <td>event.dispatch()
693      *   <tr>
694      *     <th scope="row">Other
695      *     <td>Component
696      *     <td>source.dispatchEvent(AWTEvent)
697      *   <tr>
698      *     <th scope="row">Other
699      *     <td>MenuComponent
700      *     <td>source.dispatchEvent(AWTEvent)
701      *   <tr>
702      *     <th scope="row">Other
703      *     <td>Other
704      *     <td>No action (ignored)
705      * </tbody>
706      * </table>
707      *
708      * @param event an instance of {@code java.awt.AWTEvent},
709      *          or a subclass of it
710      * @throws NullPointerException if {@code event} is {@code null}
711      * @since           1.2
712      */

713     protected void dispatchEvent(final AWTEvent event) {
714         final Object src = event.getSource();
715         final PrivilegedAction<Void> action = new PrivilegedAction<Void>() {
716             public Void run() {
717                 // In case fwDispatcher is installed and we're already on the
718                 // dispatch thread (e.g. performing DefaultKeyboardFocusManager.sendMessage),
719                 // dispatch the event straight away.
720                 if (fwDispatcher == null || isDispatchThreadImpl()) {
721                     dispatchEventImpl(event, src);
722                 } else {
723                     fwDispatcher.scheduleDispatch(new Runnable() {
724                         @Override
725                         public void run() {
726                             if (dispatchThread.filterAndCheckEvent(event)) {
727                                 dispatchEventImpl(event, src);
728                             }
729                         }
730                     });
731                 }
732                 return null;
733             }
734         };
735
736         final AccessControlContext stack = AccessController.getContext();
737         final AccessControlContext srcAcc = getAccessControlContextFrom(src);
738         final AccessControlContext eventAcc = event.getAccessControlContext();
739         if (srcAcc == null) {
740             javaSecurityAccess.doIntersectionPrivilege(action, stack, eventAcc);
741         } else {
742             javaSecurityAccess.doIntersectionPrivilege(
743                 new PrivilegedAction<Void>() {
744                     public Void run() {
745                         javaSecurityAccess.doIntersectionPrivilege(action, eventAcc);
746                         return null;
747                     }
748                 }, stack, srcAcc);
749         }
750     }
751
752     private static AccessControlContext getAccessControlContextFrom(Object src) {
753         return src instanceof Component ?
754             ((Component)src).getAccessControlContext() :
755             src instanceof MenuComponent ?
756                 ((MenuComponent)src).getAccessControlContext() :
757                 src instanceof TrayIcon ?
758                     ((TrayIcon)src).getAccessControlContext() :
759                     null;
760     }
761
762     /**
763      * Called from dispatchEvent() under a correct AccessControlContext
764      */

765     private void dispatchEventImpl(final AWTEvent event, final Object src) {
766         event.isPosted = true;
767         if (event instanceof ActiveEvent) {
768             // This could become the sole method of dispatching in time.
769             setCurrentEventAndMostRecentTimeImpl(event);
770             ((ActiveEvent)event).dispatch();
771         } else if (src instanceof Component) {
772             ((Component)src).dispatchEvent(event);
773             event.dispatched();
774         } else if (src instanceof MenuComponent) {
775             ((MenuComponent)src).dispatchEvent(event);
776         } else if (src instanceof TrayIcon) {
777             ((TrayIcon)src).dispatchEvent(event);
778         } else if (src instanceof AWTAutoShutdown) {
779             if (noEvents()) {
780                 dispatchThread.stopDispatching();
781             }
782         } else {
783             if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
784                 getEventLog().fine("Unable to dispatch event: " + event);
785             }
786         }
787     }
788
789     /**
790      * Returns the timestamp of the most recent event that had a timestamp, and
791      * that was dispatched from the {@code EventQueue} associated with the
792      * calling thread. If an event with a timestamp is currently being
793      * dispatched, its timestamp will be returned. If no events have yet
794      * been dispatched, the EventQueue's initialization time will be
795      * returned instead.In the current version of
796      * the JDK, only {@code InputEvent}s,
797      * {@code ActionEvent}s, and {@code InvocationEvent}s have
798      * timestamps; however, future versions of the JDK may add timestamps to
799      * additional event types. Note that this method should only be invoked
800      * from an application's {@link #isDispatchThread event dispatching thread}.
801      * If this method is
802      * invoked from another thread, the current system time (as reported by
803      * {@code System.currentTimeMillis()}) will be returned instead.
804      *
805      * @return the timestamp of the last {@code InputEvent},
806      *         {@code ActionEvent}, or {@code InvocationEvent} to be
807      *         dispatched, or {@code System.currentTimeMillis()} if this
808      *         method is invoked on a thread other than an event dispatching
809      *         thread
810      * @see java.awt.event.InputEvent#getWhen
811      * @see java.awt.event.ActionEvent#getWhen
812      * @see java.awt.event.InvocationEvent#getWhen
813      * @see #isDispatchThread
814      *
815      * @since 1.4
816      */

817     public static long getMostRecentEventTime() {
818         return Toolkit.getEventQueue().getMostRecentEventTimeImpl();
819     }
820     private long getMostRecentEventTimeImpl() {
821         pushPopLock.lock();
822         try {
823             return (Thread.currentThread() == dispatchThread)
824                 ? mostRecentEventTime
825                 : System.currentTimeMillis();
826         } finally {
827             pushPopLock.unlock();
828         }
829     }
830
831     /**
832      * @return most recent event time on all threads.
833      */

834     long getMostRecentEventTimeEx() {
835         pushPopLock.lock();
836         try {
837             return mostRecentEventTime;
838         } finally {
839             pushPopLock.unlock();
840         }
841     }
842
843     /**
844      * Returns the event currently being dispatched by the
845      * {@code EventQueue} associated with the calling thread. This is
846      * useful if a method needs access to the event, but was not designed to
847      * receive a reference to it as an argument. Note that this method should
848      * only be invoked from an application's event dispatching thread. If this
849      * method is invoked from another thread, null will be returned.
850      *
851      * @return the event currently being dispatched, or null if this method is
852      *         invoked on a thread other than an event dispatching thread
853      * @since 1.4
854      */

855     public static AWTEvent getCurrentEvent() {
856         return Toolkit.getEventQueue().getCurrentEventImpl();
857     }
858     private AWTEvent getCurrentEventImpl() {
859         pushPopLock.lock();
860         try {
861             if (Thread.currentThread() == dispatchThread
862                     || fxAppThreadIsDispatchThread) {
863                 return (currentEvent != null)
864                         ? currentEvent.get()
865                         : null;
866             }
867             return null;
868         } finally {
869             pushPopLock.unlock();
870         }
871     }
872
873     /**
874      * Replaces the existing {@code EventQueue} with the specified one.
875      * Any pending events are transferred to the new {@code EventQueue}
876      * for processing by it.
877      *
878      * @param newEventQueue an {@code EventQueue}
879      *          (or subclass thereof) instance to be use
880      * @see      java.awt.EventQueue#pop
881      * @throws NullPointerException if {@code newEventQueue} is {@code null}
882      * @since           1.2
883      */

884     public void push(EventQueue newEventQueue) {
885         if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
886             getEventLog().fine("EventQueue.push(" + newEventQueue + ")");
887         }
888
889         pushPopLock.lock();
890         try {
891             EventQueue topQueue = this;
892             while (topQueue.nextQueue != null) {
893                 topQueue = topQueue.nextQueue;
894             }
895             if (topQueue.fwDispatcher != null) {
896                 throw new RuntimeException("push() to queue with fwDispatcher");
897             }
898             if ((topQueue.dispatchThread != null) &&
899                 (topQueue.dispatchThread.getEventQueue() == this))
900             {
901                 newEventQueue.dispatchThread = topQueue.dispatchThread;
902                 topQueue.dispatchThread.setEventQueue(newEventQueue);
903             }
904
905             // Transfer all events forward to new EventQueue.
906             while (topQueue.peekEvent() != null) {
907                 try {
908                     // Use getNextEventPrivate() as it doesn't call flushPendingEvents()
909                     newEventQueue.postEventPrivate(topQueue.getNextEventPrivate());
910                 } catch (InterruptedException ie) {
911                     if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
912                         getEventLog().fine("Interrupted push", ie);
913                     }
914                 }
915             }
916
917             if (topQueue.dispatchThread != null) {
918                 // Wake up EDT waiting in getNextEvent(), so it can
919                 // pick up a new EventQueue. Post the waking event before
920                 // topQueue.nextQueue is assigned, otherwise the event would
921                 // go newEventQueue
922                 topQueue.postEventPrivate(new InvocationEvent(topQueue, dummyRunnable));
923             }
924
925             newEventQueue.previousQueue = topQueue;
926             topQueue.nextQueue = newEventQueue;
927
928             if (appContext.get(AppContext.EVENT_QUEUE_KEY) == topQueue) {
929                 appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue);
930             }
931
932             pushPopCond.signalAll();
933         } finally {
934             pushPopLock.unlock();
935         }
936     }
937
938     /**
939      * Stops dispatching events using this {@code EventQueue}.
940      * Any pending events are transferred to the previous
941      * {@code EventQueue} for processing.
942      * <p>
943      * Warning: To avoid deadlock, do not declare this method
944      * synchronized in a subclass.
945      *
946      * @exception EmptyStackException if no previous push was made
947      *  on this {@code EventQueue}
948      * @see      java.awt.EventQueue#push
949      * @since           1.2
950      */

951     protected void pop() throws EmptyStackException {
952         if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
953             getEventLog().fine("EventQueue.pop(" + this + ")");
954         }
955
956         pushPopLock.lock();
957         try {
958             EventQueue topQueue = this;
959             while (topQueue.nextQueue != null) {
960                 topQueue = topQueue.nextQueue;
961             }
962             EventQueue prevQueue = topQueue.previousQueue;
963             if (prevQueue == null) {
964                 throw new EmptyStackException();
965             }
966
967             topQueue.previousQueue = null;
968             prevQueue.nextQueue = null;
969
970             // Transfer all events back to previous EventQueue.
971             while (topQueue.peekEvent() != null) {
972                 try {
973                     prevQueue.postEventPrivate(topQueue.getNextEventPrivate());
974                 } catch (InterruptedException ie) {
975                     if (getEventLog().isLoggable(PlatformLogger.Level.FINE)) {
976                         getEventLog().fine("Interrupted pop", ie);
977                     }
978                 }
979             }
980
981             if ((topQueue.dispatchThread != null) &&
982                 (topQueue.dispatchThread.getEventQueue() == this))
983             {
984                 prevQueue.dispatchThread = topQueue.dispatchThread;
985                 topQueue.dispatchThread.setEventQueue(prevQueue);
986             }
987
988             if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) {
989                 appContext.put(AppContext.EVENT_QUEUE_KEY, prevQueue);
990             }
991
992             // Wake up EDT waiting in getNextEvent(), so it can
993             // pick up a new EventQueue
994             topQueue.postEventPrivate(new InvocationEvent(topQueue, dummyRunnable));
995
996             pushPopCond.signalAll();
997         } finally {
998             pushPopLock.unlock();
999         }
1000     }
1001
1002     /**
1003      * Creates a new {@code secondary loop} associated with this
1004      * event queue. Use the {@link SecondaryLoop#enter} and
1005      * {@link SecondaryLoop#exit} methods to start and stop the
1006      * event loop and dispatch the events from this queue.
1007      *
1008      * @return secondaryLoop A new secondary loop object, which can
1009      *                       be used to launch a new nested event
1010      *                       loop and dispatch events from this queue
1011      *
1012      * @see SecondaryLoop#enter
1013      * @see SecondaryLoop#exit
1014      *
1015      * @since 1.7
1016      */

1017     public SecondaryLoop createSecondaryLoop() {
1018         return createSecondaryLoop(nullnull, 0);
1019     }
1020
1021     private class FwSecondaryLoopWrapper implements SecondaryLoop {
1022         final private SecondaryLoop loop;
1023         final private EventFilter filter;
1024
1025         public FwSecondaryLoopWrapper(SecondaryLoop loop, EventFilter filter) {
1026             this.loop = loop;
1027             this.filter = filter;
1028         }
1029
1030         @Override
1031         public boolean enter() {
1032             if (filter != null) {
1033                 dispatchThread.addEventFilter(filter);
1034             }
1035             return loop.enter();
1036         }
1037
1038         @Override
1039         public boolean exit() {
1040             if (filter != null) {
1041                 dispatchThread.removeEventFilter(filter);
1042             }
1043             return loop.exit();
1044         }
1045     }
1046
1047     SecondaryLoop createSecondaryLoop(Conditional cond, EventFilter filter, long interval) {
1048         pushPopLock.lock();
1049         try {
1050             if (nextQueue != null) {
1051                 // Forward the request to the top of EventQueue stack
1052                 return nextQueue.createSecondaryLoop(cond, filter, interval);
1053             }
1054             if (fwDispatcher != null) {
1055                 return new FwSecondaryLoopWrapper(fwDispatcher.createSecondaryLoop(), filter);
1056             }
1057             if (dispatchThread == null) {
1058                 initDispatchThread();
1059             }
1060             return new WaitDispatchSupport(dispatchThread, cond, filter, interval);
1061         } finally {
1062             pushPopLock.unlock();
1063         }
1064     }
1065
1066     /**
1067      * Returns true if the calling thread is
1068      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1069      * dispatch thread. Use this method to ensure that a particular
1070      * task is being executed (or not being) there.
1071      * <p>
1072      * Note: use the {@link #invokeLater} or {@link #invokeAndWait}
1073      * methods to execute a task in
1074      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1075      * dispatch thread.
1076      *
1077      * @return true if running in
1078      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
1079      * dispatch thread
1080      * @see             #invokeLater
1081      * @see             #invokeAndWait
1082      * @see             Toolkit#getSystemEventQueue
1083      * @since           1.2
1084      */

1085     public static boolean isDispatchThread() {
1086         EventQueue eq = Toolkit.getEventQueue();
1087         return eq.isDispatchThreadImpl();
1088     }
1089
1090     final boolean isDispatchThreadImpl() {
1091         EventQueue eq = this;
1092         pushPopLock.lock();
1093         try {
1094             EventQueue next = eq.nextQueue;
1095             while (next != null) {
1096                 eq = next;
1097                 next = eq.nextQueue;
1098             }
1099             if (eq.fwDispatcher != null) {
1100                 return eq.fwDispatcher.isDispatchThread();
1101             }
1102             return (Thread.currentThread() == eq.dispatchThread);
1103         } finally {
1104             pushPopLock.unlock();
1105         }
1106     }
1107
1108     final void initDispatchThread() {
1109         pushPopLock.lock();
1110         try {
1111             if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) {
1112                 dispatchThread = AccessController.doPrivileged(
1113                     new PrivilegedAction<EventDispatchThread>() {
1114                         public EventDispatchThread run() {
1115                             EventDispatchThread t =
1116                                 new EventDispatchThread(threadGroup,
1117                                                         name,
1118                                                         EventQueue.this);
1119                             t.setContextClassLoader(classLoader);
1120                             t.setPriority(Thread.NORM_PRIORITY + 1);
1121                             t.setDaemon(false);
1122                             AWTAutoShutdown.getInstance().notifyThreadBusy(t);
1123                             return t;
1124                         }
1125                     }
1126                 );
1127                 dispatchThread.start();
1128             }
1129         } finally {
1130             pushPopLock.unlock();
1131         }
1132     }
1133
1134     final void detachDispatchThread(EventDispatchThread edt) {
1135         /*
1136          * Minimize discard possibility for non-posted events
1137          */

1138         SunToolkit.flushPendingEvents(appContext);
1139         /*
1140          * This synchronized block is to secure that the event dispatch
1141          * thread won't die in the middle of posting a new event to the
1142          * associated event queue. It is important because we notify
1143          * that the event dispatch thread is busy after posting a new event
1144          * to its queue, so the EventQueue.dispatchThread reference must
1145          * be valid at that point.
1146          */

1147         pushPopLock.lock();
1148         try {
1149             if (edt == dispatchThread) {
1150                 dispatchThread = null;
1151             }
1152             AWTAutoShutdown.getInstance().notifyThreadFree(edt);
1153             /*
1154              * Event was posted after EDT events pumping had stopped, so start
1155              * another EDT to handle this event
1156              */

1157             if (peekEvent() != null) {
1158                 initDispatchThread();
1159             }
1160         } finally {
1161             pushPopLock.unlock();
1162         }
1163     }
1164
1165     /*
1166      * Gets the {@code EventDispatchThread} for this
1167      * {@code EventQueue}.
1168      * @return the event dispatch thread associated with this event queue
1169      *         or {@code nullif this event queue doesn't have a
1170      *         working thread associated with it
1171      * @see    java.awt.EventQueue#initDispatchThread
1172      * @see    java.awt.EventQueue#detachDispatchThread
1173      */

1174     final EventDispatchThread getDispatchThread() {
1175         pushPopLock.lock();
1176         try {
1177             return dispatchThread;
1178         } finally {
1179             pushPopLock.unlock();
1180         }
1181     }
1182
1183     /*
1184      * Removes any pending events for the specified source object.
1185      * If removeAllEvents parameter is {@code true} then all
1186      * events for the specified source object are removed, if it
1187      * is {@code false} then {@code SequencedEvent}, {@code SentEvent},
1188      * {@code FocusEvent}, {@code WindowEvent}, {@code KeyEvent},
1189      * and {@code InputMethodEvent} are kept in the queue, but all other
1190      * events are removed.
1191      *
1192      * This method is normally called by the source's
1193      * {@code removeNotify} method.
1194      */

1195     final void removeSourceEvents(Object source, boolean removeAllEvents) {
1196         SunToolkit.flushPendingEvents(appContext);
1197         pushPopLock.lock();
1198         try {
1199             for (int i = 0; i < NUM_PRIORITIES; i++) {
1200                 EventQueueItem entry = queues[i].head;
1201                 EventQueueItem prev = null;
1202                 while (entry != null) {
1203                     if ((entry.event.getSource() == source)
1204                         && (removeAllEvents
1205                             || ! (entry.event instanceof SequencedEvent
1206                                   || entry.event instanceof SentEvent
1207                                   || entry.event instanceof FocusEvent
1208                                   || entry.event instanceof WindowEvent
1209                                   || entry.event instanceof KeyEvent
1210                                   || entry.event instanceof InputMethodEvent)))
1211                     {
1212                         if (entry.event instanceof SequencedEvent) {
1213                             ((SequencedEvent)entry.event).dispose();
1214                         }
1215                         if (entry.event instanceof SentEvent) {
1216                             ((SentEvent)entry.event).dispose();
1217                         }
1218                         if (entry.event instanceof InvocationEvent) {
1219                             AWTAccessor.getInvocationEventAccessor()
1220                                     .dispose((InvocationEvent)entry.event);
1221                         }
1222                         if (entry.event instanceof SunDropTargetEvent) {
1223                             ((SunDropTargetEvent)entry.event).dispose();
1224                         }
1225                         if (prev == null) {
1226                             queues[i].head = entry.next;
1227                         } else {
1228                             prev.next = entry.next;
1229                         }
1230                         uncacheEQItem(entry);
1231                     } else {
1232                         prev = entry;
1233                     }
1234                     entry = entry.next;
1235                 }
1236                 queues[i].tail = prev;
1237             }
1238         } finally {
1239             pushPopLock.unlock();
1240         }
1241     }
1242
1243     synchronized long getMostRecentKeyEventTime() {
1244         pushPopLock.lock();
1245         try {
1246             return mostRecentKeyEventTime;
1247         } finally {
1248             pushPopLock.unlock();
1249         }
1250     }
1251
1252     static void setCurrentEventAndMostRecentTime(AWTEvent e) {
1253         Toolkit.getEventQueue().setCurrentEventAndMostRecentTimeImpl(e);
1254     }
1255     private void setCurrentEventAndMostRecentTimeImpl(AWTEvent e) {
1256         pushPopLock.lock();
1257         try {
1258             if (!fxAppThreadIsDispatchThread && Thread.currentThread() != dispatchThread) {
1259                 return;
1260             }
1261
1262             currentEvent = new WeakReference<>(e);
1263
1264             // This series of 'instanceof' checks should be replaced with a
1265             // polymorphic type (for example, an interface which declares a
1266             // getWhen() method). However, this would require us to make such
1267             // a type public, or to place it in sun.awt. Both of these approaches
1268             // have been frowned upon. So for now, we hack.
1269             //
1270             // In tiger, we will probably give timestamps to all events, so this
1271             // will no longer be an issue.
1272             long mostRecentEventTime2 = Long.MIN_VALUE;
1273             if (e instanceof InputEvent) {
1274                 InputEvent ie = (InputEvent)e;
1275                 mostRecentEventTime2 = ie.getWhen();
1276                 if (e instanceof KeyEvent) {
1277                     mostRecentKeyEventTime = ie.getWhen();
1278                 }
1279             } else if (e instanceof InputMethodEvent) {
1280                 InputMethodEvent ime = (InputMethodEvent)e;
1281                 mostRecentEventTime2 = ime.getWhen();
1282             } else if (e instanceof ActionEvent) {
1283                 ActionEvent ae = (ActionEvent)e;
1284                 mostRecentEventTime2 = ae.getWhen();
1285             } else if (e instanceof InvocationEvent) {
1286                 InvocationEvent ie = (InvocationEvent)e;
1287                 mostRecentEventTime2 = ie.getWhen();
1288             }
1289             mostRecentEventTime = Math.max(mostRecentEventTime, mostRecentEventTime2);
1290         } finally {
1291             pushPopLock.unlock();
1292         }
1293     }
1294
1295     /**
1296      * Causes {@code runnable} to have its {@code run}
1297      * method called in the {@link #isDispatchThread dispatch thread} of
1298      * {@link Toolkit#getSystemEventQueue the system EventQueue}.
1299      * This will happen after all pending events are processed.
1300      *
1301      * @param runnable  the {@code Runnable} whose {@code run}
1302      *                  method should be executed
1303      *                  asynchronously in the
1304      *                  {@link #isDispatchThread event dispatch thread}
1305      *                  of {@link Toolkit#getSystemEventQueue the system EventQueue}
1306      * @see             #invokeAndWait
1307      * @see             Toolkit#getSystemEventQueue
1308      * @see             #isDispatchThread
1309      * @since           1.2
1310      */

1311     public static void invokeLater(Runnable runnable) {
1312         Toolkit.getEventQueue().postEvent(
1313             new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
1314     }
1315
1316     /**
1317      * Causes {@code runnable} to have its {@code run}
1318      * method called in the {@link #isDispatchThread dispatch thread} of
1319      * {@link Toolkit#getSystemEventQueue the system EventQueue}.
1320      * This will happen after all pending events are processed.
1321      * The call blocks until this has happened.  This method
1322      * will throw an Error if called from the
1323      * {@link #isDispatchThread event dispatcher thread}.
1324      *
1325      * @param runnable  the {@code Runnable} whose {@code run}
1326      *                  method should be executed
1327      *                  synchronously in the
1328      *                  {@link #isDispatchThread event dispatch thread}
1329      *                  of {@link Toolkit#getSystemEventQueue the system EventQueue}
1330      * @exception       InterruptedException  if any thread has
1331      *                  interrupted this thread
1332      * @exception       InvocationTargetException  if an throwable is thrown
1333      *                  when running {@code runnable}
1334      * @see             #invokeLater
1335      * @see             Toolkit#getSystemEventQueue
1336      * @see             #isDispatchThread
1337      * @since           1.2
1338      */

1339     public static void invokeAndWait(Runnable runnable)
1340         throws InterruptedException, InvocationTargetException
1341     {
1342         invokeAndWait(Toolkit.getDefaultToolkit(), runnable);
1343     }
1344
1345     static void invokeAndWait(Object source, Runnable runnable)
1346         throws InterruptedException, InvocationTargetException
1347     {
1348         if (EventQueue.isDispatchThread()) {
1349             throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
1350         }
1351
1352         class AWTInvocationLock {}
1353         Object lock = new AWTInvocationLock();
1354
1355         InvocationEvent event =
1356             new InvocationEvent(source, runnable, lock, true);
1357
1358         synchronized (lock) {
1359             Toolkit.getEventQueue().postEvent(event);
1360             while (!event.isDispatched()) {
1361                 lock.wait();
1362             }
1363         }
1364
1365         Throwable eventThrowable = event.getThrowable();
1366         if (eventThrowable != null) {
1367             throw new InvocationTargetException(eventThrowable);
1368         }
1369     }
1370
1371     /*
1372      * Called from PostEventQueue.postEvent to notify that a new event
1373      * appeared. First it proceeds to the EventQueue on the top of the
1374      * stack, then notifies the associated dispatch thread if it exists
1375      * or starts a new one otherwise.
1376      */

1377     private void wakeup(boolean isShutdown) {
1378         pushPopLock.lock();
1379         try {
1380             if (nextQueue != null) {
1381                 // Forward call to the top of EventQueue stack.
1382                 nextQueue.wakeup(isShutdown);
1383             } else if (dispatchThread != null) {
1384                 pushPopCond.signalAll();
1385             } else if (!isShutdown) {
1386                 initDispatchThread();
1387             }
1388         } finally {
1389             pushPopLock.unlock();
1390         }
1391     }
1392
1393     // The method is used by AWTAccessor for javafx/AWT single threaded mode.
1394     private void setFwDispatcher(FwDispatcher dispatcher) {
1395         if (nextQueue != null) {
1396             nextQueue.setFwDispatcher(dispatcher);
1397         } else {
1398             fwDispatcher = dispatcher;
1399         }
1400     }
1401 }
1402
1403 /**
1404  * The Queue object holds pointers to the beginning and end of one internal
1405  * queue. An EventQueue object is composed of multiple internal Queues, one
1406  * for each priority supported by the EventQueue. All Events on a particular
1407  * internal Queue have identical priority.
1408  */

1409 class Queue {
1410     EventQueueItem head;
1411     EventQueueItem tail;
1412 }
1413