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