1/*
2 * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SharedTimer.h"
28
29#include "Page.h"
30#include "Settings.h"
31#include "Widget.h"
32#include <wtf/Assertions.h>
33#include <wtf/CurrentTime.h>
34
35// Note: wx headers set defines that affect the configuration of windows.h
36// so we must include the wx header first to get unicode versions of functions,
37// etc.
38#if PLATFORM(WX)
39#include <wx/wx.h>
40#endif
41
42#include <windows.h>
43#include <mmsystem.h>
44
45#if PLATFORM(WIN)
46#include "PluginView.h"
47#endif
48
49// These aren't in winuser.h with the MSVS 2003 Platform SDK,
50// so use default values in that case.
51#ifndef USER_TIMER_MINIMUM
52#define USER_TIMER_MINIMUM 0x0000000A
53#endif
54
55#ifndef USER_TIMER_MAXIMUM
56#define USER_TIMER_MAXIMUM 0x7FFFFFFF
57#endif
58
59#ifndef QS_RAWINPUT
60#define QS_RAWINPUT         0x0400
61#endif
62
63namespace WebCore {
64
65static UINT timerID;
66static void (*sharedTimerFiredFunction)();
67
68static HWND timerWindowHandle = 0;
69static UINT timerFiredMessage = 0;
70static HANDLE timerQueue;
71static HANDLE timer;
72static bool highResTimerActive;
73static bool processingCustomTimerMessage = false;
74static LONG pendingTimers;
75
76const LPCWSTR kTimerWindowClassName = L"TimerWindowClass";
77const int timerResolution = 1; // To improve timer resolution, we call timeBeginPeriod/timeEndPeriod with this value to increase timer resolution to 1ms.
78const int highResolutionThresholdMsec = 16; // Only activate high-res timer for sub-16ms timers (Windows can fire timers at 16ms intervals without changing the system resolution).
79const int stopHighResTimerInMsec = 300; // Stop high-res timer after 0.3 seconds to lessen power consumption (we don't use a smaller time since oscillating between high and low resolution breaks timer accuracy on XP).
80
81enum {
82    sharedTimerID = 1000,
83    endHighResTimerID = 1001,
84};
85
86LRESULT CALLBACK TimerWindowWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
87{
88#if PLATFORM(WIN)
89    // Windows Media Player has a modal message loop that will deliver messages
90    // to us at inappropriate times and we will crash if we handle them when
91    // they are delivered. We repost all messages so that we will get to handle
92    // them once the modal loop exits.
93    if (PluginView::isCallingPlugin()) {
94        PostMessage(hWnd, message, wParam, lParam);
95        return 0;
96    }
97#endif
98
99    if (message == timerFiredMessage) {
100        InterlockedExchange(&pendingTimers, 0);
101        processingCustomTimerMessage = true;
102        sharedTimerFiredFunction();
103        processingCustomTimerMessage = false;
104    } else if (message == WM_TIMER) {
105        if (wParam == sharedTimerID) {
106            KillTimer(timerWindowHandle, sharedTimerID);
107            sharedTimerFiredFunction();
108        } else if (wParam == endHighResTimerID) {
109            KillTimer(timerWindowHandle, endHighResTimerID);
110            highResTimerActive = false;
111            timeEndPeriod(timerResolution);
112        }
113    } else
114        return DefWindowProc(hWnd, message, wParam, lParam);
115
116    return 0;
117}
118
119static void initializeOffScreenTimerWindow()
120{
121    if (timerWindowHandle)
122        return;
123
124    WNDCLASSEX wcex;
125    memset(&wcex, 0, sizeof(WNDCLASSEX));
126    wcex.cbSize = sizeof(WNDCLASSEX);
127    wcex.lpfnWndProc    = TimerWindowWndProc;
128    wcex.hInstance      = Page::instanceHandle();
129    wcex.lpszClassName  = kTimerWindowClassName;
130    RegisterClassEx(&wcex);
131
132    timerWindowHandle = CreateWindow(kTimerWindowClassName, 0, 0,
133       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, HWND_MESSAGE, 0, Page::instanceHandle(), 0);
134    timerFiredMessage = RegisterWindowMessage(L"com.apple.WebKit.TimerFired");
135}
136
137void setSharedTimerFiredFunction(void (*f)())
138{
139    sharedTimerFiredFunction = f;
140}
141
142static void NTAPI queueTimerProc(PVOID, BOOLEAN)
143{
144    if (InterlockedIncrement(&pendingTimers) == 1)
145        PostMessage(timerWindowHandle, timerFiredMessage, 0, 0);
146}
147
148void setSharedTimerFireTime(double fireTime)
149{
150    ASSERT(sharedTimerFiredFunction);
151
152    double interval = fireTime - currentTime();
153    unsigned intervalInMS;
154    if (interval < 0)
155        intervalInMS = 0;
156    else {
157        interval *= 1000;
158        if (interval > USER_TIMER_MAXIMUM)
159            intervalInMS = USER_TIMER_MAXIMUM;
160        else
161            intervalInMS = (unsigned)interval;
162    }
163
164    initializeOffScreenTimerWindow();
165    bool timerSet = false;
166
167    if (Settings::shouldUseHighResolutionTimers()) {
168        if (interval < highResolutionThresholdMsec) {
169            if (!highResTimerActive) {
170                highResTimerActive = true;
171                timeBeginPeriod(timerResolution);
172            }
173            SetTimer(timerWindowHandle, endHighResTimerID, stopHighResTimerInMsec, 0);
174        }
175
176        DWORD queueStatus = LOWORD(GetQueueStatus(QS_PAINT | QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT));
177
178        // Win32 has a tri-level queue with application messages > user input > WM_PAINT/WM_TIMER.
179
180        // If the queue doesn't contains input events, we use a higher priorty timer event posting mechanism.
181        if (!(queueStatus & (QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT))) {
182            if (intervalInMS < USER_TIMER_MINIMUM && !processingCustomTimerMessage && !(queueStatus & QS_PAINT)) {
183                // Call PostMessage immediately if the timer is already expired, unless a paint is pending.
184                // (we prioritize paints over timers)
185                if (InterlockedIncrement(&pendingTimers) == 1)
186                    PostMessage(timerWindowHandle, timerFiredMessage, 0, 0);
187                timerSet = true;
188            } else {
189                // Otherwise, delay the PostMessage via a CreateTimerQueueTimer
190                if (!timerQueue)
191                    timerQueue = CreateTimerQueue();
192                if (timer)
193                    DeleteTimerQueueTimer(timerQueue, timer, 0);
194                timerSet = CreateTimerQueueTimer(&timer, timerQueue, queueTimerProc, 0, intervalInMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
195            }
196        }
197    }
198
199    if (timerSet) {
200        if (timerID) {
201            KillTimer(timerWindowHandle, timerID);
202            timerID = 0;
203        }
204    } else {
205        timerID = SetTimer(timerWindowHandle, sharedTimerID, intervalInMS, 0);
206        timer = 0;
207    }
208}
209
210void stopSharedTimer()
211{
212    if (timerQueue && timer) {
213        DeleteTimerQueueTimer(timerQueue, timer, 0);
214        timer = 0;
215    }
216
217    if (timerID) {
218        KillTimer(timerWindowHandle, timerID);
219        timerID = 0;
220    }
221}
222
223}
224