ipc/chromium/src/base/event_recorder.cc

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/ipc/chromium/src/base/event_recorder.cc	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,259 @@
     1.4 +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
     1.5 +// Use of this source code is governed by a BSD-style license that can be
     1.6 +// found in the LICENSE file.
     1.7 +
     1.8 +#include "build/build_config.h"
     1.9 +
    1.10 +#include <windows.h>
    1.11 +#include <mmsystem.h>
    1.12 +
    1.13 +#include "base/event_recorder.h"
    1.14 +#include "base/file_util.h"
    1.15 +#include "base/logging.h"
    1.16 +
    1.17 +// A note about time.
    1.18 +// For perfect playback of events, you'd like a very accurate timer
    1.19 +// so that events are played back at exactly the same time that
    1.20 +// they were recorded.  However, windows has a clock which is only
    1.21 +// granular to ~15ms.  We see more consistent event playback when
    1.22 +// using a higher resolution timer.  To do this, we use the
    1.23 +// timeGetTime API instead of the default GetTickCount() API.
    1.24 +
    1.25 +namespace base {
    1.26 +
    1.27 +EventRecorder* EventRecorder::current_ = NULL;
    1.28 +
    1.29 +LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam,
    1.30 +                                     LPARAM lParam) {
    1.31 +  CHECK(EventRecorder::current());
    1.32 +  return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam);
    1.33 +}
    1.34 +
    1.35 +LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam,
    1.36 +                                       LPARAM lParam) {
    1.37 +  CHECK(EventRecorder::current());
    1.38 +  return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam);
    1.39 +}
    1.40 +
    1.41 +EventRecorder::~EventRecorder() {
    1.42 +  // Try to assert early if the caller deletes the recorder
    1.43 +  // while it is still in use.
    1.44 +  DCHECK(!journal_hook_);
    1.45 +  DCHECK(!is_recording_ && !is_playing_);
    1.46 +}
    1.47 +
    1.48 +bool EventRecorder::StartRecording(const FilePath& filename) {
    1.49 +  if (journal_hook_ != NULL)
    1.50 +    return false;
    1.51 +  if (is_recording_ || is_playing_)
    1.52 +    return false;
    1.53 +
    1.54 +  // Open the recording file.
    1.55 +  DCHECK(file_ == NULL);
    1.56 +  file_ = file_util::OpenFile(filename, "wb+");
    1.57 +  if (!file_) {
    1.58 +    DLOG(ERROR) << "EventRecorder could not open log file";
    1.59 +    return false;
    1.60 +  }
    1.61 +
    1.62 +  // Set the faster clock, if possible.
    1.63 +  ::timeBeginPeriod(1);
    1.64 +
    1.65 +  // Set the recording hook.  JOURNALRECORD can only be used as a global hook.
    1.66 +  journal_hook_ = ::SetWindowsHookEx(WH_JOURNALRECORD, StaticRecordWndProc,
    1.67 +                                     GetModuleHandle(NULL), 0);
    1.68 +  if (!journal_hook_) {
    1.69 +    DLOG(ERROR) << "EventRecorder Record Hook failed";
    1.70 +    file_util::CloseFile(file_);
    1.71 +    return false;
    1.72 +  }
    1.73 +
    1.74 +  is_recording_ = true;
    1.75 +  return true;
    1.76 +}
    1.77 +
    1.78 +void EventRecorder::StopRecording() {
    1.79 +  if (is_recording_) {
    1.80 +    DCHECK(journal_hook_ != NULL);
    1.81 +
    1.82 +    if (!::UnhookWindowsHookEx(journal_hook_)) {
    1.83 +      DLOG(ERROR) << "EventRecorder Unhook failed";
    1.84 +      // Nothing else we can really do here.
    1.85 +      return;
    1.86 +    }
    1.87 +
    1.88 +    ::timeEndPeriod(1);
    1.89 +
    1.90 +    DCHECK(file_ != NULL);
    1.91 +    file_util::CloseFile(file_);
    1.92 +    file_ = NULL;
    1.93 +
    1.94 +    journal_hook_ = NULL;
    1.95 +    is_recording_ = false;
    1.96 +  }
    1.97 +}
    1.98 +
    1.99 +bool EventRecorder::StartPlayback(const FilePath& filename) {
   1.100 +  if (journal_hook_ != NULL)
   1.101 +    return false;
   1.102 +  if (is_recording_ || is_playing_)
   1.103 +    return false;
   1.104 +
   1.105 +  // Open the recording file.
   1.106 +  DCHECK(file_ == NULL);
   1.107 +  file_ = file_util::OpenFile(filename, "rb");
   1.108 +  if (!file_) {
   1.109 +    DLOG(ERROR) << "EventRecorder Playback could not open log file";
   1.110 +    return false;
   1.111 +  }
   1.112 +  // Read the first event from the record.
   1.113 +  if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) {
   1.114 +    DLOG(ERROR) << "EventRecorder Playback has no records!";
   1.115 +    file_util::CloseFile(file_);
   1.116 +    return false;
   1.117 +  }
   1.118 +
   1.119 +  // Set the faster clock, if possible.
   1.120 +  ::timeBeginPeriod(1);
   1.121 +
   1.122 +  // Playback time is tricky.  When playing back, we read a series of events,
   1.123 +  // each with timeouts.  Simply subtracting the delta between two timers will
   1.124 +  // lead to fast playback (about 2x speed).  The API has two events, one
   1.125 +  // which advances to the next event (HC_SKIP), and another that requests the
   1.126 +  // event (HC_GETNEXT).  The same event will be requested multiple times.
   1.127 +  // Each time the event is requested, we must calculate the new delay.
   1.128 +  // To do this, we track the start time of the playback, and constantly
   1.129 +  // re-compute the delay.   I mention this only because I saw two examples
   1.130 +  // of how to use this code on the net, and both were broken :-)
   1.131 +  playback_start_time_ = timeGetTime();
   1.132 +  playback_first_msg_time_ = playback_msg_.time;
   1.133 +
   1.134 +  // Set the hook.  JOURNALPLAYBACK can only be used as a global hook.
   1.135 +  journal_hook_ = ::SetWindowsHookEx(WH_JOURNALPLAYBACK, StaticPlaybackWndProc,
   1.136 +                                     GetModuleHandle(NULL), 0);
   1.137 +  if (!journal_hook_) {
   1.138 +    DLOG(ERROR) << "EventRecorder Playback Hook failed";
   1.139 +    return false;
   1.140 +  }
   1.141 +
   1.142 +  is_playing_ = true;
   1.143 +
   1.144 +  return true;
   1.145 +}
   1.146 +
   1.147 +void EventRecorder::StopPlayback() {
   1.148 +  if (is_playing_) {
   1.149 +    DCHECK(journal_hook_ != NULL);
   1.150 +
   1.151 +    if (!::UnhookWindowsHookEx(journal_hook_)) {
   1.152 +      DLOG(ERROR) << "EventRecorder Unhook failed";
   1.153 +      // Nothing else we can really do here.
   1.154 +    }
   1.155 +
   1.156 +    DCHECK(file_ != NULL);
   1.157 +    file_util::CloseFile(file_);
   1.158 +    file_ = NULL;
   1.159 +
   1.160 +    ::timeEndPeriod(1);
   1.161 +
   1.162 +    journal_hook_ = NULL;
   1.163 +    is_playing_ = false;
   1.164 +  }
   1.165 +}
   1.166 +
   1.167 +// Windows callback hook for the recorder.
   1.168 +LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
   1.169 +  static bool recording_enabled = true;
   1.170 +  EVENTMSG* msg_ptr = NULL;
   1.171 +
   1.172 +  // The API says we have to do this.
   1.173 +  // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx
   1.174 +  if (nCode < 0)
   1.175 +    return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
   1.176 +
   1.177 +  // Check for the break key being pressed and stop recording.
   1.178 +  if (::GetKeyState(VK_CANCEL) & 0x8000) {
   1.179 +    StopRecording();
   1.180 +    return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
   1.181 +  }
   1.182 +
   1.183 +  // The Journal Recorder must stop recording events when system modal
   1.184 +  // dialogs are present. (see msdn link above)
   1.185 +  switch(nCode) {
   1.186 +    case HC_SYSMODALON:
   1.187 +      recording_enabled = false;
   1.188 +      break;
   1.189 +    case HC_SYSMODALOFF:
   1.190 +      recording_enabled = true;
   1.191 +      break;
   1.192 +  }
   1.193 +
   1.194 +  if (nCode == HC_ACTION && recording_enabled) {
   1.195 +    // Aha - we have an event to record.
   1.196 +    msg_ptr = reinterpret_cast<EVENTMSG*>(lParam);
   1.197 +    msg_ptr->time = timeGetTime();
   1.198 +    fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_);
   1.199 +    fflush(file_);
   1.200 +  }
   1.201 +
   1.202 +  return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
   1.203 +}
   1.204 +
   1.205 +// Windows callback for the playback mode.
   1.206 +LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam,
   1.207 +                                       LPARAM lParam) {
   1.208 +  static bool playback_enabled = true;
   1.209 +  int delay = 0;
   1.210 +
   1.211 +  switch(nCode) {
   1.212 +    // A system modal dialog box is being displayed.  Stop playing back
   1.213 +    // messages.
   1.214 +    case HC_SYSMODALON:
   1.215 +      playback_enabled = false;
   1.216 +      break;
   1.217 +
   1.218 +    // A system modal dialog box is destroyed.  We can start playing back
   1.219 +    // messages again.
   1.220 +    case HC_SYSMODALOFF:
   1.221 +      playback_enabled = true;
   1.222 +      break;
   1.223 +
   1.224 +    // Prepare to copy the next mouse or keyboard event to playback.
   1.225 +    case HC_SKIP:
   1.226 +      if (!playback_enabled)
   1.227 +        break;
   1.228 +
   1.229 +      // Read the next event from the record.
   1.230 +      if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1)
   1.231 +        this->StopPlayback();
   1.232 +      break;
   1.233 +
   1.234 +    // Copy the mouse or keyboard event to the EVENTMSG structure in lParam.
   1.235 +    case HC_GETNEXT:
   1.236 +      if (!playback_enabled)
   1.237 +        break;
   1.238 +
   1.239 +      memcpy(reinterpret_cast<void*>(lParam), &playback_msg_,
   1.240 +             sizeof(playback_msg_));
   1.241 +
   1.242 +      // The return value is the amount of time (in milliseconds) to wait
   1.243 +      // before playing back the next message in the playback queue.  Each
   1.244 +      // time this is called, we recalculate the delay relative to our current
   1.245 +      // wall clock.
   1.246 +      delay = (playback_msg_.time - playback_first_msg_time_) -
   1.247 +              (timeGetTime() - playback_start_time_);
   1.248 +      if (delay < 0)
   1.249 +        delay = 0;
   1.250 +      return delay;
   1.251 +
   1.252 +    // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE
   1.253 +    // indicating that the message is not removed from the message queue after
   1.254 +    // PeekMessage processing.
   1.255 +    case HC_NOREMOVE:
   1.256 +      break;
   1.257 +  }
   1.258 +
   1.259 +  return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
   1.260 +}
   1.261 +
   1.262 +}  // namespace base

mercurial