1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/gamepad_shared_memory_reader.h"
6
7#include "base/debug/trace_event.h"
8#include "base/metrics/histogram.h"
9#include "content/common/gamepad_hardware_buffer.h"
10#include "content/common/gamepad_user_gesture.h"
11#include "content/public/renderer/render_thread.h"
12#include "content/renderer/renderer_webkitplatformsupport_impl.h"
13#include "ipc/ipc_sync_message_filter.h"
14#include "third_party/WebKit/public/platform/WebGamepadListener.h"
15
16namespace content {
17
18GamepadSharedMemoryReader::GamepadSharedMemoryReader(
19    RendererWebKitPlatformSupportImpl* webkit_platform_support)
20    : gamepad_hardware_buffer_(NULL),
21      gamepad_listener_(NULL),
22      is_polling_(false),
23      ever_interacted_with_(false) {
24  webkit_platform_support->set_gamepad_provider(this);
25}
26
27void GamepadSharedMemoryReader::StartPollingIfNecessary() {
28  if (is_polling_)
29    return;
30
31  CHECK(RenderThread::Get()->Send(new GamepadHostMsg_StartPolling(
32      &renderer_shared_memory_handle_)));
33
34  // If we don't get a valid handle from the browser, don't try to Map (we're
35  // probably out of memory or file handles).
36  bool valid_handle = base::SharedMemory::IsHandleValid(
37      renderer_shared_memory_handle_);
38  UMA_HISTOGRAM_BOOLEAN("Gamepad.ValidSharedMemoryHandle", valid_handle);
39  if (!valid_handle)
40    return;
41
42  renderer_shared_memory_.reset(
43      new base::SharedMemory(renderer_shared_memory_handle_, true));
44  CHECK(renderer_shared_memory_->Map(sizeof(GamepadHardwareBuffer)));
45  void *memory = renderer_shared_memory_->memory();
46  CHECK(memory);
47  gamepad_hardware_buffer_ =
48      static_cast<GamepadHardwareBuffer*>(memory);
49
50  is_polling_ = true;
51}
52
53void GamepadSharedMemoryReader::StopPollingIfNecessary() {
54  if (is_polling_) {
55    RenderThread::Get()->Send(new GamepadHostMsg_StopPolling());
56    is_polling_ = false;
57  }
58}
59
60void GamepadSharedMemoryReader::SampleGamepads(blink::WebGamepads& gamepads) {
61  // Blink should set the listener before start sampling.
62  CHECK(gamepad_listener_);
63
64  StartPollingIfNecessary();
65  if (!is_polling_)
66    return;
67
68  // ==========
69  //   DANGER
70  // ==========
71  //
72  // This logic is duplicated in Pepper as well. If you change it, that also
73  // needs to be in sync. See ppapi/proxy/gamepad_resource.cc.
74  blink::WebGamepads read_into;
75  TRACE_EVENT0("GAMEPAD", "SampleGamepads");
76
77  if (!base::SharedMemory::IsHandleValid(renderer_shared_memory_handle_))
78    return;
79
80  // Only try to read this many times before failing to avoid waiting here
81  // very long in case of contention with the writer. TODO(scottmg) Tune this
82  // number (as low as 1?) if histogram shows distribution as mostly
83  // 0-and-maximum.
84  const int kMaximumContentionCount = 10;
85  int contention_count = -1;
86  base::subtle::Atomic32 version;
87  do {
88    version = gamepad_hardware_buffer_->sequence.ReadBegin();
89    memcpy(&read_into, &gamepad_hardware_buffer_->buffer, sizeof(read_into));
90    ++contention_count;
91    if (contention_count == kMaximumContentionCount)
92      break;
93  } while (gamepad_hardware_buffer_->sequence.ReadRetry(version));
94  UMA_HISTOGRAM_COUNTS("Gamepad.ReadContentionCount", contention_count);
95
96  if (contention_count >= kMaximumContentionCount) {
97    // We failed to successfully read, presumably because the hardware
98    // thread was taking unusually long. Don't copy the data to the output
99    // buffer, and simply leave what was there before.
100    return;
101  }
102
103  // New data was read successfully, copy it into the output buffer.
104  memcpy(&gamepads, &read_into, sizeof(gamepads));
105
106  if (!ever_interacted_with_) {
107    // Clear the connected flag if the user hasn't interacted with any of the
108    // gamepads to prevent fingerprinting. The actual data is not cleared.
109    // WebKit will only copy out data into the JS buffers for connected
110    // gamepads so this is sufficient.
111    for (unsigned i = 0; i < blink::WebGamepads::itemsLengthCap; i++)
112      gamepads.items[i].connected = false;
113  }
114}
115
116void GamepadSharedMemoryReader::SetGamepadListener(
117    blink::WebGamepadListener* listener) {
118  gamepad_listener_ = listener;
119  if (gamepad_listener_) {
120    // Polling has to be started rigth now and not just on the first sampling
121    // because want to get connection events from now.
122    StartPollingIfNecessary();
123  } else {
124    StopPollingIfNecessary();
125  }
126}
127
128GamepadSharedMemoryReader::~GamepadSharedMemoryReader() {
129  StopPollingIfNecessary();
130}
131
132bool GamepadSharedMemoryReader::OnControlMessageReceived(
133    const IPC::Message& message) {
134  bool handled = true;
135  IPC_BEGIN_MESSAGE_MAP(GamepadSharedMemoryReader, message)
136    IPC_MESSAGE_HANDLER(GamepadMsg_GamepadConnected, OnGamepadConnected)
137    IPC_MESSAGE_HANDLER(GamepadMsg_GamepadDisconnected, OnGamepadDisconnected)
138    IPC_MESSAGE_UNHANDLED(handled = false)
139  IPC_END_MESSAGE_MAP()
140  return handled;
141}
142
143void GamepadSharedMemoryReader::OnGamepadConnected(
144    int index,
145    const blink::WebGamepad& gamepad) {
146  // The browser already checks if the user actually interacted with a device.
147  ever_interacted_with_ = true;
148
149  if (gamepad_listener_)
150    gamepad_listener_->didConnectGamepad(index, gamepad);
151}
152
153void GamepadSharedMemoryReader::OnGamepadDisconnected(
154    int index,
155    const blink::WebGamepad& gamepad) {
156  if (gamepad_listener_)
157    gamepad_listener_->didDisconnectGamepad(index, gamepad);
158}
159
160} // namespace content
161