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