/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.user.client; import com.google.gwt.core.client.Duration; import com.google.gwt.junit.client.GWTTestCase; /** * Tests {@link Timer#cancel()} functionality. */ public class TimerCancelTest extends GWTTestCase { private static final class CountingTimer extends Timer { private int timerCount; @Override public void run() { timerCount++; } } @Override public String getModuleName() { return "com.google.gwt.user.UserTest"; } public void testCancelTimer() { final CountingTimer canceledTimer = new CountingTimer(); Timer cancelingTimer = new Timer() { @Override public void run() { assertEquals(0, canceledTimer.timerCount); canceledTimer.cancel(); } }; cancelingTimer.schedule(50); canceledTimer.schedule(100); busyWait(200); delayTestFinish(500); new Timer() { @Override public void run() { assertEquals(0, canceledTimer.timerCount); finishTest(); } }.schedule(300); } public void testRestartTimer() { final CountingTimer restartedTimer = new CountingTimer(); Timer cancelingTimer = new Timer() { @Override public void run() { assertEquals(0, restartedTimer.timerCount); restartedTimer.cancel(); restartedTimer.schedule(100); } }; cancelingTimer.schedule(50); restartedTimer.schedule(100); busyWait(200); delayTestFinish(500); new Timer() { @Override public void run() { assertEquals(1, restartedTimer.timerCount); finishTest(); } }.schedule(400); } private static void busyWait(double duration) { /* * It seems that IE adds an event to the javascript event loop immediately when a timer expires * (supposedly from a separate thread). After this has happened, canceling the timer has no * effect because it is already in the queue and no further checks are done when running the * event once it reaches the head of the queue. * * This means that to trigger the bug, we must ensure the timer has been added to the event loop * queue before it is canceled. To ensure this happens, we will busy wait until both timers * should have fired. This means the following happens: * * 1) While busy waiting, IE adds events for each timer to the event loop * * 2) IE pumps the event loop, running the helper timer that cancels the tested timer. This does * however not have any effect because the timer is already in the event loop queue. * * 3) IE pumps the event loop again and runs the event for the tested timer, without realizing * that it has been canceled. * * Without busy waiting, the tested timer would not yet have been added to the event loop queue * at the point when the timer is canceled, in which case canceling the timer would work as * expected. * * If the two timers are not scheduled in the same order that they will run, it seems that IE * does some additional checks that makes the problem disappear. */ double start = Duration.currentTimeMillis(); while (Duration.currentTimeMillis() - start <= duration) { // Busy wait } } }