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 64 namespace WebCore { 65 66 static UINT timerID; 67 static void (*sharedTimerFiredFunction)(); 68 69 static HWND timerWindowHandle = 0; 70 static UINT timerFiredMessage = 0; 71 static HANDLE timerQueue; 72 static HANDLE timer; 73 static bool highResTimerActive; 74 static bool processingCustomTimerMessage = false; 75 static LONG pendingTimers; 76 77 const LPCWSTR kTimerWindowClassName = L"TimerWindowClass"; 78 const int timerResolution = 1; // To improve timer resolution, we call timeBeginPeriod/timeEndPeriod with this value to increase timer resolution to 1ms. 79 const int highResolutionThresholdMsec = 16; // Only activate high-res timer for sub-16ms timers (Windows can fire timers at 16ms intervals without changing the system resolution). 80 const 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 82 enum { 83 sharedTimerID = 1000, 84 endHighResTimerID = 1001, 85 }; 86 87 LRESULT 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 120 static 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 138 void setSharedTimerFiredFunction(void (*f)()) 139 { 140 sharedTimerFiredFunction = f; 141 } 142 143 static void NTAPI queueTimerProc(PVOID, BOOLEAN) 144 { 145 if (InterlockedIncrement(&pendingTimers) == 1) 146 PostMessage(timerWindowHandle, timerFiredMessage, 0, 0); 147 } 148 149 void 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 211 void 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