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