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 "WebCoreInstanceHandle.h"
32#include "Widget.h"
33#include <wtf/Assertions.h>
34#include <wtf/CurrentTime.h>
35
36#include "WindowsExtras.h"
37#include <mmsystem.h>
38
39#if PLATFORM(WIN)
40#include "PluginView.h"
41#endif
42
43// These aren't in winuser.h with the MSVS 2003 Platform SDK,
44// so use default values in that case.
45#ifndef USER_TIMER_MINIMUM
46#define USER_TIMER_MINIMUM 0x0000000A
47#endif
48
49#ifndef USER_TIMER_MAXIMUM
50#define USER_TIMER_MAXIMUM 0x7FFFFFFF
51#endif
52
53#ifndef QS_RAWINPUT
54#define QS_RAWINPUT         0x0400
55#endif
56
57namespace WebCore {
58
59static UINT timerID;
60static void (*sharedTimerFiredFunction)();
61
62static HANDLE timer;
63static HWND timerWindowHandle = 0;
64const LPCWSTR kTimerWindowClassName = L"TimerWindowClass";
65
66#if !OS(WINCE)
67static UINT timerFiredMessage = 0;
68static HANDLE timerQueue;
69static bool highResTimerActive;
70static bool processingCustomTimerMessage = false;
71static LONG pendingTimers;
72
73const int timerResolution = 1; // To improve timer resolution, we call timeBeginPeriod/timeEndPeriod with this value to increase timer resolution to 1ms.
74const int highResolutionThresholdMsec = 16; // Only activate high-res timer for sub-16ms timers (Windows can fire timers at 16ms intervals without changing the system resolution).
75const 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).
76#endif
77
78enum {
79    sharedTimerID = 1000,
80    endHighResTimerID = 1001,
81};
82
83LRESULT CALLBACK TimerWindowWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
84{
85#if PLATFORM(WIN)
86    // Windows Media Player has a modal message loop that will deliver messages
87    // to us at inappropriate times and we will crash if we handle them when
88    // they are delivered. We repost all messages so that we will get to handle
89    // them once the modal loop exits.
90    if (PluginView::isCallingPlugin()) {
91        PostMessage(hWnd, message, wParam, lParam);
92        return 0;
93    }
94#endif
95
96    if (message == WM_TIMER) {
97        if (wParam == sharedTimerID) {
98            KillTimer(timerWindowHandle, sharedTimerID);
99            sharedTimerFiredFunction();
100        }
101#if !OS(WINCE)
102        else if (wParam == endHighResTimerID) {
103            KillTimer(timerWindowHandle, endHighResTimerID);
104            highResTimerActive = false;
105            timeEndPeriod(timerResolution);
106        }
107    } else if (message == timerFiredMessage) {
108        InterlockedExchange(&pendingTimers, 0);
109        processingCustomTimerMessage = true;
110        sharedTimerFiredFunction();
111        processingCustomTimerMessage = false;
112#endif
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#if OS(WINCE)
125    WNDCLASS wcex;
126    memset(&wcex, 0, sizeof(WNDCLASS));
127#else
128    WNDCLASSEX wcex;
129    memset(&wcex, 0, sizeof(WNDCLASSEX));
130    wcex.cbSize = sizeof(WNDCLASSEX);
131#endif
132
133    wcex.lpfnWndProc    = TimerWindowWndProc;
134    wcex.hInstance      = WebCore::instanceHandle();
135    wcex.lpszClassName  = kTimerWindowClassName;
136#if OS(WINCE)
137    RegisterClass(&wcex);
138#else
139    RegisterClassEx(&wcex);
140#endif
141
142    timerWindowHandle = CreateWindow(kTimerWindowClassName, 0, 0,
143       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, HWND_MESSAGE, 0, WebCore::instanceHandle(), 0);
144
145#if !OS(WINCE)
146    timerFiredMessage = RegisterWindowMessage(L"com.apple.WebKit.TimerFired");
147#endif
148}
149
150void setSharedTimerFiredFunction(void (*f)())
151{
152    sharedTimerFiredFunction = f;
153}
154
155#if !OS(WINCE)
156static void NTAPI queueTimerProc(PVOID, BOOLEAN)
157{
158    if (InterlockedIncrement(&pendingTimers) == 1)
159        PostMessage(timerWindowHandle, timerFiredMessage, 0, 0);
160}
161#endif
162
163void setSharedTimerFireInterval(double interval)
164{
165    ASSERT(sharedTimerFiredFunction);
166
167    unsigned intervalInMS;
168    interval *= 1000;
169    if (interval > USER_TIMER_MAXIMUM)
170        intervalInMS = USER_TIMER_MAXIMUM;
171    else
172        intervalInMS = static_cast<unsigned>(interval);
173
174    initializeOffScreenTimerWindow();
175    bool timerSet = false;
176
177#if !OS(WINCE)
178    if (Settings::shouldUseHighResolutionTimers()) {
179        if (interval < highResolutionThresholdMsec) {
180            if (!highResTimerActive) {
181                highResTimerActive = true;
182                timeBeginPeriod(timerResolution);
183            }
184            SetTimer(timerWindowHandle, endHighResTimerID, stopHighResTimerInMsec, 0);
185        }
186
187        DWORD queueStatus = LOWORD(GetQueueStatus(QS_PAINT | QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT));
188
189        // Win32 has a tri-level queue with application messages > user input > WM_PAINT/WM_TIMER.
190
191        // If the queue doesn't contains input events, we use a higher priorty timer event posting mechanism.
192        if (!(queueStatus & (QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT))) {
193            if (intervalInMS < USER_TIMER_MINIMUM && !processingCustomTimerMessage && !(queueStatus & QS_PAINT)) {
194                // Call PostMessage immediately if the timer is already expired, unless a paint is pending.
195                // (we prioritize paints over timers)
196                if (InterlockedIncrement(&pendingTimers) == 1)
197                    PostMessage(timerWindowHandle, timerFiredMessage, 0, 0);
198                timerSet = true;
199            } else {
200                // Otherwise, delay the PostMessage via a CreateTimerQueueTimer
201                if (!timerQueue)
202                    timerQueue = CreateTimerQueue();
203                if (timer)
204                    DeleteTimerQueueTimer(timerQueue, timer, 0);
205                timerSet = CreateTimerQueueTimer(&timer, timerQueue, queueTimerProc, 0, intervalInMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
206            }
207        }
208    }
209#endif // !OS(WINCE)
210
211    if (timerSet) {
212        if (timerID) {
213            KillTimer(timerWindowHandle, timerID);
214            timerID = 0;
215        }
216    } else {
217        timerID = SetTimer(timerWindowHandle, sharedTimerID, intervalInMS, 0);
218        timer = 0;
219    }
220}
221
222void stopSharedTimer()
223{
224#if !OS(WINCE)
225    if (timerQueue && timer) {
226        DeleteTimerQueueTimer(timerQueue, timer, 0);
227        timer = 0;
228    }
229#endif
230
231    if (timerID) {
232        KillTimer(timerWindowHandle, timerID);
233        timerID = 0;
234    }
235}
236
237}
238