зеркало из https://github.com/mozilla/gecko-dev.git
Import Chromium sources r15462
This commit is contained in:
Родитель
8bfb4369c2
Коммит
a64afe22b9
|
@ -0,0 +1,6 @@
|
|||
include_rules = [
|
||||
"+third_party/zlib",
|
||||
"+third_party/libevent",
|
||||
"+third_party/libjpeg",
|
||||
"+third_party/dmg_fp",
|
||||
]
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// Keep a stack of registered AtExitManagers. We always operate on the most
|
||||
// recent, and we should never have more than one outside of testing, when we
|
||||
// use the shadow version of the constructor. We don't protect this for
|
||||
// thread-safe access, since it will only be modified in testing.
|
||||
static AtExitManager* g_top_manager = NULL;
|
||||
|
||||
AtExitManager::AtExitManager() : next_manager_(NULL) {
|
||||
DCHECK(!g_top_manager);
|
||||
g_top_manager = this;
|
||||
}
|
||||
|
||||
AtExitManager::AtExitManager(bool shadow) : next_manager_(g_top_manager) {
|
||||
DCHECK(shadow || !g_top_manager);
|
||||
g_top_manager = this;
|
||||
}
|
||||
|
||||
AtExitManager::~AtExitManager() {
|
||||
if (!g_top_manager) {
|
||||
NOTREACHED() << "Tried to ~AtExitManager without an AtExitManager";
|
||||
return;
|
||||
}
|
||||
DCHECK(g_top_manager == this);
|
||||
|
||||
ProcessCallbacksNow();
|
||||
g_top_manager = next_manager_;
|
||||
}
|
||||
|
||||
// static
|
||||
void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param) {
|
||||
if (!g_top_manager) {
|
||||
NOTREACHED() << "Tried to RegisterCallback without an AtExitManager";
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK(func);
|
||||
|
||||
AutoLock lock(g_top_manager->lock_);
|
||||
g_top_manager->stack_.push(CallbackAndParam(func, param));
|
||||
}
|
||||
|
||||
// static
|
||||
void AtExitManager::ProcessCallbacksNow() {
|
||||
if (!g_top_manager) {
|
||||
NOTREACHED() << "Tried to ProcessCallbacksNow without an AtExitManager";
|
||||
return;
|
||||
}
|
||||
|
||||
AutoLock lock(g_top_manager->lock_);
|
||||
|
||||
while (!g_top_manager->stack_.empty()) {
|
||||
CallbackAndParam callback_and_param = g_top_manager->stack_.top();
|
||||
g_top_manager->stack_.pop();
|
||||
|
||||
callback_and_param.func_(callback_and_param.param_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_AT_EXIT_H_
|
||||
#define BASE_AT_EXIT_H_
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/lock.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// This class provides a facility similar to the CRT atexit(), except that
|
||||
// we control when the callbacks are executed. Under Windows for a DLL they
|
||||
// happen at a really bad time and under the loader lock. This facility is
|
||||
// mostly used by base::Singleton.
|
||||
//
|
||||
// The usage is simple. Early in the main() or WinMain() scope create an
|
||||
// AtExitManager object on the stack:
|
||||
// int main(...) {
|
||||
// base::AtExitManager exit_manager;
|
||||
//
|
||||
// }
|
||||
// When the exit_manager object goes out of scope, all the registered
|
||||
// callbacks and singleton destructors will be called.
|
||||
|
||||
class AtExitManager {
|
||||
protected:
|
||||
// This constructor will allow this instance of AtExitManager to be created
|
||||
// even if one already exists. This should only be used for testing!
|
||||
// AtExitManagers are kept on a global stack, and it will be removed during
|
||||
// destruction. This allows you to shadow another AtExitManager.
|
||||
AtExitManager(bool shadow);
|
||||
|
||||
public:
|
||||
typedef void (*AtExitCallbackType)(void*);
|
||||
|
||||
AtExitManager();
|
||||
|
||||
// The dtor calls all the registered callbacks. Do not try to register more
|
||||
// callbacks after this point.
|
||||
~AtExitManager();
|
||||
|
||||
// Registers the specified function to be called at exit. The prototype of
|
||||
// the callback function is void func().
|
||||
static void RegisterCallback(AtExitCallbackType func, void* param);
|
||||
|
||||
// Calls the functions registered with RegisterCallback in LIFO order. It
|
||||
// is possible to register new callbacks after calling this function.
|
||||
static void ProcessCallbacksNow();
|
||||
|
||||
private:
|
||||
struct CallbackAndParam {
|
||||
CallbackAndParam(AtExitCallbackType func, void* param)
|
||||
: func_(func), param_(param) { }
|
||||
AtExitCallbackType func_;
|
||||
void* param_;
|
||||
};
|
||||
|
||||
Lock lock_;
|
||||
std::stack<CallbackAndParam> stack_;
|
||||
AtExitManager* next_manager_; // Stack of managers to allow shadowing.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtExitManager);
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_AT_EXIT_H_
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/at_exit.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Don't test the global AtExitManager, because asking it to process its
|
||||
// AtExit callbacks can ruin the global state that other tests may depend on.
|
||||
class ShadowingAtExitManager : public base::AtExitManager {
|
||||
public:
|
||||
ShadowingAtExitManager() : AtExitManager(true) {}
|
||||
};
|
||||
|
||||
int g_test_counter_1 = 0;
|
||||
int g_test_counter_2 = 0;
|
||||
|
||||
void IncrementTestCounter1(void* unused) {
|
||||
++g_test_counter_1;
|
||||
}
|
||||
|
||||
void IncrementTestCounter2(void* unused) {
|
||||
++g_test_counter_2;
|
||||
}
|
||||
|
||||
void ZeroTestCounters() {
|
||||
g_test_counter_1 = 0;
|
||||
g_test_counter_2 = 0;
|
||||
}
|
||||
|
||||
void ExpectCounter1IsZero(void* unused) {
|
||||
EXPECT_EQ(0, g_test_counter_1);
|
||||
}
|
||||
|
||||
void ExpectParamIsNull(void* param) {
|
||||
EXPECT_EQ(static_cast<void*>(NULL), param);
|
||||
}
|
||||
|
||||
void ExpectParamIsCounter(void* param) {
|
||||
EXPECT_EQ(&g_test_counter_1, param);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(AtExitTest, Basic) {
|
||||
ShadowingAtExitManager shadowing_at_exit_manager;
|
||||
|
||||
ZeroTestCounters();
|
||||
base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
|
||||
base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL);
|
||||
base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
|
||||
|
||||
EXPECT_EQ(0, g_test_counter_1);
|
||||
EXPECT_EQ(0, g_test_counter_2);
|
||||
base::AtExitManager::ProcessCallbacksNow();
|
||||
EXPECT_EQ(2, g_test_counter_1);
|
||||
EXPECT_EQ(1, g_test_counter_2);
|
||||
}
|
||||
|
||||
TEST(AtExitTest, LIFOOrder) {
|
||||
ShadowingAtExitManager shadowing_at_exit_manager;
|
||||
|
||||
ZeroTestCounters();
|
||||
base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
|
||||
base::AtExitManager::RegisterCallback(&ExpectCounter1IsZero, NULL);
|
||||
base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL);
|
||||
|
||||
EXPECT_EQ(0, g_test_counter_1);
|
||||
EXPECT_EQ(0, g_test_counter_2);
|
||||
base::AtExitManager::ProcessCallbacksNow();
|
||||
EXPECT_EQ(1, g_test_counter_1);
|
||||
EXPECT_EQ(1, g_test_counter_2);
|
||||
}
|
||||
|
||||
TEST(AtExitTest, Param) {
|
||||
ShadowingAtExitManager shadowing_at_exit_manager;
|
||||
|
||||
base::AtExitManager::RegisterCallback(&ExpectParamIsNull, NULL);
|
||||
base::AtExitManager::RegisterCallback(&ExpectParamIsCounter,
|
||||
&g_test_counter_1);
|
||||
base::AtExitManager::ProcessCallbacksNow();
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This is a low level implementation of atomic semantics for reference
|
||||
// counting. Please use base/ref_counted.h directly instead.
|
||||
|
||||
#ifndef BASE_ATOMIC_REF_COUNT_H_
|
||||
#define BASE_ATOMIC_REF_COUNT_H_
|
||||
|
||||
#include "base/atomicops.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
typedef subtle::Atomic32 AtomicRefCount;
|
||||
|
||||
// Increment a reference count by "increment", which must exceed 0.
|
||||
inline void AtomicRefCountIncN(volatile AtomicRefCount *ptr,
|
||||
AtomicRefCount increment) {
|
||||
subtle::NoBarrier_AtomicIncrement(ptr, increment);
|
||||
}
|
||||
|
||||
// Decrement a reference count by "decrement", which must exceed 0,
|
||||
// and return whether the result is non-zero.
|
||||
// Insert barriers to ensure that state written before the reference count
|
||||
// became zero will be visible to a thread that has just made the count zero.
|
||||
inline bool AtomicRefCountDecN(volatile AtomicRefCount *ptr,
|
||||
AtomicRefCount decrement) {
|
||||
return subtle::Barrier_AtomicIncrement(ptr, -decrement) != 0;
|
||||
}
|
||||
|
||||
// Increment a reference count by 1.
|
||||
inline void AtomicRefCountInc(volatile AtomicRefCount *ptr) {
|
||||
base::AtomicRefCountIncN(ptr, 1);
|
||||
}
|
||||
|
||||
// Decrement a reference count by 1 and return whether the result is non-zero.
|
||||
// Insert barriers to ensure that state written before the reference count
|
||||
// became zero will be visible to a thread that has just made the count zero.
|
||||
inline bool AtomicRefCountDec(volatile AtomicRefCount *ptr) {
|
||||
return base::AtomicRefCountDecN(ptr, 1);
|
||||
}
|
||||
|
||||
// Return whether the reference count is one. If the reference count is used
|
||||
// in the conventional way, a refrerence count of 1 implies that the current
|
||||
// thread owns the reference and no other thread shares it. This call performs
|
||||
// the test for a reference count of one, and performs the memory barrier
|
||||
// needed for the owning thread to act on the object, knowing that it has
|
||||
// exclusive access to the object.
|
||||
inline bool AtomicRefCountIsOne(volatile AtomicRefCount *ptr) {
|
||||
return subtle::Acquire_Load(ptr) == 1;
|
||||
}
|
||||
|
||||
// Return whether the reference count is zero. With conventional object
|
||||
// referencing counting, the object will be destroyed, so the reference count
|
||||
// should never be zero. Hence this is generally used for a debug check.
|
||||
inline bool AtomicRefCountIsZero(volatile AtomicRefCount *ptr) {
|
||||
return subtle::Acquire_Load(ptr) == 0;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ATOMIC_REF_COUNT_H_
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_ATOMIC_SEQUENCE_NUM_H_
|
||||
#define BASE_ATOMIC_SEQUENCE_NUM_H_
|
||||
|
||||
#include "base/atomicops.h"
|
||||
#include "base/basictypes.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
class AtomicSequenceNumber {
|
||||
public:
|
||||
AtomicSequenceNumber() : seq_(0) { }
|
||||
explicit AtomicSequenceNumber(base::LinkerInitialized x) { /* seq_ is 0 */ }
|
||||
|
||||
int GetNext() {
|
||||
return static_cast<int>(
|
||||
base::subtle::NoBarrier_AtomicIncrement(&seq_, 1) - 1);
|
||||
}
|
||||
|
||||
private:
|
||||
base::subtle::Atomic32 seq_;
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomicSequenceNumber);
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ATOMIC_SEQUENCE_NUM_H_
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// For atomic operations on reference counts, see atomic_refcount.h.
|
||||
// For atomic operations on sequence numbers, see atomic_sequence_num.h.
|
||||
|
||||
// The routines exported by this module are subtle. If you use them, even if
|
||||
// you get the code right, it will depend on careful reasoning about atomicity
|
||||
// and memory ordering; it will be less readable, and harder to maintain. If
|
||||
// you plan to use these routines, you should have a good reason, such as solid
|
||||
// evidence that performance would otherwise suffer, or there being no
|
||||
// alternative. You should assume only properties explicitly guaranteed by the
|
||||
// specifications in this file. You are almost certainly _not_ writing code
|
||||
// just for the x86; if you assume x86 semantics, x86 hardware bugs and
|
||||
// implementations on other archtectures will cause your code to break. If you
|
||||
// do not know what you are doing, avoid these routines, and use a Mutex.
|
||||
//
|
||||
// It is incorrect to make direct assignments to/from an atomic variable.
|
||||
// You should use one of the Load or Store routines. The NoBarrier
|
||||
// versions are provided when no barriers are needed:
|
||||
// NoBarrier_Store()
|
||||
// NoBarrier_Load()
|
||||
// Although there are currently no compiler enforcement, you are encouraged
|
||||
// to use these.
|
||||
//
|
||||
|
||||
#ifndef BASE_ATOMICOPS_H_
|
||||
#define BASE_ATOMICOPS_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/port.h"
|
||||
|
||||
namespace base {
|
||||
namespace subtle {
|
||||
|
||||
// Bug 1308991. We need this for /Wp64, to mark it safe for AtomicWord casting.
|
||||
#ifndef OS_WIN
|
||||
#define __w64
|
||||
#endif
|
||||
typedef __w64 int32 Atomic32;
|
||||
#ifdef CPU_ARCH_64_BITS
|
||||
typedef int64 Atomic64;
|
||||
#endif
|
||||
|
||||
// Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or
|
||||
// Atomic64 routines below, depending on your architecture.
|
||||
typedef intptr_t AtomicWord;
|
||||
|
||||
// Atomically execute:
|
||||
// result = *ptr;
|
||||
// if (*ptr == old_value)
|
||||
// *ptr = new_value;
|
||||
// return result;
|
||||
//
|
||||
// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value".
|
||||
// Always return the old value of "*ptr"
|
||||
//
|
||||
// This routine implies no memory barriers.
|
||||
Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value);
|
||||
|
||||
// Atomically store new_value into *ptr, returning the previous value held in
|
||||
// *ptr. This routine implies no memory barriers.
|
||||
Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, Atomic32 new_value);
|
||||
|
||||
// Atomically increment *ptr by "increment". Returns the new value of
|
||||
// *ptr with the increment applied. This routine implies no memory barriers.
|
||||
Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment);
|
||||
|
||||
Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment);
|
||||
|
||||
// These following lower-level operations are typically useful only to people
|
||||
// implementing higher-level synchronization operations like spinlocks,
|
||||
// mutexes, and condition-variables. They combine CompareAndSwap(), a load, or
|
||||
// a store with appropriate memory-ordering instructions. "Acquire" operations
|
||||
// ensure that no later memory access can be reordered ahead of the operation.
|
||||
// "Release" operations ensure that no previous memory access can be reordered
|
||||
// after the operation. "Barrier" operations have both "Acquire" and "Release"
|
||||
// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory
|
||||
// access.
|
||||
Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value);
|
||||
Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value);
|
||||
|
||||
void MemoryBarrier();
|
||||
void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value);
|
||||
void Acquire_Store(volatile Atomic32* ptr, Atomic32 value);
|
||||
void Release_Store(volatile Atomic32* ptr, Atomic32 value);
|
||||
|
||||
Atomic32 NoBarrier_Load(volatile const Atomic32* ptr);
|
||||
Atomic32 Acquire_Load(volatile const Atomic32* ptr);
|
||||
Atomic32 Release_Load(volatile const Atomic32* ptr);
|
||||
|
||||
// 64-bit atomic operations (only available on 64-bit processors).
|
||||
#ifdef CPU_ARCH_64_BITS
|
||||
Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value);
|
||||
Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value);
|
||||
Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment);
|
||||
Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment);
|
||||
|
||||
Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value);
|
||||
Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value);
|
||||
void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value);
|
||||
void Acquire_Store(volatile Atomic64* ptr, Atomic64 value);
|
||||
void Release_Store(volatile Atomic64* ptr, Atomic64 value);
|
||||
Atomic64 NoBarrier_Load(volatile const Atomic64* ptr);
|
||||
Atomic64 Acquire_Load(volatile const Atomic64* ptr);
|
||||
Atomic64 Release_Load(volatile const Atomic64* ptr);
|
||||
#endif // CPU_ARCH_64_BITS
|
||||
|
||||
} // namespace base::subtle
|
||||
} // namespace base
|
||||
|
||||
// Include our platform specific implementation.
|
||||
#if defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY)
|
||||
#include "base/atomicops_internals_x86_msvc.h"
|
||||
#elif defined(OS_MACOSX) && defined(ARCH_CPU_X86_FAMILY)
|
||||
#include "base/atomicops_internals_x86_macosx.h"
|
||||
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY)
|
||||
#include "base/atomicops_internals_x86_gcc.h"
|
||||
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)
|
||||
#include "base/atomicops_internals_arm_gcc.h"
|
||||
#else
|
||||
#error "Atomic operations are not supported on your platform"
|
||||
#endif
|
||||
|
||||
#endif // BASE_ATOMICOPS_H_
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file is an internal atomic implementation, use base/atomicops.h instead.
|
||||
//
|
||||
// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears.
|
||||
|
||||
#ifndef BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_
|
||||
#define BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_
|
||||
|
||||
namespace base {
|
||||
namespace subtle {
|
||||
|
||||
// 0xffff0fc0 is the hard coded address of a function provided by
|
||||
// the kernel which implements an atomic compare-exchange. On older
|
||||
// ARM architecture revisions (pre-v6) this may be implemented using
|
||||
// a syscall. This address is stable, and in active use (hard coded)
|
||||
// by at least glibc-2.7 and the Android C library.
|
||||
typedef Atomic32 (*LinuxKernelCmpxchgFunc)(Atomic32 old_value,
|
||||
Atomic32 new_value,
|
||||
volatile Atomic32* ptr);
|
||||
LinuxKernelCmpxchgFunc pLinuxKernelCmpxchg __attribute__((weak)) =
|
||||
(LinuxKernelCmpxchgFunc) 0xffff0fc0;
|
||||
|
||||
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
|
||||
LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) =
|
||||
(LinuxKernelMemoryBarrierFunc) 0xffff0fa0;
|
||||
|
||||
|
||||
inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 prev_value = *ptr;
|
||||
do {
|
||||
if (!pLinuxKernelCmpxchg(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr))) {
|
||||
return old_value;
|
||||
}
|
||||
prev_value = *ptr;
|
||||
} while (prev_value == old_value);
|
||||
return prev_value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 old_value;
|
||||
do {
|
||||
old_value = *ptr;
|
||||
} while (pLinuxKernelCmpxchg(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr)));
|
||||
return old_value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
return Barrier_AtomicIncrement(ptr, increment);
|
||||
}
|
||||
|
||||
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
for (;;) {
|
||||
// Atomic exchange the old value with an incremented one.
|
||||
Atomic32 old_value = *ptr;
|
||||
Atomic32 new_value = old_value + increment;
|
||||
if (pLinuxKernelCmpxchg(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr)) == 0) {
|
||||
// The exchange took place as expected.
|
||||
return new_value;
|
||||
}
|
||||
// Otherwise, *ptr changed mid-loop and we need to retry.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void MemoryBarrier() {
|
||||
pLinuxKernelMemoryBarrier();
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
MemoryBarrier();
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
MemoryBarrier();
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
|
||||
Atomic32 value = *ptr;
|
||||
MemoryBarrier();
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_Load(volatile const Atomic32* ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
} // namespace base::subtle
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ATOMICOPS_INTERNALS_ARM_GCC_H_
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This module gets enough CPU information to optimize the
|
||||
// atomicops module on x86.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "base/atomicops.h"
|
||||
#include "base/basictypes.h"
|
||||
|
||||
// This file only makes sense with atomicops_internals_x86_gcc.h -- it
|
||||
// depends on structs that are defined in that file. If atomicops.h
|
||||
// doesn't sub-include that file, then we aren't needed, and shouldn't
|
||||
// try to do anything.
|
||||
#ifdef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
|
||||
|
||||
// Inline cpuid instruction. In PIC compilations, %ebx contains the address
|
||||
// of the global offset table. To avoid breaking such executables, this code
|
||||
// must preserve that register's value across cpuid instructions.
|
||||
#if defined(__i386__)
|
||||
#define cpuid(a, b, c, d, inp) \
|
||||
asm ("mov %%ebx, %%edi\n" \
|
||||
"cpuid\n" \
|
||||
"xchg %%edi, %%ebx\n" \
|
||||
: "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp))
|
||||
#elif defined (__x86_64__)
|
||||
#define cpuid(a, b, c, d, inp) \
|
||||
asm ("mov %%rbx, %%rdi\n" \
|
||||
"cpuid\n" \
|
||||
"xchg %%rdi, %%rbx\n" \
|
||||
: "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp))
|
||||
#endif
|
||||
|
||||
#if defined(cpuid) // initialize the struct only on x86
|
||||
|
||||
// Set the flags so that code will run correctly and conservatively, so even
|
||||
// if we haven't been initialized yet, we're probably single threaded, and our
|
||||
// default values should hopefully be pretty safe.
|
||||
struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = {
|
||||
false, // bug can't exist before process spawns multiple threads
|
||||
false, // no SSE2
|
||||
};
|
||||
|
||||
// Initialize the AtomicOps_Internalx86CPUFeatures struct.
|
||||
static void AtomicOps_Internalx86CPUFeaturesInit() {
|
||||
uint32 eax;
|
||||
uint32 ebx;
|
||||
uint32 ecx;
|
||||
uint32 edx;
|
||||
|
||||
// Get vendor string (issue CPUID with eax = 0)
|
||||
cpuid(eax, ebx, ecx, edx, 0);
|
||||
char vendor[13];
|
||||
memcpy(vendor, &ebx, 4);
|
||||
memcpy(vendor + 4, &edx, 4);
|
||||
memcpy(vendor + 8, &ecx, 4);
|
||||
vendor[12] = 0;
|
||||
|
||||
// get feature flags in ecx/edx, and family/model in eax
|
||||
cpuid(eax, ebx, ecx, edx, 1);
|
||||
|
||||
int family = (eax >> 8) & 0xf; // family and model fields
|
||||
int model = (eax >> 4) & 0xf;
|
||||
if (family == 0xf) { // use extended family and model fields
|
||||
family += (eax >> 20) & 0xff;
|
||||
model += ((eax >> 16) & 0xf) << 4;
|
||||
}
|
||||
|
||||
// Opteron Rev E has a bug in which on very rare occasions a locked
|
||||
// instruction doesn't act as a read-acquire barrier if followed by a
|
||||
// non-locked read-modify-write instruction. Rev F has this bug in
|
||||
// pre-release versions, but not in versions released to customers,
|
||||
// so we test only for Rev E, which is family 15, model 32..63 inclusive.
|
||||
if (strcmp(vendor, "AuthenticAMD") == 0 && // AMD
|
||||
family == 15 &&
|
||||
32 <= model && model <= 63) {
|
||||
AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = true;
|
||||
} else {
|
||||
AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = false;
|
||||
}
|
||||
|
||||
// edx bit 26 is SSE2 which we use to tell use whether we can use mfence
|
||||
AtomicOps_Internalx86CPUFeatures.has_sse2 = ((edx >> 26) & 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class AtomicOpsx86Initializer {
|
||||
public:
|
||||
AtomicOpsx86Initializer() {
|
||||
AtomicOps_Internalx86CPUFeaturesInit();
|
||||
}
|
||||
};
|
||||
|
||||
// A global to get use initialized on startup via static initialization :/
|
||||
AtomicOpsx86Initializer g_initer;
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // if x86
|
||||
|
||||
#endif // ifdef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
|
|
@ -0,0 +1,248 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file is an internal atomic implementation, use base/atomicops.h instead.
|
||||
|
||||
#ifndef BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
|
||||
#define BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
|
||||
|
||||
// This struct is not part of the public API of this module; clients may not
|
||||
// use it.
|
||||
// Features of this x86. Values may not be correct before main() is run,
|
||||
// but are set conservatively.
|
||||
struct AtomicOps_x86CPUFeatureStruct {
|
||||
bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence
|
||||
// after acquire compare-and-swap.
|
||||
bool has_sse2; // Processor has SSE2.
|
||||
};
|
||||
extern struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures;
|
||||
|
||||
#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory")
|
||||
|
||||
namespace base {
|
||||
namespace subtle {
|
||||
|
||||
// 32-bit low-level operations on any platform.
|
||||
|
||||
inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 prev;
|
||||
__asm__ __volatile__("lock; cmpxchgl %1,%2"
|
||||
: "=a" (prev)
|
||||
: "q" (new_value), "m" (*ptr), "0" (old_value)
|
||||
: "memory");
|
||||
return prev;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr,
|
||||
Atomic32 new_value) {
|
||||
__asm__ __volatile__("xchgl %1,%0" // The lock prefix is implicit for xchg.
|
||||
: "=r" (new_value)
|
||||
: "m" (*ptr), "0" (new_value)
|
||||
: "memory");
|
||||
return new_value; // Now it's the previous value.
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
Atomic32 temp = increment;
|
||||
__asm__ __volatile__("lock; xaddl %0,%1"
|
||||
: "+r" (temp), "+m" (*ptr)
|
||||
: : "memory");
|
||||
// temp now holds the old value of *ptr
|
||||
return temp + increment;
|
||||
}
|
||||
|
||||
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
Atomic32 temp = increment;
|
||||
__asm__ __volatile__("lock; xaddl %0,%1"
|
||||
: "+r" (temp), "+m" (*ptr)
|
||||
: : "memory");
|
||||
// temp now holds the old value of *ptr
|
||||
if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) {
|
||||
__asm__ __volatile__("lfence" : : : "memory");
|
||||
}
|
||||
return temp + increment;
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) {
|
||||
__asm__ __volatile__("lfence" : : : "memory");
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
// 64-bit implementations of memory barrier can be simpler, because it
|
||||
// "mfence" is guaranteed to exist.
|
||||
inline void MemoryBarrier() {
|
||||
__asm__ __volatile__("mfence" : : : "memory");
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
MemoryBarrier();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline void MemoryBarrier() {
|
||||
if (AtomicOps_Internalx86CPUFeatures.has_sse2) {
|
||||
__asm__ __volatile__("mfence" : : : "memory");
|
||||
} else { // mfence is faster but not present on PIII
|
||||
Atomic32 x = 0;
|
||||
NoBarrier_AtomicExchange(&x, 0); // acts as a barrier on PIII
|
||||
}
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
if (AtomicOps_Internalx86CPUFeatures.has_sse2) {
|
||||
*ptr = value;
|
||||
__asm__ __volatile__("mfence" : : : "memory");
|
||||
} else {
|
||||
NoBarrier_AtomicExchange(ptr, value);
|
||||
// acts as a barrier on PIII
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
ATOMICOPS_COMPILER_BARRIER();
|
||||
*ptr = value; // An x86 store acts as a release barrier.
|
||||
// See comments in Atomic64 version of Release_Store(), below.
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
|
||||
Atomic32 value = *ptr; // An x86 load acts as a acquire barrier.
|
||||
// See comments in Atomic64 version of Release_Store(), below.
|
||||
ATOMICOPS_COMPILER_BARRIER();
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_Load(volatile const Atomic32* ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
// 64-bit low-level operations on 64-bit platform.
|
||||
|
||||
inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value) {
|
||||
Atomic64 prev;
|
||||
__asm__ __volatile__("lock; cmpxchgq %1,%2"
|
||||
: "=a" (prev)
|
||||
: "q" (new_value), "m" (*ptr), "0" (old_value)
|
||||
: "memory");
|
||||
return prev;
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr,
|
||||
Atomic64 new_value) {
|
||||
__asm__ __volatile__("xchgq %1,%0" // The lock prefix is implicit for xchg.
|
||||
: "=r" (new_value)
|
||||
: "m" (*ptr), "0" (new_value)
|
||||
: "memory");
|
||||
return new_value; // Now it's the previous value.
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr,
|
||||
Atomic64 increment) {
|
||||
Atomic64 temp = increment;
|
||||
__asm__ __volatile__("lock; xaddq %0,%1"
|
||||
: "+r" (temp), "+m" (*ptr)
|
||||
: : "memory");
|
||||
// temp now contains the previous value of *ptr
|
||||
return temp + increment;
|
||||
}
|
||||
|
||||
inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr,
|
||||
Atomic64 increment) {
|
||||
Atomic64 temp = increment;
|
||||
__asm__ __volatile__("lock; xaddq %0,%1"
|
||||
: "+r" (temp), "+m" (*ptr)
|
||||
: : "memory");
|
||||
// temp now contains the previous value of *ptr
|
||||
if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) {
|
||||
__asm__ __volatile__("lfence" : : : "memory");
|
||||
}
|
||||
return temp + increment;
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
*ptr = value;
|
||||
MemoryBarrier();
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
ATOMICOPS_COMPILER_BARRIER();
|
||||
|
||||
*ptr = value; // An x86 store acts as a release barrier
|
||||
// for current AMD/Intel chips as of Jan 2008.
|
||||
// See also Acquire_Load(), below.
|
||||
|
||||
// When new chips come out, check:
|
||||
// IA-32 Intel Architecture Software Developer's Manual, Volume 3:
|
||||
// System Programming Guide, Chatper 7: Multiple-processor management,
|
||||
// Section 7.2, Memory Ordering.
|
||||
// Last seen at:
|
||||
// http://developer.intel.com/design/pentium4/manuals/index_new.htm
|
||||
//
|
||||
// x86 stores/loads fail to act as barriers for a few instructions (clflush
|
||||
// maskmovdqu maskmovq movntdq movnti movntpd movntps movntq) but these are
|
||||
// not generated by the compiler, and are rare. Users of these instructions
|
||||
// need to know about cache behaviour in any case since all of these involve
|
||||
// either flushing cache lines or non-temporal cache hints.
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) {
|
||||
Atomic64 value = *ptr; // An x86 load acts as a acquire barrier,
|
||||
// for current AMD/Intel chips as of Jan 2008.
|
||||
// See also Release_Store(), above.
|
||||
ATOMICOPS_COMPILER_BARRIER();
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic64 Release_Load(volatile const Atomic64* ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
#endif // defined(__x86_64__)
|
||||
|
||||
} // namespace base::subtle
|
||||
} // namespace base
|
||||
|
||||
#undef ATOMICOPS_COMPILER_BARRIER
|
||||
|
||||
#endif // BASE_ATOMICOPS_INTERNALS_X86_GCC_H_
|
|
@ -0,0 +1,279 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file is an internal atomic implementation, use base/atomicops.h instead.
|
||||
|
||||
#ifndef BASE_ATOMICOPS_INTERNALS_X86_MACOSX_H_
|
||||
#define BASE_ATOMICOPS_INTERNALS_X86_MACOSX_H_
|
||||
|
||||
#include <libkern/OSAtomic.h>
|
||||
|
||||
namespace base {
|
||||
namespace subtle {
|
||||
|
||||
inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32 *ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 prev_value;
|
||||
do {
|
||||
if (OSAtomicCompareAndSwap32(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr))) {
|
||||
return old_value;
|
||||
}
|
||||
prev_value = *ptr;
|
||||
} while (prev_value == old_value);
|
||||
return prev_value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32 *ptr,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 old_value;
|
||||
do {
|
||||
old_value = *ptr;
|
||||
} while (!OSAtomicCompareAndSwap32(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr)));
|
||||
return old_value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr,
|
||||
Atomic32 increment) {
|
||||
return OSAtomicAdd32(increment, const_cast<Atomic32*>(ptr));
|
||||
}
|
||||
|
||||
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr,
|
||||
Atomic32 increment) {
|
||||
return OSAtomicAdd32Barrier(increment, const_cast<Atomic32*>(ptr));
|
||||
}
|
||||
|
||||
inline void MemoryBarrier() {
|
||||
OSMemoryBarrier();
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32 *ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
Atomic32 prev_value;
|
||||
do {
|
||||
if (OSAtomicCompareAndSwap32Barrier(old_value, new_value,
|
||||
const_cast<Atomic32*>(ptr))) {
|
||||
return old_value;
|
||||
}
|
||||
prev_value = *ptr;
|
||||
} while (prev_value == old_value);
|
||||
return prev_value;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_CompareAndSwap(volatile Atomic32 *ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return Acquire_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic32 *ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
MemoryBarrier();
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic32 *ptr, Atomic32 value) {
|
||||
MemoryBarrier();
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_Load(volatile const Atomic32 *ptr) {
|
||||
Atomic32 value = *ptr;
|
||||
MemoryBarrier();
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_Load(volatile const Atomic32 *ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
|
||||
// 64-bit implementation on 64-bit platform
|
||||
|
||||
inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64 *ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value) {
|
||||
Atomic64 prev_value;
|
||||
do {
|
||||
if (OSAtomicCompareAndSwap64(old_value, new_value,
|
||||
const_cast<Atomic64*>(ptr))) {
|
||||
return old_value;
|
||||
}
|
||||
prev_value = *ptr;
|
||||
} while (prev_value == old_value);
|
||||
return prev_value;
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64 *ptr,
|
||||
Atomic64 new_value) {
|
||||
Atomic64 old_value;
|
||||
do {
|
||||
old_value = *ptr;
|
||||
} while (!OSAtomicCompareAndSwap64(old_value, new_value,
|
||||
const_cast<Atomic64*>(ptr)));
|
||||
return old_value;
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr,
|
||||
Atomic64 increment) {
|
||||
return OSAtomicAdd64(increment, const_cast<Atomic64*>(ptr));
|
||||
}
|
||||
|
||||
inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr,
|
||||
Atomic64 increment) {
|
||||
return OSAtomicAdd64Barrier(increment, const_cast<Atomic64*>(ptr));
|
||||
}
|
||||
|
||||
inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64 *ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value) {
|
||||
Atomic64 prev_value;
|
||||
do {
|
||||
if (OSAtomicCompareAndSwap64Barrier(old_value, new_value,
|
||||
const_cast<Atomic64*>(ptr))) {
|
||||
return old_value;
|
||||
}
|
||||
prev_value = *ptr;
|
||||
} while (prev_value == old_value);
|
||||
return prev_value;
|
||||
}
|
||||
|
||||
inline Atomic64 Release_CompareAndSwap(volatile Atomic64 *ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value) {
|
||||
// The lib kern interface does not distinguish between
|
||||
// Acquire and Release memory barriers; they are equivalent.
|
||||
return Acquire_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic64 *ptr, Atomic64 value) {
|
||||
*ptr = value;
|
||||
MemoryBarrier();
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic64 *ptr, Atomic64 value) {
|
||||
MemoryBarrier();
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic64 Acquire_Load(volatile const Atomic64 *ptr) {
|
||||
Atomic64 value = *ptr;
|
||||
MemoryBarrier();
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic64 Release_Load(volatile const Atomic64 *ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
#endif // defined(__LP64__)
|
||||
|
||||
// MacOS uses long for intptr_t, AtomicWord and Atomic32 are always different
|
||||
// on the Mac, even when they are the same size. We need to explicitly cast
|
||||
// from AtomicWord to Atomic32/64 to implement the AtomicWord interface.
|
||||
#ifdef __LP64__
|
||||
#define AtomicWordCastType Atomic64
|
||||
#else
|
||||
#define AtomicWordCastType Atomic32
|
||||
#endif
|
||||
|
||||
inline AtomicWord NoBarrier_CompareAndSwap(volatile AtomicWord* ptr,
|
||||
AtomicWord old_value,
|
||||
AtomicWord new_value) {
|
||||
return NoBarrier_CompareAndSwap(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr),
|
||||
old_value, new_value);
|
||||
}
|
||||
|
||||
inline AtomicWord NoBarrier_AtomicExchange(volatile AtomicWord* ptr,
|
||||
AtomicWord new_value) {
|
||||
return NoBarrier_AtomicExchange(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), new_value);
|
||||
}
|
||||
|
||||
inline AtomicWord NoBarrier_AtomicIncrement(volatile AtomicWord* ptr,
|
||||
AtomicWord increment) {
|
||||
return NoBarrier_AtomicIncrement(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), increment);
|
||||
}
|
||||
|
||||
inline AtomicWord Barrier_AtomicIncrement(volatile AtomicWord* ptr,
|
||||
AtomicWord increment) {
|
||||
return Barrier_AtomicIncrement(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), increment);
|
||||
}
|
||||
|
||||
inline AtomicWord Acquire_CompareAndSwap(volatile AtomicWord* ptr,
|
||||
AtomicWord old_value,
|
||||
AtomicWord new_value) {
|
||||
return base::subtle::Acquire_CompareAndSwap(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr),
|
||||
old_value, new_value);
|
||||
}
|
||||
|
||||
inline AtomicWord Release_CompareAndSwap(volatile AtomicWord* ptr,
|
||||
AtomicWord old_value,
|
||||
AtomicWord new_value) {
|
||||
return base::subtle::Release_CompareAndSwap(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr),
|
||||
old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile AtomicWord *ptr, AtomicWord value) {
|
||||
NoBarrier_Store(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), value);
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile AtomicWord* ptr, AtomicWord value) {
|
||||
return base::subtle::Acquire_Store(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), value);
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile AtomicWord* ptr, AtomicWord value) {
|
||||
return base::subtle::Release_Store(
|
||||
reinterpret_cast<volatile AtomicWordCastType*>(ptr), value);
|
||||
}
|
||||
|
||||
inline AtomicWord NoBarrier_Load(volatile const AtomicWord *ptr) {
|
||||
return NoBarrier_Load(
|
||||
reinterpret_cast<volatile const AtomicWordCastType*>(ptr));
|
||||
}
|
||||
|
||||
inline AtomicWord Acquire_Load(volatile const AtomicWord* ptr) {
|
||||
return base::subtle::Acquire_Load(
|
||||
reinterpret_cast<volatile const AtomicWordCastType*>(ptr));
|
||||
}
|
||||
|
||||
inline AtomicWord Release_Load(volatile const AtomicWord* ptr) {
|
||||
return base::subtle::Release_Load(
|
||||
reinterpret_cast<volatile const AtomicWordCastType*>(ptr));
|
||||
}
|
||||
|
||||
#undef AtomicWordCastType
|
||||
|
||||
} // namespace base::subtle
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ATOMICOPS_INTERNALS_X86_MACOSX_H_
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file is an internal atomic implementation, use base/atomicops.h instead.
|
||||
|
||||
#ifndef BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
|
||||
#define BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace base {
|
||||
namespace subtle {
|
||||
|
||||
inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
LONG result = InterlockedCompareExchange(
|
||||
reinterpret_cast<volatile LONG*>(ptr),
|
||||
static_cast<LONG>(new_value),
|
||||
static_cast<LONG>(old_value));
|
||||
return static_cast<Atomic32>(result);
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr,
|
||||
Atomic32 new_value) {
|
||||
LONG result = InterlockedExchange(
|
||||
reinterpret_cast<volatile LONG*>(ptr),
|
||||
static_cast<LONG>(new_value));
|
||||
return static_cast<Atomic32>(result);
|
||||
}
|
||||
|
||||
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
return InterlockedExchangeAdd(
|
||||
reinterpret_cast<volatile LONG*>(ptr),
|
||||
static_cast<LONG>(increment)) + increment;
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
|
||||
Atomic32 increment) {
|
||||
return Barrier_AtomicIncrement(ptr, increment);
|
||||
}
|
||||
|
||||
#if !(defined(_MSC_VER) && _MSC_VER >= 1400)
|
||||
#error "We require at least vs2005 for MemoryBarrier"
|
||||
#endif
|
||||
inline void MemoryBarrier() {
|
||||
// We use MemoryBarrier from WinNT.h
|
||||
::MemoryBarrier();
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
|
||||
Atomic32 old_value,
|
||||
Atomic32 new_value) {
|
||||
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
NoBarrier_AtomicExchange(ptr, value);
|
||||
// acts as a barrier in this implementation
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
|
||||
*ptr = value; // works w/o barrier for current Intel chips as of June 2005
|
||||
// See comments in Atomic64 version of Release_Store() below.
|
||||
}
|
||||
|
||||
inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
|
||||
Atomic32 value = *ptr;
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic32 Release_Load(volatile const Atomic32* ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
// 64-bit low-level operations on 64-bit platform.
|
||||
|
||||
COMPILE_ASSERT(sizeof(Atomic64) == sizeof(PVOID), atomic_word_is_atomic);
|
||||
|
||||
inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
|
||||
Atomic64 old_value,
|
||||
Atomic64 new_value) {
|
||||
PVOID result = InterlockedCompareExchangePointer(
|
||||
reinterpret_cast<volatile PVOID*>(ptr),
|
||||
reinterpret_cast<PVOID>(new_value), reinterpret_cast<PVOID>(old_value));
|
||||
return reinterpret_cast<Atomic64>(result);
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr,
|
||||
Atomic64 new_value) {
|
||||
PVOID result = InterlockedExchangePointer(
|
||||
reinterpret_cast<volatile PVOID*>(ptr),
|
||||
reinterpret_cast<PVOID>(new_value));
|
||||
return reinterpret_cast<Atomic64>(result);
|
||||
}
|
||||
|
||||
inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr,
|
||||
Atomic64 increment) {
|
||||
return InterlockedExchangeAdd64(
|
||||
reinterpret_cast<volatile LONGLONG*>(ptr),
|
||||
static_cast<LONGLONG>(increment)) + increment;
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr,
|
||||
Atomic64 increment) {
|
||||
return Barrier_AtomicIncrement(ptr, increment);
|
||||
}
|
||||
|
||||
inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
NoBarrier_AtomicExchange(ptr, value);
|
||||
// acts as a barrier in this implementation
|
||||
}
|
||||
|
||||
inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) {
|
||||
*ptr = value; // works w/o barrier for current Intel chips as of June 2005
|
||||
|
||||
// When new chips come out, check:
|
||||
// IA-32 Intel Architecture Software Developer's Manual, Volume 3:
|
||||
// System Programming Guide, Chatper 7: Multiple-processor management,
|
||||
// Section 7.2, Memory Ordering.
|
||||
// Last seen at:
|
||||
// http://developer.intel.com/design/pentium4/manuals/index_new.htm
|
||||
}
|
||||
|
||||
inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) {
|
||||
Atomic64 value = *ptr;
|
||||
return value;
|
||||
}
|
||||
|
||||
inline Atomic64 Release_Load(volatile const Atomic64* ptr) {
|
||||
MemoryBarrier();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
#endif // defined(_WIN64)
|
||||
|
||||
} // namespace base::subtle
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
|
|
@ -0,0 +1,237 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/atomicops.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
template <class AtomicType>
|
||||
static void TestAtomicIncrement() {
|
||||
// For now, we just test single threaded execution
|
||||
|
||||
// use a guard value to make sure the NoBarrier_AtomicIncrement doesn't go
|
||||
// outside the expected address bounds. This is in particular to
|
||||
// test that some future change to the asm code doesn't cause the
|
||||
// 32-bit NoBarrier_AtomicIncrement doesn't do the wrong thing on 64-bit
|
||||
// machines.
|
||||
struct {
|
||||
AtomicType prev_word;
|
||||
AtomicType count;
|
||||
AtomicType next_word;
|
||||
} s;
|
||||
|
||||
AtomicType prev_word_value, next_word_value;
|
||||
memset(&prev_word_value, 0xFF, sizeof(AtomicType));
|
||||
memset(&next_word_value, 0xEE, sizeof(AtomicType));
|
||||
|
||||
s.prev_word = prev_word_value;
|
||||
s.count = 0;
|
||||
s.next_word = next_word_value;
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1);
|
||||
EXPECT_EQ(s.count, 1);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3);
|
||||
EXPECT_EQ(s.count, 3);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6);
|
||||
EXPECT_EQ(s.count, 6);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3);
|
||||
EXPECT_EQ(s.count, 3);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1);
|
||||
EXPECT_EQ(s.count, 1);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0);
|
||||
EXPECT_EQ(s.count, 0);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1);
|
||||
EXPECT_EQ(s.count, -1);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5);
|
||||
EXPECT_EQ(s.count, -5);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
|
||||
EXPECT_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0);
|
||||
EXPECT_EQ(s.count, 0);
|
||||
EXPECT_EQ(s.prev_word, prev_word_value);
|
||||
EXPECT_EQ(s.next_word, next_word_value);
|
||||
}
|
||||
|
||||
|
||||
#define NUM_BITS(T) (sizeof(T) * 8)
|
||||
|
||||
|
||||
template <class AtomicType>
|
||||
static void TestCompareAndSwap() {
|
||||
AtomicType value = 0;
|
||||
AtomicType prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 1);
|
||||
EXPECT_EQ(1, value);
|
||||
EXPECT_EQ(0, prev);
|
||||
|
||||
// Use test value that has non-zero bits in both halves, more for testing
|
||||
// 64-bit implementation on 32-bit platforms.
|
||||
const AtomicType k_test_val = (GG_ULONGLONG(1) <<
|
||||
(NUM_BITS(AtomicType) - 2)) + 11;
|
||||
value = k_test_val;
|
||||
prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 5);
|
||||
EXPECT_EQ(k_test_val, value);
|
||||
EXPECT_EQ(k_test_val, prev);
|
||||
|
||||
value = k_test_val;
|
||||
prev = base::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5);
|
||||
EXPECT_EQ(5, value);
|
||||
EXPECT_EQ(k_test_val, prev);
|
||||
}
|
||||
|
||||
|
||||
template <class AtomicType>
|
||||
static void TestAtomicExchange() {
|
||||
AtomicType value = 0;
|
||||
AtomicType new_value = base::subtle::NoBarrier_AtomicExchange(&value, 1);
|
||||
EXPECT_EQ(1, value);
|
||||
EXPECT_EQ(0, new_value);
|
||||
|
||||
// Use test value that has non-zero bits in both halves, more for testing
|
||||
// 64-bit implementation on 32-bit platforms.
|
||||
const AtomicType k_test_val = (GG_ULONGLONG(1) <<
|
||||
(NUM_BITS(AtomicType) - 2)) + 11;
|
||||
value = k_test_val;
|
||||
new_value = base::subtle::NoBarrier_AtomicExchange(&value, k_test_val);
|
||||
EXPECT_EQ(k_test_val, value);
|
||||
EXPECT_EQ(k_test_val, new_value);
|
||||
|
||||
value = k_test_val;
|
||||
new_value = base::subtle::NoBarrier_AtomicExchange(&value, 5);
|
||||
EXPECT_EQ(5, value);
|
||||
EXPECT_EQ(k_test_val, new_value);
|
||||
}
|
||||
|
||||
|
||||
template <class AtomicType>
|
||||
static void TestAtomicIncrementBounds() {
|
||||
// Test at rollover boundary between int_max and int_min
|
||||
AtomicType test_val = (GG_ULONGLONG(1) <<
|
||||
(NUM_BITS(AtomicType) - 1));
|
||||
AtomicType value = -1 ^ test_val;
|
||||
AtomicType new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1);
|
||||
EXPECT_EQ(test_val, value);
|
||||
EXPECT_EQ(value, new_value);
|
||||
|
||||
base::subtle::NoBarrier_AtomicIncrement(&value, -1);
|
||||
EXPECT_EQ(-1 ^ test_val, value);
|
||||
|
||||
// Test at 32-bit boundary for 64-bit atomic type.
|
||||
test_val = GG_ULONGLONG(1) << (NUM_BITS(AtomicType) / 2);
|
||||
value = test_val - 1;
|
||||
new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1);
|
||||
EXPECT_EQ(test_val, value);
|
||||
EXPECT_EQ(value, new_value);
|
||||
|
||||
base::subtle::NoBarrier_AtomicIncrement(&value, -1);
|
||||
EXPECT_EQ(test_val - 1, value);
|
||||
}
|
||||
|
||||
// Return an AtomicType with the value 0xa5a5a5..
|
||||
template <class AtomicType>
|
||||
static AtomicType TestFillValue() {
|
||||
AtomicType val = 0;
|
||||
memset(&val, 0xa5, sizeof(AtomicType));
|
||||
return val;
|
||||
}
|
||||
|
||||
// This is a simple sanity check that values are correct. Not testing
|
||||
// atomicity
|
||||
template <class AtomicType>
|
||||
static void TestStore() {
|
||||
const AtomicType kVal1 = TestFillValue<AtomicType>();
|
||||
const AtomicType kVal2 = static_cast<AtomicType>(-1);
|
||||
|
||||
AtomicType value;
|
||||
|
||||
base::subtle::NoBarrier_Store(&value, kVal1);
|
||||
EXPECT_EQ(kVal1, value);
|
||||
base::subtle::NoBarrier_Store(&value, kVal2);
|
||||
EXPECT_EQ(kVal2, value);
|
||||
|
||||
base::subtle::Acquire_Store(&value, kVal1);
|
||||
EXPECT_EQ(kVal1, value);
|
||||
base::subtle::Acquire_Store(&value, kVal2);
|
||||
EXPECT_EQ(kVal2, value);
|
||||
|
||||
base::subtle::Release_Store(&value, kVal1);
|
||||
EXPECT_EQ(kVal1, value);
|
||||
base::subtle::Release_Store(&value, kVal2);
|
||||
EXPECT_EQ(kVal2, value);
|
||||
}
|
||||
|
||||
// This is a simple sanity check that values are correct. Not testing
|
||||
// atomicity
|
||||
template <class AtomicType>
|
||||
static void TestLoad() {
|
||||
const AtomicType kVal1 = TestFillValue<AtomicType>();
|
||||
const AtomicType kVal2 = static_cast<AtomicType>(-1);
|
||||
|
||||
AtomicType value;
|
||||
|
||||
value = kVal1;
|
||||
EXPECT_EQ(kVal1, base::subtle::NoBarrier_Load(&value));
|
||||
value = kVal2;
|
||||
EXPECT_EQ(kVal2, base::subtle::NoBarrier_Load(&value));
|
||||
|
||||
value = kVal1;
|
||||
EXPECT_EQ(kVal1, base::subtle::Acquire_Load(&value));
|
||||
value = kVal2;
|
||||
EXPECT_EQ(kVal2, base::subtle::Acquire_Load(&value));
|
||||
|
||||
value = kVal1;
|
||||
EXPECT_EQ(kVal1, base::subtle::Release_Load(&value));
|
||||
value = kVal2;
|
||||
EXPECT_EQ(kVal2, base::subtle::Release_Load(&value));
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, Inc) {
|
||||
TestAtomicIncrement<base::subtle::Atomic32>();
|
||||
TestAtomicIncrement<base::subtle::AtomicWord>();
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, CompareAndSwap) {
|
||||
TestCompareAndSwap<base::subtle::Atomic32>();
|
||||
TestCompareAndSwap<base::subtle::AtomicWord>();
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, Exchange) {
|
||||
TestAtomicExchange<base::subtle::Atomic32>();
|
||||
TestAtomicExchange<base::subtle::AtomicWord>();
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, IncrementBounds) {
|
||||
TestAtomicIncrementBounds<base::subtle::Atomic32>();
|
||||
TestAtomicIncrementBounds<base::subtle::AtomicWord>();
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, Store) {
|
||||
TestStore<base::subtle::Atomic32>();
|
||||
TestStore<base::subtle::AtomicWord>();
|
||||
}
|
||||
|
||||
TEST(AtomicOpsTest, Load) {
|
||||
TestLoad<base::subtle::Atomic32>();
|
||||
TestLoad<base::subtle::AtomicWord>();
|
||||
}
|
|
@ -0,0 +1,724 @@
|
|||
# Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
{
|
||||
'variables': {
|
||||
'chromium_code': 1,
|
||||
},
|
||||
'includes': [
|
||||
'../build/common.gypi',
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'base',
|
||||
'type': '<(library)',
|
||||
'dependencies': [
|
||||
'../third_party/icu38/icu38.gyp:icui18n',
|
||||
'../third_party/icu38/icu38.gyp:icuuc',
|
||||
],
|
||||
'msvs_guid': '1832A374-8A74-4F9E-B536-69A699B3E165',
|
||||
'sources': [
|
||||
'../build/build_config.h',
|
||||
'crypto/cssm_init.cc',
|
||||
'crypto/cssm_init.h',
|
||||
'crypto/signature_verifier.h',
|
||||
'crypto/signature_verifier_mac.cc',
|
||||
'crypto/signature_verifier_nss.cc',
|
||||
'crypto/signature_verifier_win.cc',
|
||||
'third_party/dmg_fp/dmg_fp.h',
|
||||
'third_party/dmg_fp/dtoa.cc',
|
||||
'third_party/dmg_fp/g_fmt.cc',
|
||||
'third_party/nspr/prcpucfg.h',
|
||||
'third_party/nspr/prcpucfg_win.h',
|
||||
'third_party/nspr/prtime.cc',
|
||||
'third_party/nspr/prtime.h',
|
||||
'third_party/nspr/prtypes.h',
|
||||
'third_party/nss/blapi.h',
|
||||
'third_party/nss/blapit.h',
|
||||
'third_party/nss/sha256.h',
|
||||
'third_party/nss/sha512.cc',
|
||||
'third_party/purify/pure.h',
|
||||
'third_party/purify/pure_api.c',
|
||||
'atomicops_internals_x86_gcc.cc',
|
||||
'at_exit.cc',
|
||||
'at_exit.h',
|
||||
'atomic_ref_count.h',
|
||||
'atomic_sequence_num.h',
|
||||
'atomicops.h',
|
||||
'atomicops_internals_x86_msvc.h',
|
||||
'base_drag_source.cc',
|
||||
'base_drag_source.h',
|
||||
'base_drop_target.cc',
|
||||
'base_drop_target.h',
|
||||
'base_paths.cc',
|
||||
'base_paths.h',
|
||||
'base_paths_linux.h',
|
||||
'base_paths_linux.cc',
|
||||
'base_paths_mac.h',
|
||||
'base_paths_mac.mm',
|
||||
'base_paths_win.cc',
|
||||
'base_paths_win.h',
|
||||
'base_switches.cc',
|
||||
'base_switches.h',
|
||||
'basictypes.h',
|
||||
'bzip2_error_handler.cc',
|
||||
'clipboard.cc',
|
||||
'clipboard.h',
|
||||
'clipboard_linux.cc',
|
||||
'clipboard_mac.mm',
|
||||
'clipboard_util.cc',
|
||||
'clipboard_util.h',
|
||||
'clipboard_win.cc',
|
||||
'command_line.cc',
|
||||
'command_line.h',
|
||||
'compiler_specific.h',
|
||||
'condition_variable.h',
|
||||
'condition_variable_posix.cc',
|
||||
'condition_variable_win.cc',
|
||||
'cpu.cc',
|
||||
'cpu.h',
|
||||
'data_pack.cc',
|
||||
'debug_on_start.cc',
|
||||
'debug_on_start.h',
|
||||
'debug_util.cc',
|
||||
'debug_util.h',
|
||||
'debug_util_mac.cc',
|
||||
'debug_util_posix.cc',
|
||||
'debug_util_win.cc',
|
||||
'directory_watcher.h',
|
||||
'directory_watcher_inotify.cc',
|
||||
'directory_watcher_mac.cc',
|
||||
'directory_watcher_win.cc',
|
||||
'event_recorder.cc',
|
||||
'event_recorder.h',
|
||||
'event_recorder_stubs.cc',
|
||||
'field_trial.cc',
|
||||
'field_trial.h',
|
||||
'file_descriptor_shuffle.cc',
|
||||
'file_descriptor_shuffle.h',
|
||||
'file_path.cc',
|
||||
'file_path.h',
|
||||
'file_util.cc',
|
||||
'file_util.h',
|
||||
'file_util_icu.cc',
|
||||
'file_util_linux.cc',
|
||||
'file_util_mac.mm',
|
||||
'file_util_posix.cc',
|
||||
'file_util_win.cc',
|
||||
'file_version_info.cc',
|
||||
'file_version_info.h',
|
||||
'file_version_info_linux.cc',
|
||||
'file_version_info_mac.mm',
|
||||
'fix_wp64.h',
|
||||
'float_util.h',
|
||||
'foundation_utils_mac.h',
|
||||
'hash_tables.h',
|
||||
'histogram.cc',
|
||||
'histogram.h',
|
||||
'hmac.h',
|
||||
'hmac_mac.cc',
|
||||
'hmac_nss.cc',
|
||||
'hmac_win.cc',
|
||||
'iat_patch.cc',
|
||||
'iat_patch.h',
|
||||
'icu_util.cc',
|
||||
'icu_util.h',
|
||||
'id_map.h',
|
||||
'idle_timer.cc',
|
||||
'idle_timer.h',
|
||||
'idle_timer_none.cc',
|
||||
'image_util.cc',
|
||||
'image_util.h',
|
||||
'json_reader.cc',
|
||||
'json_reader.h',
|
||||
'json_writer.cc',
|
||||
'json_writer.h',
|
||||
'keyboard_codes.h',
|
||||
'keyboard_codes_win.h',
|
||||
'lazy_instance.cc',
|
||||
'lazy_instance.h',
|
||||
'linked_ptr.h',
|
||||
'linux_util.cc',
|
||||
'linux_util.h',
|
||||
'lock.cc',
|
||||
'lock.h',
|
||||
'lock_impl.h',
|
||||
'lock_impl_posix.cc',
|
||||
'lock_impl_win.cc',
|
||||
'logging.cc',
|
||||
'logging.h',
|
||||
'mac_util.h',
|
||||
'mac_util.mm',
|
||||
'md5.cc',
|
||||
'md5.h',
|
||||
'memory_debug.cc',
|
||||
'memory_debug.h',
|
||||
'message_loop.cc',
|
||||
'message_loop.h',
|
||||
'message_pump.h',
|
||||
'message_pump_default.cc',
|
||||
'message_pump_default.h',
|
||||
'message_pump_glib.cc',
|
||||
'message_pump_glib.h',
|
||||
'message_pump_libevent.cc',
|
||||
'message_pump_libevent.h',
|
||||
'message_pump_mac.h',
|
||||
'message_pump_mac.mm',
|
||||
'message_pump_win.cc',
|
||||
'message_pump_win.h',
|
||||
'native_library.h',
|
||||
'native_library_linux.cc',
|
||||
'native_library_mac.mm',
|
||||
'native_library_win.cc',
|
||||
'non_thread_safe.cc',
|
||||
'non_thread_safe.h',
|
||||
'nss_init.cc',
|
||||
'nss_init.h',
|
||||
'object_watcher.cc',
|
||||
'object_watcher.h',
|
||||
'observer_list.h',
|
||||
'observer_list_threadsafe.h',
|
||||
'path_service.cc',
|
||||
'path_service.h',
|
||||
'pe_image.cc',
|
||||
'pe_image.h',
|
||||
'pickle.cc',
|
||||
'pickle.h',
|
||||
'platform_file.h',
|
||||
'platform_file_win.cc',
|
||||
'platform_file_posix.cc',
|
||||
'platform_thread.h',
|
||||
'platform_thread_mac.mm',
|
||||
'platform_thread_posix.cc',
|
||||
'platform_thread_win.cc',
|
||||
'port.h',
|
||||
'profiler.cc',
|
||||
'profiler.h',
|
||||
'process.h',
|
||||
'process_posix.cc',
|
||||
'process_util.h',
|
||||
'process_util_linux.cc',
|
||||
'process_util_mac.mm',
|
||||
'process_util_posix.cc',
|
||||
'process_util_win.cc',
|
||||
'process_win.cc',
|
||||
'rand_util.cc',
|
||||
'rand_util.h',
|
||||
'rand_util_posix.cc',
|
||||
'rand_util_win.cc',
|
||||
'ref_counted.cc',
|
||||
'ref_counted.h',
|
||||
'registry.cc',
|
||||
'registry.h',
|
||||
'resource_util.cc',
|
||||
'resource_util.h',
|
||||
'revocable_store.cc',
|
||||
'revocable_store.h',
|
||||
'scoped_bstr_win.cc',
|
||||
'scoped_bstr_win.h',
|
||||
'scoped_cftyperef.h',
|
||||
'scoped_clipboard_writer.cc',
|
||||
'scoped_clipboard_writer.h',
|
||||
'scoped_comptr_win.h',
|
||||
'scoped_handle.h',
|
||||
'scoped_handle_win.h',
|
||||
'scoped_nsautorelease_pool.h',
|
||||
'scoped_nsautorelease_pool.mm',
|
||||
'scoped_nsobject.h',
|
||||
'scoped_ptr.h',
|
||||
'scoped_temp_dir.cc',
|
||||
'scoped_temp_dir.h',
|
||||
'scoped_variant_win.cc',
|
||||
'scoped_variant_win.h',
|
||||
'scoped_vector.h',
|
||||
'sha2.cc',
|
||||
'sha2.h',
|
||||
'shared_memory.h',
|
||||
'shared_memory_posix.cc',
|
||||
'shared_memory_win.cc',
|
||||
'simple_thread.cc',
|
||||
'simple_thread.h',
|
||||
'singleton.h',
|
||||
'spin_wait.h',
|
||||
'stack_container.h',
|
||||
'stats_counters.h',
|
||||
'stats_table.cc',
|
||||
'stats_table.h',
|
||||
'stl_util-inl.h',
|
||||
'string16.cc',
|
||||
'string16.h',
|
||||
'string_escape.cc',
|
||||
'string_escape.h',
|
||||
'string_piece.cc',
|
||||
'string_piece.h',
|
||||
'string_tokenizer.h',
|
||||
'string_util.cc',
|
||||
'string_util.h',
|
||||
'string_util_icu.cc',
|
||||
'string_util_win.h',
|
||||
'sys_info.h',
|
||||
'sys_info_mac.cc',
|
||||
'sys_info_posix.cc',
|
||||
'sys_info_win.cc',
|
||||
'sys_string_conversions.h',
|
||||
'sys_string_conversions_linux.cc',
|
||||
'sys_string_conversions_mac.mm',
|
||||
'sys_string_conversions_win.cc',
|
||||
'system_monitor.cc',
|
||||
'system_monitor.h',
|
||||
'system_monitor_posix.cc',
|
||||
'system_monitor_win.cc',
|
||||
'task.h',
|
||||
'test_file_util.h',
|
||||
'test_file_util_linux.cc',
|
||||
'test_file_util_mac.cc',
|
||||
'test_file_util_posix.cc',
|
||||
'test_file_util_win.cc',
|
||||
'thread.cc',
|
||||
'thread.h',
|
||||
'thread_collision_warner.cc',
|
||||
'thread_collision_warner.h',
|
||||
'thread_local.h',
|
||||
'thread_local_posix.cc',
|
||||
'thread_local_storage.h',
|
||||
'thread_local_storage_posix.cc',
|
||||
'thread_local_storage_win.cc',
|
||||
'thread_local_win.cc',
|
||||
'time.cc',
|
||||
'time.h',
|
||||
'time_format.cc',
|
||||
'time_format.h',
|
||||
'time_mac.cc',
|
||||
'time_posix.cc',
|
||||
'time_win.cc',
|
||||
'timer.cc',
|
||||
'timer.h',
|
||||
'trace_event.cc',
|
||||
'trace_event.h',
|
||||
'tracked.cc',
|
||||
'tracked.h',
|
||||
'tracked_objects.cc',
|
||||
'tracked_objects.h',
|
||||
'tuple.h',
|
||||
'values.cc',
|
||||
'values.h',
|
||||
'version.cc',
|
||||
'version.h',
|
||||
'waitable_event.h',
|
||||
'waitable_event_posix.cc',
|
||||
'waitable_event_watcher.h',
|
||||
'waitable_event_watcher_posix.cc',
|
||||
'waitable_event_watcher_win.cc',
|
||||
'waitable_event_win.cc',
|
||||
'watchdog.cc',
|
||||
'watchdog.h',
|
||||
'win_util.cc',
|
||||
'win_util.h',
|
||||
'windows_message_list.h',
|
||||
'wmi_util.cc',
|
||||
'wmi_util.h',
|
||||
'word_iterator.cc',
|
||||
'word_iterator.h',
|
||||
'worker_pool.h',
|
||||
'worker_pool_linux.cc',
|
||||
'worker_pool_linux.h',
|
||||
'worker_pool_mac.mm',
|
||||
'worker_pool_win.cc',
|
||||
],
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'..',
|
||||
],
|
||||
},
|
||||
# These warnings are needed for the files in third_party\dmg_fp.
|
||||
'msvs_disabled_warnings': [
|
||||
4244, 4554, 4018, 4102,
|
||||
],
|
||||
'conditions': [
|
||||
[ 'OS == "linux"', {
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'linux_version',
|
||||
'variables': {
|
||||
'template_input_path': 'file_version_info_linux.h.version',
|
||||
'template_output_path':
|
||||
'<(SHARED_INTERMEDIATE_DIR)/base/file_version_info_linux.h',
|
||||
},
|
||||
'inputs': [
|
||||
'<(template_input_path)',
|
||||
'../chrome/VERSION',
|
||||
'../chrome/tools/build/linux/version.sh',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'branding == "Chrome"', {
|
||||
'inputs': ['../chrome/app/theme/google_chrome/BRANDING']
|
||||
}, { # else branding!="Chrome"
|
||||
'inputs': ['../chrome/app/theme/chromium/BRANDING']
|
||||
}],
|
||||
],
|
||||
'outputs': [
|
||||
# Use a non-existant output so this action always runs and
|
||||
# generates version information, e.g. to capture revision
|
||||
# changes, which aren't captured by file dependencies.
|
||||
'<(SHARED_INTERMEDIATE_DIR)/base/file_version_info_linux.bogus',
|
||||
],
|
||||
'action': [
|
||||
'../chrome/tools/build/linux/version.sh',
|
||||
'<(template_input_path)', '<(template_output_path)',
|
||||
],
|
||||
},
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)',
|
||||
],
|
||||
'sources/': [ ['exclude', '_(mac|win)\\.cc$'],
|
||||
['exclude', '\\.mm?$' ] ],
|
||||
'sources!': [
|
||||
# Linux has an implementation of idle_timer that depends
|
||||
# on XScreenSaver, but it's unclear if we want it yet,
|
||||
# so use idle_timer_none.cc instead.
|
||||
'idle_timer.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'../build/linux/system.gyp:gtk',
|
||||
'../build/linux/system.gyp:nss',
|
||||
],
|
||||
'cflags': [
|
||||
'-Wno-write-strings',
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
# We need rt for clock_gettime().
|
||||
'-lrt',
|
||||
],
|
||||
},
|
||||
},
|
||||
{ # else: OS != "linux"
|
||||
'sources!': [
|
||||
'crypto/signature_verifier_nss.cc',
|
||||
'atomicops_internals_x86_gcc.cc',
|
||||
'directory_watcher_inotify.cc',
|
||||
'hmac_nss.cc',
|
||||
'idle_timer_none.cc',
|
||||
'linux_util.cc',
|
||||
'message_pump_glib.cc',
|
||||
'nss_init.cc',
|
||||
'nss_init.h',
|
||||
'time_posix.cc',
|
||||
],
|
||||
}
|
||||
],
|
||||
[ 'GENERATOR == "quentin"', {
|
||||
# Quentin builds don't have a recent enough glibc to include the
|
||||
# inotify headers
|
||||
'sources!': [
|
||||
'directory_watcher_inotify.cc',
|
||||
],
|
||||
'sources': [
|
||||
'directory_watcher_stub.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
[ 'OS == "mac"', {
|
||||
'sources/': [ ['exclude', '_(linux|win)\\.cc$'] ],
|
||||
'sources!': [
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'$(SDKROOT)/System/Library/Frameworks/AppKit.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/Carbon.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/Security.framework',
|
||||
],
|
||||
},
|
||||
},
|
||||
{ # else: OS != "mac"
|
||||
'sources!': [
|
||||
'crypto/cssm_init.cc',
|
||||
'crypto/cssm_init.h',
|
||||
],
|
||||
}
|
||||
],
|
||||
[ 'OS == "win"', {
|
||||
'sources/': [ ['exclude', '_(linux|mac|posix)\\.cc$'],
|
||||
['exclude', '\\.mm?$' ] ],
|
||||
'sources!': [
|
||||
'data_pack.cc',
|
||||
'event_recorder_stubs.cc',
|
||||
'file_descriptor_shuffle.cc',
|
||||
'message_pump_libevent.cc',
|
||||
'string16.cc',
|
||||
],
|
||||
},
|
||||
{ # else: OS != "win"
|
||||
'dependencies': ['../third_party/libevent/libevent.gyp:libevent'],
|
||||
'sources!': [
|
||||
'third_party/purify/pure_api.c',
|
||||
'base_drag_source.cc',
|
||||
'base_drop_target.cc',
|
||||
'cpu.cc',
|
||||
'clipboard_util.cc',
|
||||
'debug_on_start.cc',
|
||||
'event_recorder.cc',
|
||||
'file_version_info.cc',
|
||||
'iat_patch.cc',
|
||||
'image_util.cc',
|
||||
'object_watcher.cc',
|
||||
'pe_image.cc',
|
||||
'registry.cc',
|
||||
'resource_util.cc',
|
||||
'win_util.cc',
|
||||
'wmi_util.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'base_gfx',
|
||||
'type': '<(library)',
|
||||
'msvs_guid': 'A508ADD3-CECE-4E0F-8448-2F5E454DF551',
|
||||
'sources': [
|
||||
'gfx/gdi_util.cc',
|
||||
'gfx/gdi_util.h',
|
||||
'gfx/gtk_native_view_id_manager.cc',
|
||||
'gfx/gtk_native_view_id_manager.h',
|
||||
'gfx/gtk_util.cc',
|
||||
'gfx/gtk_util.h',
|
||||
'gfx/jpeg_codec.cc',
|
||||
'gfx/jpeg_codec.h',
|
||||
'gfx/native_theme.cc',
|
||||
'gfx/native_theme.h',
|
||||
'gfx/native_widget_types.h',
|
||||
'gfx/native_widget_types_gtk.cc',
|
||||
'gfx/platform_canvas.h',
|
||||
'gfx/platform_canvas_linux.h',
|
||||
'gfx/platform_canvas_mac.h',
|
||||
'gfx/platform_device_linux.h',
|
||||
'gfx/platform_device_mac.h',
|
||||
'gfx/png_decoder.cc',
|
||||
'gfx/png_decoder.h',
|
||||
'gfx/png_encoder.cc',
|
||||
'gfx/png_encoder.h',
|
||||
'gfx/point.cc',
|
||||
'gfx/point.h',
|
||||
'gfx/rect.cc',
|
||||
'gfx/rect.h',
|
||||
'gfx/size.cc',
|
||||
'gfx/size.h',
|
||||
],
|
||||
'mac_framework_dirs': [
|
||||
'$(SDKROOT)/System/Library/Frameworks/ApplicationServices.framework/Frameworks',
|
||||
],
|
||||
'dependencies': [
|
||||
'base',
|
||||
'../skia/skia.gyp:skia',
|
||||
'../third_party/libjpeg/libjpeg.gyp:libjpeg',
|
||||
'../third_party/libpng/libpng.gyp:libpng',
|
||||
'../third_party/zlib/zlib.gyp:zlib',
|
||||
],
|
||||
'export_dependent_settings': [
|
||||
'base',
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'dependencies': [
|
||||
'../build/linux/system.gyp:gtk',
|
||||
],
|
||||
}],
|
||||
[ 'OS != "win"', { 'sources!': [
|
||||
'gfx/gdi_util.cc',
|
||||
'gfx/native_theme.cc',
|
||||
],
|
||||
}],
|
||||
[ 'OS != "linux"', { 'sources!': [
|
||||
'gfx/gtk_native_view_id_manager.cc',
|
||||
'gfx/gtk_util.cc',
|
||||
'gfx/native_widget_types_gtk.cc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'base_unittests',
|
||||
'type': 'executable',
|
||||
'msvs_guid': '27A30967-4BBA-48D1-8522-CDE95F7B1CEC',
|
||||
'sources': [
|
||||
'crypto/signature_verifier_unittest.cc',
|
||||
'gfx/jpeg_codec_unittest.cc',
|
||||
'gfx/native_theme_unittest.cc',
|
||||
'gfx/png_codec_unittest.cc',
|
||||
'gfx/rect_unittest.cc',
|
||||
'at_exit_unittest.cc',
|
||||
'atomicops_unittest.cc',
|
||||
'clipboard_unittest.cc',
|
||||
'command_line_unittest.cc',
|
||||
'condition_variable_unittest.cc',
|
||||
'data_pack_unittest.cc',
|
||||
'debug_util_unittest.cc',
|
||||
'directory_watcher_unittest.cc',
|
||||
'field_trial_unittest.cc',
|
||||
'file_descriptor_shuffle_unittest.cc',
|
||||
'file_path_unittest.cc',
|
||||
'file_util_unittest.cc',
|
||||
'file_version_info_unittest.cc',
|
||||
'histogram_unittest.cc',
|
||||
'hmac_unittest.cc',
|
||||
'idletimer_unittest.cc',
|
||||
'json_reader_unittest.cc',
|
||||
'json_writer_unittest.cc',
|
||||
'lazy_instance_unittest.cc',
|
||||
'linked_ptr_unittest.cc',
|
||||
'mac_util_unittest.cc',
|
||||
'message_loop_unittest.cc',
|
||||
'object_watcher_unittest.cc',
|
||||
'observer_list_unittest.cc',
|
||||
'path_service_unittest.cc',
|
||||
'pe_image_unittest.cc',
|
||||
'pickle_unittest.cc',
|
||||
'pr_time_unittest.cc',
|
||||
'process_util_unittest.cc',
|
||||
'rand_util_unittest.cc',
|
||||
'ref_counted_unittest.cc',
|
||||
'run_all_unittests.cc',
|
||||
'scoped_bstr_win_unittest.cc',
|
||||
'scoped_comptr_win_unittest.cc',
|
||||
'scoped_ptr_unittest.cc',
|
||||
'scoped_temp_dir_unittest.cc',
|
||||
'scoped_variant_win_unittest.cc',
|
||||
'sha2_unittest.cc',
|
||||
'shared_memory_unittest.cc',
|
||||
'simple_thread_unittest.cc',
|
||||
'singleton_unittest.cc',
|
||||
'stack_container_unittest.cc',
|
||||
'stats_table_unittest.cc',
|
||||
'string_escape_unittest.cc',
|
||||
'string_piece_unittest.cc',
|
||||
'string_tokenizer_unittest.cc',
|
||||
'string_util_unittest.cc',
|
||||
'sys_info_unittest.cc',
|
||||
'sys_string_conversions_unittest.cc',
|
||||
'system_monitor_unittest.cc',
|
||||
'thread_collision_warner_unittest.cc',
|
||||
'thread_local_storage_unittest.cc',
|
||||
'thread_local_unittest.cc',
|
||||
'thread_unittest.cc',
|
||||
'time_unittest.cc',
|
||||
'time_win_unittest.cc',
|
||||
'timer_unittest.cc',
|
||||
'tracked_objects_unittest.cc',
|
||||
'tuple_unittest.cc',
|
||||
'values_unittest.cc',
|
||||
'version_unittest.cc',
|
||||
'waitable_event_unittest.cc',
|
||||
'waitable_event_watcher_unittest.cc',
|
||||
'watchdog_unittest.cc',
|
||||
'win_util_unittest.cc',
|
||||
'wmi_util_unittest.cc',
|
||||
'word_iterator_unittest.cc',
|
||||
'worker_pool_unittest.cc',
|
||||
],
|
||||
'include_dirs': [
|
||||
# word_iterator.h (used by word_iterator_unittest.cc) leaks an ICU
|
||||
# #include for unicode/uchar.h. This should probably be cleaned up.
|
||||
'../third_party/icu38/public/common',
|
||||
],
|
||||
'dependencies': [
|
||||
'base',
|
||||
'base_gfx',
|
||||
'../skia/skia.gyp:skia',
|
||||
'../testing/gtest.gyp:gtest',
|
||||
],
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'sources!': [
|
||||
'file_version_info_unittest.cc',
|
||||
# Linux has an implementation of idle_timer, but it's unclear
|
||||
# if we want it yet, so leave it 'unported' for now.
|
||||
'idletimer_unittest.cc',
|
||||
'worker_pool_linux_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'../build/linux/system.gyp:gtk',
|
||||
'../build/linux/system.gyp:nss',
|
||||
],
|
||||
}],
|
||||
['OS != "mac"', {
|
||||
'sources!': [
|
||||
'mac_util_unittest.cc',
|
||||
],
|
||||
}],
|
||||
# This is needed to trigger the dll copy step on windows.
|
||||
# TODO(mark): This should not be necessary.
|
||||
['OS == "win"', {
|
||||
'dependencies': [
|
||||
'../third_party/icu38/icu38.gyp:icudata',
|
||||
],
|
||||
'sources!': [
|
||||
'data_pack_unittest.cc',
|
||||
'file_descriptor_shuffle_unittest.cc',
|
||||
],
|
||||
}, { # OS != "win"
|
||||
'sources!': [
|
||||
'gfx/native_theme_unittest.cc',
|
||||
'object_watcher_unittest.cc',
|
||||
'pe_image_unittest.cc',
|
||||
'scoped_bstr_win_unittest.cc',
|
||||
'scoped_comptr_win_unittest.cc',
|
||||
'scoped_variant_win_unittest.cc',
|
||||
'system_monitor_unittest.cc',
|
||||
'time_win_unittest.cc',
|
||||
'win_util_unittest.cc',
|
||||
'wmi_util_unittest.cc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'test_support_base',
|
||||
'type': '<(library)',
|
||||
'dependencies': [
|
||||
'base',
|
||||
'../testing/gtest.gyp:gtest',
|
||||
],
|
||||
'sources': [
|
||||
'perftimer.cc',
|
||||
'run_all_perftests.cc',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'defines': [
|
||||
'PERF_TEST',
|
||||
],
|
||||
},
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'dependencies': [
|
||||
# Needed to handle the #include chain:
|
||||
# base/perf_test_suite.h
|
||||
# base/test_suite.h
|
||||
# gtk/gtk.h
|
||||
'../build/linux/system.gyp:gtk',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
],
|
||||
'conditions': [
|
||||
[ 'OS == "win"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'debug_message',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'debug_message.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_drag_source.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BaseDragSource, public:
|
||||
|
||||
BaseDragSource::BaseDragSource() : ref_count_(0) {
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BaseDragSource, IDropSource implementation:
|
||||
|
||||
HRESULT BaseDragSource::QueryContinueDrag(BOOL escape_pressed,
|
||||
DWORD key_state) {
|
||||
if (escape_pressed) {
|
||||
OnDragSourceCancel();
|
||||
return DRAGDROP_S_CANCEL;
|
||||
}
|
||||
|
||||
if (!(key_state & MK_LBUTTON)) {
|
||||
OnDragSourceDrop();
|
||||
return DRAGDROP_S_DROP;
|
||||
}
|
||||
|
||||
OnDragSourceMove();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BaseDragSource::GiveFeedback(DWORD effect) {
|
||||
return DRAGDROP_S_USEDEFAULTCURSORS;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BaseDragSource, IUnknown implementation:
|
||||
|
||||
HRESULT BaseDragSource::QueryInterface(const IID& iid, void** object) {
|
||||
*object = NULL;
|
||||
if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDropSource)) {
|
||||
*object = this;
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG BaseDragSource::AddRef() {
|
||||
return ++ref_count_;
|
||||
}
|
||||
|
||||
ULONG BaseDragSource::Release() {
|
||||
if (--ref_count_ == 0) {
|
||||
delete this;
|
||||
return 0U;
|
||||
}
|
||||
return ref_count_;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_DRAG_SOURCE_H_
|
||||
#define BASE_BASE_DRAG_SOURCE_H_
|
||||
|
||||
#include <objidl.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// BaseDragSource
|
||||
//
|
||||
// A base IDropSource implementation. Handles notifications sent by an active
|
||||
// drag-drop operation as the user mouses over other drop targets on their
|
||||
// system. This object tells Windows whether or not the drag should continue,
|
||||
// and supplies the appropriate cursors.
|
||||
//
|
||||
class BaseDragSource : public IDropSource {
|
||||
public:
|
||||
BaseDragSource();
|
||||
virtual ~BaseDragSource() { }
|
||||
|
||||
// IDropSource implementation:
|
||||
HRESULT __stdcall QueryContinueDrag(BOOL escape_pressed, DWORD key_state);
|
||||
HRESULT __stdcall GiveFeedback(DWORD effect);
|
||||
|
||||
// IUnknown implementation:
|
||||
HRESULT __stdcall QueryInterface(const IID& iid, void** object);
|
||||
ULONG __stdcall AddRef();
|
||||
ULONG __stdcall Release();
|
||||
|
||||
protected:
|
||||
virtual void OnDragSourceCancel() { }
|
||||
virtual void OnDragSourceDrop() { }
|
||||
virtual void OnDragSourceMove() { }
|
||||
|
||||
private:
|
||||
LONG ref_count_;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(BaseDragSource);
|
||||
};
|
||||
|
||||
#endif // BASE_BASE_DRAG_SOURCE_H_
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_drop_target.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
IDropTargetHelper* BaseDropTarget::cached_drop_target_helper_ = NULL;
|
||||
int32 BaseDropTarget::drag_identity_ = 0;
|
||||
|
||||
BaseDropTarget::BaseDropTarget(HWND hwnd)
|
||||
: hwnd_(hwnd),
|
||||
suspend_(false),
|
||||
ref_count_(0) {
|
||||
DCHECK(hwnd);
|
||||
HRESULT result = RegisterDragDrop(hwnd, this);
|
||||
DCHECK(SUCCEEDED(result));
|
||||
}
|
||||
|
||||
BaseDropTarget::~BaseDropTarget() {
|
||||
}
|
||||
|
||||
// static
|
||||
IDropTargetHelper* BaseDropTarget::DropHelper() {
|
||||
if (!cached_drop_target_helper_) {
|
||||
CoCreateInstance(CLSID_DragDropHelper, 0, CLSCTX_INPROC_SERVER,
|
||||
IID_IDropTargetHelper,
|
||||
reinterpret_cast<void**>(&cached_drop_target_helper_));
|
||||
}
|
||||
return cached_drop_target_helper_;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BaseDropTarget, IDropTarget implementation:
|
||||
|
||||
HRESULT BaseDropTarget::DragEnter(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect) {
|
||||
// Tell the helper that we entered so it can update the drag image.
|
||||
IDropTargetHelper* drop_helper = DropHelper();
|
||||
if (drop_helper) {
|
||||
drop_helper->DragEnter(GetHWND(), data_object,
|
||||
reinterpret_cast<POINT*>(&cursor_position), *effect);
|
||||
}
|
||||
|
||||
// You can't drag and drop within the same HWND.
|
||||
if (suspend_) {
|
||||
*effect = DROPEFFECT_NONE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Update the drag identity, skipping 0.
|
||||
if (++drag_identity_ == 0)
|
||||
++drag_identity_;
|
||||
|
||||
current_data_object_ = data_object;
|
||||
POINT screen_pt = { cursor_position.x, cursor_position.y };
|
||||
*effect = OnDragEnter(current_data_object_, key_state, screen_pt, *effect);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BaseDropTarget::DragOver(DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect) {
|
||||
// Tell the helper that we moved over it so it can update the drag image.
|
||||
IDropTargetHelper* drop_helper = DropHelper();
|
||||
if (drop_helper)
|
||||
drop_helper->DragOver(reinterpret_cast<POINT*>(&cursor_position), *effect);
|
||||
|
||||
if (suspend_) {
|
||||
*effect = DROPEFFECT_NONE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
POINT screen_pt = { cursor_position.x, cursor_position.y };
|
||||
*effect = OnDragOver(current_data_object_, key_state, screen_pt, *effect);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BaseDropTarget::DragLeave() {
|
||||
// Tell the helper that we moved out of it so it can update the drag image.
|
||||
IDropTargetHelper* drop_helper = DropHelper();
|
||||
if (drop_helper)
|
||||
drop_helper->DragLeave();
|
||||
|
||||
OnDragLeave(current_data_object_);
|
||||
|
||||
current_data_object_ = NULL;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT BaseDropTarget::Drop(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect) {
|
||||
// Tell the helper that we dropped onto it so it can update the drag image.
|
||||
IDropTargetHelper* drop_helper = DropHelper();
|
||||
if (drop_helper) {
|
||||
drop_helper->Drop(current_data_object_,
|
||||
reinterpret_cast<POINT*>(&cursor_position), *effect);
|
||||
}
|
||||
|
||||
if (suspend_) {
|
||||
*effect = DROPEFFECT_NONE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
POINT screen_pt = { cursor_position.x, cursor_position.y };
|
||||
*effect = OnDrop(current_data_object_, key_state, screen_pt, *effect);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BaseDropTarget, IUnknown implementation:
|
||||
|
||||
HRESULT BaseDropTarget::QueryInterface(const IID& iid, void** object) {
|
||||
*object = NULL;
|
||||
if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDropTarget)) {
|
||||
*object = this;
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
ULONG BaseDropTarget::AddRef() {
|
||||
return ++ref_count_;
|
||||
}
|
||||
|
||||
ULONG BaseDropTarget::Release() {
|
||||
if (--ref_count_ == 0) {
|
||||
delete this;
|
||||
return 0U;
|
||||
}
|
||||
return ref_count_;
|
||||
}
|
||||
|
||||
DWORD BaseDropTarget::OnDragEnter(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect) {
|
||||
return DROPEFFECT_NONE;
|
||||
}
|
||||
|
||||
DWORD BaseDropTarget::OnDragOver(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect) {
|
||||
return DROPEFFECT_NONE;
|
||||
}
|
||||
|
||||
void BaseDropTarget::OnDragLeave(IDataObject* data_object) {
|
||||
}
|
||||
|
||||
DWORD BaseDropTarget::OnDrop(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect) {
|
||||
return DROPEFFECT_NONE;
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_DROP_TARGET_H_
|
||||
#define BASE_BASE_DROP_TARGET_H_
|
||||
|
||||
#include <objidl.h>
|
||||
|
||||
#include "base/ref_counted.h"
|
||||
|
||||
struct IDropTargetHelper;
|
||||
|
||||
// A DropTarget implementation that takes care of the nitty gritty
|
||||
// of dnd. While this class is concrete, subclasses will most likely
|
||||
// want to override various OnXXX methods.
|
||||
//
|
||||
// Because BaseDropTarget is ref counted you shouldn't delete it directly,
|
||||
// rather wrap it in a scoped_refptr. Be sure and invoke RevokeDragDrop(m_hWnd)
|
||||
// before the HWND is deleted too.
|
||||
//
|
||||
// This class is meant to be used in a STA and is not multithread-safe.
|
||||
class BaseDropTarget : public IDropTarget {
|
||||
public:
|
||||
// Create a new BaseDropTarget associating it with the given HWND.
|
||||
explicit BaseDropTarget(HWND hwnd);
|
||||
virtual ~BaseDropTarget();
|
||||
|
||||
// When suspend is set to |true|, the drop target does not receive drops from
|
||||
// drags initiated within the owning HWND.
|
||||
// TODO(beng): (http://b/1085385) figure out how we will handle legitimate
|
||||
// drag-drop operations within the same HWND, such as dragging
|
||||
// selected text to an edit field.
|
||||
void set_suspend(bool suspend) { suspend_ = suspend; }
|
||||
|
||||
// IDropTarget implementation:
|
||||
HRESULT __stdcall DragEnter(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect);
|
||||
HRESULT __stdcall DragOver(DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect);
|
||||
HRESULT __stdcall DragLeave();
|
||||
HRESULT __stdcall Drop(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINTL cursor_position,
|
||||
DWORD* effect);
|
||||
|
||||
// IUnknown implementation:
|
||||
HRESULT __stdcall QueryInterface(const IID& iid, void** object);
|
||||
ULONG __stdcall AddRef();
|
||||
ULONG __stdcall Release();
|
||||
|
||||
protected:
|
||||
// Returns the hosting HWND.
|
||||
HWND GetHWND() { return hwnd_; }
|
||||
|
||||
// Invoked when the cursor first moves over the hwnd during a dnd session.
|
||||
// This should return a bitmask of the supported drop operations:
|
||||
// DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_LINK and/or
|
||||
// DROPEFFECT_MOVE.
|
||||
virtual DWORD OnDragEnter(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect);
|
||||
|
||||
// Invoked when the cursor moves over the window during a dnd session.
|
||||
// This should return a bitmask of the supported drop operations:
|
||||
// DROPEFFECT_NONE, DROPEFFECT_COPY, DROPEFFECT_LINK and/or
|
||||
// DROPEFFECT_MOVE.
|
||||
virtual DWORD OnDragOver(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect);
|
||||
|
||||
// Invoked when the cursor moves outside the bounds of the hwnd during a
|
||||
// dnd session.
|
||||
virtual void OnDragLeave(IDataObject* data_object);
|
||||
|
||||
// Invoked when the drop ends on the window. This should return the operation
|
||||
// that was taken.
|
||||
virtual DWORD OnDrop(IDataObject* data_object,
|
||||
DWORD key_state,
|
||||
POINT cursor_position,
|
||||
DWORD effect);
|
||||
|
||||
// Return the drag identity.
|
||||
static int32 GetDragIdentity() { return drag_identity_; }
|
||||
|
||||
private:
|
||||
// Returns the cached drop helper, creating one if necessary. The returned
|
||||
// object is not addrefed. May return NULL if the object couldn't be created.
|
||||
static IDropTargetHelper* DropHelper();
|
||||
|
||||
// The data object currently being dragged over this drop target.
|
||||
scoped_refptr<IDataObject> current_data_object_;
|
||||
|
||||
// A helper object that is used to provide drag image support while the mouse
|
||||
// is dragging over the content area.
|
||||
//
|
||||
// DO NOT ACCESS DIRECTLY! Use DropHelper() instead, which will lazily create
|
||||
// this if it doesn't exist yet. This object can take tens of milliseconds to
|
||||
// create, and we don't want to block any window opening for this, especially
|
||||
// since often, DnD will never be used. Instead, we force this penalty to the
|
||||
// first time it is actually used.
|
||||
static IDropTargetHelper* cached_drop_target_helper_;
|
||||
|
||||
// The drag identity (id). An up-counter that increases when the cursor first
|
||||
// moves over the HWND in a DnD session (OnDragEnter). 0 is reserved to mean
|
||||
// the "no/unknown" identity, and is used for initialization. The identity is
|
||||
// sent to the renderer in drag enter notifications. Note: the identity value
|
||||
// is passed over the renderer NPAPI interface to gears, so use int32 instead
|
||||
// of int here.
|
||||
static int32 drag_identity_;
|
||||
|
||||
// The HWND of the source. This HWND is used to determine coordinates for
|
||||
// mouse events that are sent to the renderer notifying various drag states.
|
||||
HWND hwnd_;
|
||||
|
||||
// Whether or not we are currently processing drag notifications for drags
|
||||
// initiated in this window.
|
||||
bool suspend_;
|
||||
|
||||
LONG ref_count_;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(BaseDropTarget);
|
||||
};
|
||||
|
||||
#endif // BASE_BASE_DROP_TARGET_H_
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_paths.h"
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/path_service.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
bool PathProvider(int key, FilePath* result) {
|
||||
// NOTE: DIR_CURRENT is a special cased in PathService::Get
|
||||
|
||||
FilePath cur;
|
||||
switch (key) {
|
||||
case base::DIR_EXE:
|
||||
PathService::Get(base::FILE_EXE, &cur);
|
||||
cur = cur.DirName();
|
||||
break;
|
||||
case base::DIR_MODULE:
|
||||
PathService::Get(base::FILE_MODULE, &cur);
|
||||
cur = cur.DirName();
|
||||
break;
|
||||
case base::DIR_TEMP:
|
||||
if (!file_util::GetTempDir(&cur))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = cur;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_PATHS_H_
|
||||
#define BASE_BASE_PATHS_H_
|
||||
|
||||
// This file declares path keys for the base module. These can be used with
|
||||
// the PathService to access various special directories and files.
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#if defined(OS_WIN)
|
||||
#include "base/base_paths_win.h"
|
||||
#elif defined(OS_MACOSX)
|
||||
#include "base/base_paths_mac.h"
|
||||
#elif defined(OS_LINUX)
|
||||
#include "base/base_paths_linux.h"
|
||||
#endif
|
||||
#include "base/path_service.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
enum {
|
||||
PATH_START = 0,
|
||||
|
||||
DIR_CURRENT, // current directory
|
||||
DIR_EXE, // directory containing FILE_EXE
|
||||
DIR_MODULE, // directory containing FILE_MODULE
|
||||
DIR_TEMP, // temporary directory
|
||||
PATH_END
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_BASE_PATHS_H_
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_paths_linux.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/string_piece.h"
|
||||
#include "base/sys_string_conversions.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
bool PathProviderLinux(int key, FilePath* result) {
|
||||
FilePath path;
|
||||
switch (key) {
|
||||
case base::FILE_EXE:
|
||||
case base::FILE_MODULE: { // TODO(evanm): is this correct?
|
||||
char bin_dir[PATH_MAX + 1];
|
||||
int bin_dir_size = readlink("/proc/self/exe", bin_dir, PATH_MAX);
|
||||
if (bin_dir_size < 0 || bin_dir_size > PATH_MAX) {
|
||||
NOTREACHED() << "Unable to resolve /proc/self/exe.";
|
||||
return false;
|
||||
}
|
||||
bin_dir[bin_dir_size] = 0;
|
||||
*result = FilePath(bin_dir);
|
||||
return true;
|
||||
}
|
||||
case base::DIR_SOURCE_ROOT:
|
||||
// On linux, unit tests execute two levels deep from the source root.
|
||||
// For example: sconsbuild/{Debug|Release}/net_unittest
|
||||
if (!PathService::Get(base::DIR_EXE, &path))
|
||||
return false;
|
||||
path = path.Append(FilePath::kParentDirectory)
|
||||
.Append(FilePath::kParentDirectory);
|
||||
*result = path;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_PATHS_LINUX_H_
|
||||
#define BASE_BASE_PATHS_LINUX_H_
|
||||
|
||||
// This file declares Linux-specific path keys for the base module.
|
||||
// These can be used with the PathService to access various special
|
||||
// directories and files.
|
||||
|
||||
namespace base {
|
||||
|
||||
enum {
|
||||
PATH_LINUX_START = 200,
|
||||
|
||||
FILE_EXE, // Path and filename of the current executable.
|
||||
FILE_MODULE, // Path and filename of the module containing the code for the
|
||||
// PathService (which could differ from FILE_EXE if the
|
||||
// PathService were compiled into a shared object, for example).
|
||||
DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful
|
||||
// for tests that need to locate various resources. It
|
||||
// should not be used outside of test code.
|
||||
|
||||
PATH_LINUX_END
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_BASE_PATHS_LINUX_H_
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_PATHS_MAC_H_
|
||||
#define BASE_BASE_PATHS_MAC_H_
|
||||
|
||||
// This file declares Mac-specific path keys for the base module.
|
||||
// These can be used with the PathService to access various special
|
||||
// directories and files.
|
||||
|
||||
namespace base {
|
||||
|
||||
enum {
|
||||
PATH_MAC_START = 200,
|
||||
|
||||
FILE_EXE, // path and filename of the current executable
|
||||
FILE_MODULE, // path and filename of the module containing the code for the
|
||||
// PathService (which could differ from FILE_EXE if the
|
||||
// PathService were compiled into a library, for example)
|
||||
DIR_APP_DATA, // ~/Library/Application Support/Google/Chrome
|
||||
DIR_LOCAL_APP_DATA, // same as above (can we remove?)
|
||||
DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful
|
||||
// for tests that need to locate various resources. It
|
||||
// should not be used outside of test code.
|
||||
PATH_MAC_END
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_BASE_PATHS_MAC_H_
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_paths_mac.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/mac_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
bool PathProviderMac(int key, FilePath* result) {
|
||||
std::string cur;
|
||||
switch (key) {
|
||||
case base::FILE_EXE:
|
||||
case base::FILE_MODULE: {
|
||||
// Executable path can have relative references ("..") depending on
|
||||
// how the app was launched.
|
||||
NSString* path =
|
||||
[[[NSBundle mainBundle] executablePath] stringByStandardizingPath];
|
||||
cur = [path fileSystemRepresentation];
|
||||
break;
|
||||
}
|
||||
case base::DIR_SOURCE_ROOT: {
|
||||
FilePath path;
|
||||
PathService::Get(base::DIR_EXE, &path);
|
||||
if (mac_util::AmIBundled()) {
|
||||
// The bundled app executables (Chromium, TestShell, etc) live five
|
||||
// levels down, eg:
|
||||
// src/xcodebuild/{Debug|Release}/Chromium.app/Contents/MacOS/Chromium.
|
||||
path = path.DirName();
|
||||
path = path.DirName();
|
||||
path = path.DirName();
|
||||
path = path.DirName();
|
||||
*result = path.DirName();
|
||||
} else {
|
||||
// Unit tests execute two levels deep from the source root, eg:
|
||||
// src/xcodebuild/{Debug|Release}/base_unittests
|
||||
path = path.DirName();
|
||||
*result = path.DirName();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = FilePath(cur);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_paths_win.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/win_util.h"
|
||||
|
||||
// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace base {
|
||||
|
||||
bool PathProviderWin(int key, FilePath* result) {
|
||||
|
||||
// We need to go compute the value. It would be nice to support paths with
|
||||
// names longer than MAX_PATH, but the system functions don't seem to be
|
||||
// designed for it either, with the exception of GetTempPath (but other
|
||||
// things will surely break if the temp path is too long, so we don't bother
|
||||
// handling it.
|
||||
wchar_t system_buffer[MAX_PATH];
|
||||
system_buffer[0] = 0;
|
||||
|
||||
FilePath cur;
|
||||
std::wstring wstring_path;
|
||||
switch (key) {
|
||||
case base::FILE_EXE:
|
||||
GetModuleFileName(NULL, system_buffer, MAX_PATH);
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::FILE_MODULE: {
|
||||
// the resource containing module is assumed to be the one that
|
||||
// this code lives in, whether that's a dll or exe
|
||||
HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||
GetModuleFileName(this_module, system_buffer, MAX_PATH);
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
}
|
||||
case base::DIR_WINDOWS:
|
||||
GetWindowsDirectory(system_buffer, MAX_PATH);
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_SYSTEM:
|
||||
GetSystemDirectory(system_buffer, MAX_PATH);
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_PROGRAM_FILES:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
|
||||
SHGFP_TYPE_CURRENT, system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_IE_INTERNET_CACHE:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL,
|
||||
SHGFP_TYPE_CURRENT, system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_COMMON_START_MENU:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_COMMON_PROGRAMS, NULL,
|
||||
SHGFP_TYPE_CURRENT, system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_START_MENU:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_PROGRAMS, NULL,
|
||||
SHGFP_TYPE_CURRENT, system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_APP_DATA:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT,
|
||||
system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_LOCAL_APP_DATA_LOW:
|
||||
if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) {
|
||||
return false;
|
||||
}
|
||||
// TODO(nsylvain): We should use SHGetKnownFolderPath instead. Bug 1281128
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT,
|
||||
system_buffer)))
|
||||
return false;
|
||||
wstring_path = system_buffer;
|
||||
file_util::UpOneDirectory(&wstring_path);
|
||||
file_util::AppendToPath(&wstring_path, L"LocalLow");
|
||||
cur = FilePath(wstring_path);
|
||||
break;
|
||||
case base::DIR_LOCAL_APP_DATA:
|
||||
if (FAILED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL,
|
||||
SHGFP_TYPE_CURRENT, system_buffer)))
|
||||
return false;
|
||||
cur = FilePath(system_buffer);
|
||||
break;
|
||||
case base::DIR_SOURCE_ROOT:
|
||||
// On Windows, unit tests execute two levels deep from the source root.
|
||||
// For example: chrome/{Debug|Release}/ui_tests.exe
|
||||
PathService::Get(base::DIR_EXE, &wstring_path);
|
||||
file_util::UpOneDirectory(&wstring_path);
|
||||
file_util::UpOneDirectory(&wstring_path);
|
||||
cur = FilePath(wstring_path);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = cur;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASE_PATHS_WIN_H__
|
||||
#define BASE_BASE_PATHS_WIN_H__
|
||||
|
||||
// This file declares windows-specific path keys for the base module.
|
||||
// These can be used with the PathService to access various special
|
||||
// directories and files.
|
||||
|
||||
namespace base {
|
||||
|
||||
enum {
|
||||
PATH_WIN_START = 100,
|
||||
|
||||
FILE_EXE, // path and filename of the current executable
|
||||
FILE_MODULE, // path and filename of the module containing the code for the
|
||||
// PathService (which could differ from FILE_EXE if the
|
||||
// PathService were compiled into a DLL, for example)
|
||||
DIR_WINDOWS, // Windows directory, usually "c:\windows"
|
||||
DIR_SYSTEM, // Usually c:\windows\system32"
|
||||
DIR_PROGRAM_FILES, // Usually c:\program files
|
||||
|
||||
DIR_IE_INTERNET_CACHE, // Temporary Internet Files directory.
|
||||
DIR_COMMON_START_MENU, // Usually "C:\Documents and Settings\All Users\
|
||||
// Start Menu\Programs"
|
||||
DIR_START_MENU, // Usually "C:\Documents and Settings\<user>\
|
||||
// Start Menu\Programs"
|
||||
DIR_APP_DATA, // Application Data directory under the user profile.
|
||||
DIR_LOCAL_APP_DATA_LOW, // Local AppData directory for low integrity level.
|
||||
DIR_LOCAL_APP_DATA, // "Local Settings\Application Data" directory under the
|
||||
// user profile.
|
||||
DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful
|
||||
// for tests that need to locate various resources. It
|
||||
// should not be used outside of test code.
|
||||
PATH_WIN_END
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_BASE_PATHS_WIN_H__
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/base_switches.h"
|
||||
|
||||
namespace switches {
|
||||
|
||||
// If the program includes chrome/common/debug_on_start.h, the process will
|
||||
// start the JIT system-registered debugger on itself and will wait for 60
|
||||
// seconds for the debugger to attach to itself. Then a break point will be hit.
|
||||
const wchar_t kDebugOnStart[] = L"debug-on-start";
|
||||
|
||||
// Will wait for 60 seconds for a debugger to come to attach to the process.
|
||||
const wchar_t kWaitForDebugger[] = L"wait-for-debugger";
|
||||
|
||||
// Suppresses all error dialogs when present.
|
||||
const wchar_t kNoErrorDialogs[] = L"noerrdialogs";
|
||||
|
||||
// Disables the crash reporting.
|
||||
const wchar_t kDisableBreakpad[] = L"disable-breakpad";
|
||||
|
||||
// Generates full memory crash dump.
|
||||
const wchar_t kFullMemoryCrashReport[] = L"full-memory-crash-report";
|
||||
|
||||
// The value of this switch determines whether the process is started as a
|
||||
// renderer or plugin host. If it's empty, it's the browser.
|
||||
const wchar_t kProcessType[] = L"type";
|
||||
|
||||
// Enable DCHECKs in release mode.
|
||||
const wchar_t kEnableDCHECK[] = L"enable-dcheck";
|
||||
|
||||
// Refuse to make HTTP connections and refuse to accept certificate errors.
|
||||
// For more information about the design of this feature, please see
|
||||
//
|
||||
// ForceHTTPS: Protecting High-Security Web Sites from Network Attacks
|
||||
// Collin Jackson and Adam Barth
|
||||
// In Proc. of the 17th International World Wide Web Conference (WWW 2008)
|
||||
//
|
||||
// Available at http://www.adambarth.com/papers/2008/jackson-barth.pdf
|
||||
const wchar_t kForceHTTPS[] = L"force-https";
|
||||
|
||||
} // namespace switches
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Defines all the "base" command-line switches.
|
||||
|
||||
#ifndef BASE_BASE_SWITCHES_H_
|
||||
#define BASE_BASE_SWITCHES_H_
|
||||
|
||||
namespace switches {
|
||||
|
||||
extern const wchar_t kDebugOnStart[];
|
||||
extern const wchar_t kWaitForDebugger[];
|
||||
extern const wchar_t kDisableBreakpad[];
|
||||
extern const wchar_t kFullMemoryCrashReport[];
|
||||
extern const wchar_t kNoErrorDialogs[];
|
||||
extern const wchar_t kProcessType[];
|
||||
extern const wchar_t kEnableDCHECK[];
|
||||
extern const wchar_t kForceHTTPS[];
|
||||
|
||||
} // namespace switches
|
||||
|
||||
#endif // BASE_BASE_SWITCHES_H_
|
|
@ -0,0 +1,335 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_BASICTYPES_H_
|
||||
#define BASE_BASICTYPES_H_
|
||||
|
||||
#include <limits.h> // So we can set the bounds of our types
|
||||
#include <stddef.h> // For size_t
|
||||
#include <string.h> // for memcpy
|
||||
|
||||
#include "base/port.h" // Types that only need exist on certain systems
|
||||
|
||||
#ifndef COMPILER_MSVC
|
||||
// stdint.h is part of C99 but MSVC doesn't have it.
|
||||
#include <stdint.h> // For intptr_t.
|
||||
#endif
|
||||
|
||||
typedef signed char schar;
|
||||
typedef signed char int8;
|
||||
typedef short int16;
|
||||
// TODO(mbelshe) Remove these type guards. These are
|
||||
// temporary to avoid conflicts with npapi.h.
|
||||
#ifndef _INT32
|
||||
#define _INT32
|
||||
typedef int int32;
|
||||
#endif
|
||||
typedef long long int64;
|
||||
|
||||
// NOTE: unsigned types are DANGEROUS in loops and other arithmetical
|
||||
// places. Use the signed types unless your variable represents a bit
|
||||
// pattern (eg a hash value) or you really need the extra bit. Do NOT
|
||||
// use 'unsigned' to express "this value should always be positive";
|
||||
// use assertions for this.
|
||||
|
||||
typedef unsigned char uint8;
|
||||
typedef unsigned short uint16;
|
||||
// TODO(mbelshe) Remove these type guards. These are
|
||||
// temporary to avoid conflicts with npapi.h.
|
||||
#ifndef _UINT32
|
||||
#define _UINT32
|
||||
typedef unsigned int uint32;
|
||||
#endif
|
||||
typedef unsigned long long uint64;
|
||||
|
||||
// A type to represent a Unicode code-point value. As of Unicode 4.0,
|
||||
// such values require up to 21 bits.
|
||||
// (For type-checking on pointers, make this explicitly signed,
|
||||
// and it should always be the signed version of whatever int32 is.)
|
||||
typedef signed int char32;
|
||||
|
||||
const uint8 kuint8max = (( uint8) 0xFF);
|
||||
const uint16 kuint16max = ((uint16) 0xFFFF);
|
||||
const uint32 kuint32max = ((uint32) 0xFFFFFFFF);
|
||||
const uint64 kuint64max = ((uint64) GG_LONGLONG(0xFFFFFFFFFFFFFFFF));
|
||||
const int8 kint8min = (( int8) 0x80);
|
||||
const int8 kint8max = (( int8) 0x7F);
|
||||
const int16 kint16min = (( int16) 0x8000);
|
||||
const int16 kint16max = (( int16) 0x7FFF);
|
||||
const int32 kint32min = (( int32) 0x80000000);
|
||||
const int32 kint32max = (( int32) 0x7FFFFFFF);
|
||||
const int64 kint64min = (( int64) GG_LONGLONG(0x8000000000000000));
|
||||
const int64 kint64max = (( int64) GG_LONGLONG(0x7FFFFFFFFFFFFFFF));
|
||||
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
// An older, deprecated, politically incorrect name for the above.
|
||||
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName(); \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
//
|
||||
// One caveat is that arraysize() doesn't accept any array of an
|
||||
// anonymous type or a type defined inside a function. In these rare
|
||||
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
|
||||
// due to a limitation in C++'s template system. The limitation might
|
||||
// eventually be removed, but it hasn't happened yet.
|
||||
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(T (&array)[N]))[N];
|
||||
|
||||
// That gcc wants both of these prototypes seems mysterious. VC, for
|
||||
// its part, can't decide which to use (another mystery). Matching of
|
||||
// template overloads: the final frontier.
|
||||
#ifndef _MSC_VER
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(const T (&array)[N]))[N];
|
||||
#endif
|
||||
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
|
||||
// ARRAYSIZE_UNSAFE performs essentially the same calculation as arraysize,
|
||||
// but can be used on anonymous types or types defined inside
|
||||
// functions. It's less safe than arraysize as it accepts some
|
||||
// (although not all) pointers. Therefore, you should use arraysize
|
||||
// whenever possible.
|
||||
//
|
||||
// The expression ARRAYSIZE_UNSAFE(a) is a compile-time constant of type
|
||||
// size_t.
|
||||
//
|
||||
// ARRAYSIZE_UNSAFE catches a few type errors. If you see a compiler error
|
||||
//
|
||||
// "warning: division by zero in ..."
|
||||
//
|
||||
// when using ARRAYSIZE_UNSAFE, you are (wrongfully) giving it a pointer.
|
||||
// You should only use ARRAYSIZE_UNSAFE on statically allocated arrays.
|
||||
//
|
||||
// The following comments are on the implementation details, and can
|
||||
// be ignored by the users.
|
||||
//
|
||||
// ARRAYSIZE_UNSAFE(arr) works by inspecting sizeof(arr) (the # of bytes in
|
||||
// the array) and sizeof(*(arr)) (the # of bytes in one array
|
||||
// element). If the former is divisible by the latter, perhaps arr is
|
||||
// indeed an array, in which case the division result is the # of
|
||||
// elements in the array. Otherwise, arr cannot possibly be an array,
|
||||
// and we generate a compiler error to prevent the code from
|
||||
// compiling.
|
||||
//
|
||||
// Since the size of bool is implementation-defined, we need to cast
|
||||
// !(sizeof(a) & sizeof(*(a))) to size_t in order to ensure the final
|
||||
// result has type size_t.
|
||||
//
|
||||
// This macro is not perfect as it wrongfully accepts certain
|
||||
// pointers, namely where the pointer size is divisible by the pointee
|
||||
// size. Since all our code has to go through a 32-bit compiler,
|
||||
// where a pointer is 4 bytes, this means all pointers to a type whose
|
||||
// size is 3 or greater than 4 will be (righteously) rejected.
|
||||
|
||||
#define ARRAYSIZE_UNSAFE(a) \
|
||||
((sizeof(a) / sizeof(*(a))) / \
|
||||
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
|
||||
|
||||
|
||||
// Use implicit_cast as a safe version of static_cast or const_cast
|
||||
// for upcasting in the type hierarchy (i.e. casting a pointer to Foo
|
||||
// to a pointer to SuperclassOfFoo or casting a pointer to Foo to
|
||||
// a const pointer to Foo).
|
||||
// When you use implicit_cast, the compiler checks that the cast is safe.
|
||||
// Such explicit implicit_casts are necessary in surprisingly many
|
||||
// situations where C++ demands an exact type match instead of an
|
||||
// argument type convertable to a target type.
|
||||
//
|
||||
// The From type can be inferred, so the preferred syntax for using
|
||||
// implicit_cast is the same as for static_cast etc.:
|
||||
//
|
||||
// implicit_cast<ToType>(expr)
|
||||
//
|
||||
// implicit_cast would have been part of the C++ standard library,
|
||||
// but the proposal was submitted too late. It will probably make
|
||||
// its way into the language in the future.
|
||||
template<typename To, typename From>
|
||||
inline To implicit_cast(From const &f) {
|
||||
return f;
|
||||
}
|
||||
|
||||
// The COMPILE_ASSERT macro can be used to verify that a compile time
|
||||
// expression is true. For example, you could use it to verify the
|
||||
// size of a static array:
|
||||
//
|
||||
// COMPILE_ASSERT(ARRAYSIZE_UNSAFE(content_type_names) == CONTENT_NUM_TYPES,
|
||||
// content_type_names_incorrect_size);
|
||||
//
|
||||
// or to make sure a struct is smaller than a certain size:
|
||||
//
|
||||
// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);
|
||||
//
|
||||
// The second argument to the macro is the name of the variable. If
|
||||
// the expression is false, most compilers will issue a warning/error
|
||||
// containing the name of the variable.
|
||||
|
||||
template <bool>
|
||||
struct CompileAssert {
|
||||
};
|
||||
|
||||
#undef COMPILE_ASSERT
|
||||
#define COMPILE_ASSERT(expr, msg) \
|
||||
typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
|
||||
|
||||
// Implementation details of COMPILE_ASSERT:
|
||||
//
|
||||
// - COMPILE_ASSERT works by defining an array type that has -1
|
||||
// elements (and thus is invalid) when the expression is false.
|
||||
//
|
||||
// - The simpler definition
|
||||
//
|
||||
// #define COMPILE_ASSERT(expr, msg) typedef char msg[(expr) ? 1 : -1]
|
||||
//
|
||||
// does not work, as gcc supports variable-length arrays whose sizes
|
||||
// are determined at run-time (this is gcc's extension and not part
|
||||
// of the C++ standard). As a result, gcc fails to reject the
|
||||
// following code with the simple definition:
|
||||
//
|
||||
// int foo;
|
||||
// COMPILE_ASSERT(foo, msg); // not supposed to compile as foo is
|
||||
// // not a compile-time constant.
|
||||
//
|
||||
// - By using the type CompileAssert<(bool(expr))>, we ensures that
|
||||
// expr is a compile-time constant. (Template arguments must be
|
||||
// determined at compile-time.)
|
||||
//
|
||||
// - The outter parentheses in CompileAssert<(bool(expr))> are necessary
|
||||
// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written
|
||||
//
|
||||
// CompileAssert<bool(expr)>
|
||||
//
|
||||
// instead, these compilers will refuse to compile
|
||||
//
|
||||
// COMPILE_ASSERT(5 > 0, some_message);
|
||||
//
|
||||
// (They seem to think the ">" in "5 > 0" marks the end of the
|
||||
// template argument list.)
|
||||
//
|
||||
// - The array size is (bool(expr) ? 1 : -1), instead of simply
|
||||
//
|
||||
// ((expr) ? 1 : -1).
|
||||
//
|
||||
// This is to avoid running into a bug in MS VC 7.1, which
|
||||
// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1.
|
||||
|
||||
|
||||
// MetatagId refers to metatag-id that we assign to
|
||||
// each metatag <name, value> pair..
|
||||
typedef uint32 MetatagId;
|
||||
|
||||
// Argument type used in interfaces that can optionally take ownership
|
||||
// of a passed in argument. If TAKE_OWNERSHIP is passed, the called
|
||||
// object takes ownership of the argument. Otherwise it does not.
|
||||
enum Ownership {
|
||||
DO_NOT_TAKE_OWNERSHIP,
|
||||
TAKE_OWNERSHIP
|
||||
};
|
||||
|
||||
// bit_cast<Dest,Source> is a template function that implements the
|
||||
// equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in
|
||||
// very low-level functions like the protobuf library and fast math
|
||||
// support.
|
||||
//
|
||||
// float f = 3.14159265358979;
|
||||
// int i = bit_cast<int32>(f);
|
||||
// // i = 0x40490fdb
|
||||
//
|
||||
// The classical address-casting method is:
|
||||
//
|
||||
// // WRONG
|
||||
// float f = 3.14159265358979; // WRONG
|
||||
// int i = * reinterpret_cast<int*>(&f); // WRONG
|
||||
//
|
||||
// The address-casting method actually produces undefined behavior
|
||||
// according to ISO C++ specification section 3.10 -15 -. Roughly, this
|
||||
// section says: if an object in memory has one type, and a program
|
||||
// accesses it with a different type, then the result is undefined
|
||||
// behavior for most values of "different type".
|
||||
//
|
||||
// This is true for any cast syntax, either *(int*)&f or
|
||||
// *reinterpret_cast<int*>(&f). And it is particularly true for
|
||||
// conversions betweeen integral lvalues and floating-point lvalues.
|
||||
//
|
||||
// The purpose of 3.10 -15- is to allow optimizing compilers to assume
|
||||
// that expressions with different types refer to different memory. gcc
|
||||
// 4.0.1 has an optimizer that takes advantage of this. So a
|
||||
// non-conforming program quietly produces wildly incorrect output.
|
||||
//
|
||||
// The problem is not the use of reinterpret_cast. The problem is type
|
||||
// punning: holding an object in memory of one type and reading its bits
|
||||
// back using a different type.
|
||||
//
|
||||
// The C++ standard is more subtle and complex than this, but that
|
||||
// is the basic idea.
|
||||
//
|
||||
// Anyways ...
|
||||
//
|
||||
// bit_cast<> calls memcpy() which is blessed by the standard,
|
||||
// especially by the example in section 3.9 . Also, of course,
|
||||
// bit_cast<> wraps up the nasty logic in one place.
|
||||
//
|
||||
// Fortunately memcpy() is very fast. In optimized mode, with a
|
||||
// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline
|
||||
// code with the minimal amount of data movement. On a 32-bit system,
|
||||
// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8)
|
||||
// compiles to two loads and two stores.
|
||||
//
|
||||
// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1.
|
||||
//
|
||||
// WARNING: if Dest or Source is a non-POD type, the result of the memcpy
|
||||
// is likely to surprise you.
|
||||
|
||||
template <class Dest, class Source>
|
||||
inline Dest bit_cast(const Source& source) {
|
||||
// Compile time assertion: sizeof(Dest) == sizeof(Source)
|
||||
// A compile error here means your Dest and Source have different sizes.
|
||||
typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1];
|
||||
|
||||
Dest dest;
|
||||
memcpy(&dest, &source, sizeof(dest));
|
||||
return dest;
|
||||
}
|
||||
|
||||
// The following enum should be used only as a constructor argument to indicate
|
||||
// that the variable has static storage class, and that the constructor should
|
||||
// do nothing to its state. It indicates to the reader that it is legal to
|
||||
// declare a static instance of the class, provided the constructor is given
|
||||
// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a
|
||||
// static variable that has a constructor or a destructor because invocation
|
||||
// order is undefined. However, IF the type can be initialized by filling with
|
||||
// zeroes (which the loader does for static variables), AND the destructor also
|
||||
// does nothing to the storage, AND there are no virtual methods, then a
|
||||
// constructor declared as
|
||||
// explicit MyClass(base::LinkerInitialized x) {}
|
||||
// and invoked as
|
||||
// static MyClass my_variable_name(base::LINKER_INITIALIZED);
|
||||
namespace base {
|
||||
enum LinkerInitialized { LINKER_INITIALIZED };
|
||||
} // base
|
||||
|
||||
|
||||
#endif // BASE_BASICTYPES_H_
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="Windows-1252"?>
|
||||
<VisualStudioPropertySheet
|
||||
ProjectType="Visual C++"
|
||||
Version="8.00"
|
||||
Name="base"
|
||||
InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops"
|
||||
>
|
||||
</VisualStudioPropertySheet>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="Windows-1252"?>
|
||||
<VisualStudioPropertySheet
|
||||
ProjectType="Visual C++"
|
||||
Version="8.00"
|
||||
Name="base_gfx"
|
||||
InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\libjpeg\using_libjpeg.vsprops;$(SolutionDir)..\third_party\libpng\using_libpng.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;..\..\skia\using_skia.vsprops"
|
||||
>
|
||||
</VisualStudioPropertySheet>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="Windows-1252"?>
|
||||
<VisualStudioPropertySheet
|
||||
ProjectType="Visual C++"
|
||||
Version="8.00"
|
||||
Name="base_unittests"
|
||||
InheritedPropertySheets=".\base_gfx.vsprops"
|
||||
>
|
||||
</VisualStudioPropertySheet>
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
// We define BZ_NO_STDIO in third_party/bzip2 to remove its internal STDERR
|
||||
// error reporting. This requires us to export our own error handler.
|
||||
extern "C"
|
||||
void bz_internal_error(int errcode) {
|
||||
CHECK(false) << "bzip2 internal error: " << errcode;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A compromised renderer could send us bad data, so validate it.
|
||||
bool IsBitmapSafe(const Clipboard::ObjectMapParams& params) {
|
||||
const gfx::Size* size =
|
||||
reinterpret_cast<const gfx::Size*>(&(params[1].front()));
|
||||
return params[0].size() ==
|
||||
static_cast<size_t>(size->width() * size->height() * 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Clipboard::DispatchObject(ObjectType type, const ObjectMapParams& params) {
|
||||
switch (type) {
|
||||
case CBF_TEXT:
|
||||
WriteText(&(params[0].front()), params[0].size());
|
||||
break;
|
||||
|
||||
case CBF_HTML:
|
||||
if (params.size() == 2)
|
||||
WriteHTML(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
else
|
||||
WriteHTML(&(params[0].front()), params[0].size(), NULL, 0);
|
||||
break;
|
||||
|
||||
case CBF_BOOKMARK:
|
||||
WriteBookmark(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
break;
|
||||
|
||||
case CBF_LINK:
|
||||
WriteHyperlink(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
break;
|
||||
|
||||
case CBF_FILES:
|
||||
WriteFiles(&(params[0].front()), params[0].size());
|
||||
break;
|
||||
|
||||
case CBF_WEBKIT:
|
||||
WriteWebSmartPaste();
|
||||
break;
|
||||
|
||||
#if defined(OS_WIN) || defined(OS_LINUX)
|
||||
case CBF_BITMAP:
|
||||
if (!IsBitmapSafe(params))
|
||||
return;
|
||||
WriteBitmap(&(params[0].front()), &(params[1].front()));
|
||||
break;
|
||||
#endif // defined(OS_WIN) || defined(OS_LINUX)
|
||||
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CLIPBOARD_H_
|
||||
#define BASE_CLIPBOARD_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/process.h"
|
||||
#include "base/string16.h"
|
||||
#include "base/gfx/size.h"
|
||||
|
||||
class Clipboard {
|
||||
public:
|
||||
typedef std::string FormatType;
|
||||
#if defined(OS_LINUX)
|
||||
typedef struct _GtkClipboard GtkClipboard;
|
||||
typedef std::map<FormatType, std::pair<char*, size_t> > TargetMap;
|
||||
#endif
|
||||
|
||||
// ObjectType designates the type of data to be stored in the clipboard. This
|
||||
// designation is shared across all OSes. The system-specific designation
|
||||
// is defined by FormatType. A single ObjectType might be represented by
|
||||
// several system-specific FormatTypes. For example, on Linux the CBF_TEXT
|
||||
// ObjectType maps to "text/plain", "STRING", and several other formats. On
|
||||
// windows it maps to CF_UNICODETEXT.
|
||||
enum ObjectType {
|
||||
CBF_TEXT,
|
||||
CBF_HTML,
|
||||
CBF_BOOKMARK,
|
||||
CBF_LINK,
|
||||
CBF_FILES,
|
||||
CBF_WEBKIT,
|
||||
CBF_BITMAP,
|
||||
CBF_SMBITMAP // bitmap from shared memory
|
||||
};
|
||||
|
||||
// ObjectMap is a map from ObjectType to associated data.
|
||||
// The data is organized differently for each ObjectType. The following
|
||||
// table summarizes what kind of data is stored for each key.
|
||||
// * indicates an optional argument.
|
||||
//
|
||||
// Key Arguments Type
|
||||
// -------------------------------------
|
||||
// CBF_TEXT text char array
|
||||
// CBF_HTML html char array
|
||||
// url* char array
|
||||
// CBF_BOOKMARK html char array
|
||||
// url char array
|
||||
// CBF_LINK html char array
|
||||
// url char array
|
||||
// CBF_FILES files char array representing multiple files.
|
||||
// Filenames are separated by null characters and
|
||||
// the final filename is double null terminated.
|
||||
// The filenames are encoded in platform-specific
|
||||
// encoding.
|
||||
// CBF_WEBKIT none empty vector
|
||||
// CBF_BITMAP pixels byte array
|
||||
// size gfx::Size struct
|
||||
// CBF_SMBITMAP shared_mem shared memory handle
|
||||
// size gfx::Size struct
|
||||
typedef std::vector<char> ObjectMapParam;
|
||||
typedef std::vector<ObjectMapParam> ObjectMapParams;
|
||||
typedef std::map<int /* ObjectType */, ObjectMapParams> ObjectMap;
|
||||
|
||||
Clipboard();
|
||||
~Clipboard();
|
||||
|
||||
// Write a bunch of objects to the system clipboard. Copies are made of the
|
||||
// contents of |objects|. On Windows they are copied to the system clipboard.
|
||||
// On linux they are copied into a structure owned by the Clipboard object and
|
||||
// kept until the system clipboard is set again.
|
||||
void WriteObjects(const ObjectMap& objects);
|
||||
|
||||
// Behaves as above. If there is some shared memory handle passed as one of
|
||||
// the objects, it came from the process designated by |process|. This will
|
||||
// assist in turning it into a shared memory region that the current process
|
||||
// can use.
|
||||
void WriteObjects(const ObjectMap& objects, base::ProcessHandle process);
|
||||
|
||||
// Tests whether the clipboard contains a certain format
|
||||
bool IsFormatAvailable(const FormatType& format) const;
|
||||
|
||||
// Reads UNICODE text from the clipboard, if available.
|
||||
void ReadText(string16* result) const;
|
||||
|
||||
// Reads ASCII text from the clipboard, if available.
|
||||
void ReadAsciiText(std::string* result) const;
|
||||
|
||||
// Reads HTML from the clipboard, if available.
|
||||
void ReadHTML(string16* markup, std::string* src_url) const;
|
||||
|
||||
// Reads a bookmark from the clipboard, if available.
|
||||
void ReadBookmark(string16* title, std::string* url) const;
|
||||
|
||||
// Reads a file or group of files from the clipboard, if available, into the
|
||||
// out parameter.
|
||||
void ReadFile(FilePath* file) const;
|
||||
void ReadFiles(std::vector<FilePath>* files) const;
|
||||
|
||||
// Get format Identifiers for various types.
|
||||
static FormatType GetUrlFormatType();
|
||||
static FormatType GetUrlWFormatType();
|
||||
static FormatType GetMozUrlFormatType();
|
||||
static FormatType GetPlainTextFormatType();
|
||||
static FormatType GetPlainTextWFormatType();
|
||||
static FormatType GetFilenameFormatType();
|
||||
static FormatType GetFilenameWFormatType();
|
||||
static FormatType GetWebKitSmartPasteFormatType();
|
||||
// Win: MS HTML Format, Other: Generic HTML format
|
||||
static FormatType GetHtmlFormatType();
|
||||
#if defined(OS_WIN)
|
||||
static FormatType GetBitmapFormatType();
|
||||
// Firefox text/html
|
||||
static FormatType GetTextHtmlFormatType();
|
||||
static FormatType GetCFHDropFormatType();
|
||||
static FormatType GetFileDescriptorFormatType();
|
||||
static FormatType GetFileContentFormatZeroType();
|
||||
|
||||
// Duplicates any remote shared memory handle embedded inside |objects| that
|
||||
// was created by |process| so that it can be used by this process.
|
||||
static void DuplicateRemoteHandles(base::ProcessHandle process,
|
||||
ObjectMap* objects);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void WriteText(const char* text_data, size_t text_len);
|
||||
|
||||
void WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteWebSmartPaste();
|
||||
|
||||
void WriteFiles(const char* file_data, size_t file_len);
|
||||
|
||||
void DispatchObject(ObjectType type, const ObjectMapParams& params);
|
||||
|
||||
void WriteBitmap(const char* pixel_data, const char* size_data);
|
||||
#if defined(OS_WIN)
|
||||
void WriteBitmapFromSharedMemory(const char* bitmap_data,
|
||||
const char* size_data,
|
||||
base::ProcessHandle handle);
|
||||
|
||||
void WriteBitmapFromHandle(HBITMAP source_hbitmap,
|
||||
const gfx::Size& size);
|
||||
|
||||
// Safely write to system clipboard. Free |handle| on failure.
|
||||
void WriteToClipboard(unsigned int format, HANDLE handle);
|
||||
|
||||
static void ParseBookmarkClipboardFormat(const string16& bookmark,
|
||||
string16* title,
|
||||
std::string* url);
|
||||
|
||||
// Free a handle depending on its type (as intuited from format)
|
||||
static void FreeData(unsigned int format, HANDLE data);
|
||||
|
||||
// Return the window that should be the clipboard owner, creating it
|
||||
// if neccessary. Marked const for lazily initialization by const methods.
|
||||
HWND GetClipboardWindow() const;
|
||||
|
||||
// Mark this as mutable so const methods can still do lazy initialization.
|
||||
mutable HWND clipboard_owner_;
|
||||
|
||||
// True if we can create a window.
|
||||
bool create_window_;
|
||||
#elif defined(OS_LINUX)
|
||||
// Data is stored in the |clipboard_data_| map until it is saved to the system
|
||||
// clipboard. The Store* functions save data to the |clipboard_data_| map. The
|
||||
// SetGtkClipboard function replaces whatever is on the system clipboard with
|
||||
// the contents of |clipboard_data_|.
|
||||
// The Write* functions make a deep copy of the data passed to them an store
|
||||
// it in |clipboard_data_|.
|
||||
|
||||
// Write changes to gtk clipboard.
|
||||
void SetGtkClipboard();
|
||||
// Free pointers in clipboard_data_ and clear() the map.
|
||||
void FreeTargetMap();
|
||||
// Insert a mapping into clipboard_data_.
|
||||
void InsertMapping(const char* key, char* data, size_t data_len);
|
||||
|
||||
TargetMap* clipboard_data_;
|
||||
GtkClipboard* clipboard_;
|
||||
#endif
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(Clipboard);
|
||||
};
|
||||
|
||||
#endif // BASE_CLIPBOARD_H_
|
|
@ -0,0 +1,328 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/linux_util.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kMimeBmp[] = "image/bmp";
|
||||
const char kMimeHtml[] = "text/html";
|
||||
const char kMimeText[] = "text/plain";
|
||||
const char kMimeWebkitSmartPaste[] = "chromium-internal/webkit-paste";
|
||||
|
||||
std::string GdkAtomToString(const GdkAtom& atom) {
|
||||
gchar* name = gdk_atom_name(atom);
|
||||
std::string rv(name);
|
||||
g_free(name);
|
||||
return rv;
|
||||
}
|
||||
|
||||
GdkAtom StringToGdkAtom(const std::string& str) {
|
||||
return gdk_atom_intern(str.c_str(), false);
|
||||
}
|
||||
|
||||
// GtkClipboardGetFunc callback.
|
||||
// GTK will call this when an application wants data we copied to the clipboard.
|
||||
void GetData(GtkClipboard* clipboard,
|
||||
GtkSelectionData* selection_data,
|
||||
guint info,
|
||||
gpointer user_data) {
|
||||
Clipboard::TargetMap* data_map =
|
||||
reinterpret_cast<Clipboard::TargetMap*>(user_data);
|
||||
|
||||
std::string target_string = GdkAtomToString(selection_data->target);
|
||||
Clipboard::TargetMap::iterator iter = data_map->find(target_string);
|
||||
|
||||
if (iter == data_map->end())
|
||||
return;
|
||||
|
||||
if (target_string == kMimeBmp) {
|
||||
gtk_selection_data_set_pixbuf(selection_data,
|
||||
reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
} else {
|
||||
gtk_selection_data_set(selection_data, selection_data->target, 8,
|
||||
reinterpret_cast<guchar*>(iter->second.first),
|
||||
iter->second.second);
|
||||
}
|
||||
}
|
||||
|
||||
// GtkClipboardClearFunc callback.
|
||||
// We are guaranteed this will be called exactly once for each call to
|
||||
// gtk_clipboard_set_with_data
|
||||
void ClearData(GtkClipboard* clipboard,
|
||||
gpointer user_data) {
|
||||
Clipboard::TargetMap* map =
|
||||
reinterpret_cast<Clipboard::TargetMap*>(user_data);
|
||||
std::set<char*> ptrs;
|
||||
|
||||
for (Clipboard::TargetMap::iterator iter = map->begin();
|
||||
iter != map->end(); ++iter) {
|
||||
if (iter->first == kMimeBmp)
|
||||
g_object_unref(reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
else
|
||||
ptrs.insert(iter->second.first);
|
||||
}
|
||||
|
||||
for (std::set<char*>::iterator iter = ptrs.begin();
|
||||
iter != ptrs.end(); ++iter)
|
||||
delete[] *iter;
|
||||
|
||||
delete map;
|
||||
}
|
||||
|
||||
// Frees the pointers in the given map and clears the map.
|
||||
// Does not double-free any pointers.
|
||||
void FreeTargetMap(Clipboard::TargetMap map) {
|
||||
std::set<char*> ptrs;
|
||||
|
||||
for (Clipboard::TargetMap::iterator iter = map.begin();
|
||||
iter != map.end(); ++iter)
|
||||
ptrs.insert(iter->second.first);
|
||||
|
||||
for (std::set<char*>::iterator iter = ptrs.begin();
|
||||
iter != ptrs.end(); ++iter)
|
||||
delete[] *iter;
|
||||
|
||||
map.clear();
|
||||
}
|
||||
|
||||
// Called on GdkPixbuf destruction; see WriteBitmap().
|
||||
void GdkPixbufFree(guchar* pixels, gpointer data) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() {
|
||||
clipboard_ = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
// TODO(estade): do we want to save clipboard data after we exit?
|
||||
// gtk_clipboard_set_can_store and gtk_clipboard_store work
|
||||
// but have strangely awful performance.
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
clipboard_data_ = new TargetMap();
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
|
||||
SetGtkClipboard();
|
||||
}
|
||||
|
||||
// Take ownership of the GTK clipboard and inform it of the targets we support.
|
||||
void Clipboard::SetGtkClipboard() {
|
||||
scoped_array<GtkTargetEntry> targets(
|
||||
new GtkTargetEntry[clipboard_data_->size()]);
|
||||
|
||||
int i = 0;
|
||||
for (Clipboard::TargetMap::iterator iter = clipboard_data_->begin();
|
||||
iter != clipboard_data_->end(); ++iter, ++i) {
|
||||
char* target_string = new char[iter->first.size() + 1];
|
||||
strcpy(target_string, iter->first.c_str());
|
||||
targets[i].target = target_string;
|
||||
targets[i].flags = 0;
|
||||
targets[i].info = i;
|
||||
}
|
||||
|
||||
gtk_clipboard_set_with_data(clipboard_, targets.get(),
|
||||
clipboard_data_->size(),
|
||||
GetData, ClearData,
|
||||
clipboard_data_);
|
||||
|
||||
for (size_t i = 0; i < clipboard_data_->size(); i++)
|
||||
delete[] targets[i].target;
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
char* data = new char[text_len];
|
||||
memcpy(data, text_data, text_len);
|
||||
|
||||
InsertMapping(kMimeText, data, text_len);
|
||||
InsertMapping("TEXT", data, text_len);
|
||||
InsertMapping("STRING", data, text_len);
|
||||
InsertMapping("UTF8_STRING", data, text_len);
|
||||
InsertMapping("COMPOUND_TEXT", data, text_len);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
// TODO(estade): might not want to ignore |url_data|
|
||||
char* data = new char[markup_len];
|
||||
memcpy(data, markup_data, markup_len);
|
||||
|
||||
InsertMapping(kMimeHtml, data, markup_len);
|
||||
}
|
||||
|
||||
// Write an extra flavor that signifies WebKit was the last to modify the
|
||||
// pasteboard. This flavor has no data.
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
InsertMapping(kMimeWebkitSmartPaste, NULL, 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
|
||||
guchar* data = base::BGRAToRGBA(reinterpret_cast<const uint8_t*>(pixel_data),
|
||||
size->width(), size->height(), 0);
|
||||
|
||||
GdkPixbuf* pixbuf =
|
||||
gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE,
|
||||
8, size->width(), size->height(),
|
||||
size->width() * 4, GdkPixbufFree, NULL);
|
||||
// We store the GdkPixbuf*, and the size_t half of the pair is meaningless.
|
||||
// Note that this contrasts with the vast majority of entries in our target
|
||||
// map, which directly store the data and its length.
|
||||
InsertMapping(kMimeBmp, reinterpret_cast<char*>(pixbuf), 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data, size_t title_len,
|
||||
const char* url_data, size_t url_len) {
|
||||
// TODO(estade): implement this, but for now fail silently so we do not
|
||||
// write error output during layout tests.
|
||||
// NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data, size_t title_len,
|
||||
const char* url_data, size_t url_len) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
// We do not use gtk_clipboard_wait_is_target_available because of
|
||||
// a bug with the gtk clipboard. It caches the available targets
|
||||
// and does not always refresh the cache when it is appropriate.
|
||||
// TODO(estade): When gnome bug 557315 is resolved, change this function
|
||||
// to use gtk_clipboard_wait_is_target_available. Also, catch requests
|
||||
// for plain text and change them to gtk_clipboard_wait_is_text_available
|
||||
// (which checks for several standard text targets).
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
bool retval = false;
|
||||
GdkAtom* targets = NULL;
|
||||
GtkSelectionData* data =
|
||||
gtk_clipboard_wait_for_contents(clipboard_,
|
||||
gdk_atom_intern("TARGETS", false));
|
||||
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
int num = 0;
|
||||
gtk_selection_data_get_targets(data, &targets, &num);
|
||||
|
||||
GdkAtom format_atom = StringToGdkAtom(format);
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (targets[i] == format_atom) {
|
||||
retval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_selection_data_free(data);
|
||||
g_free(targets);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
result->clear();
|
||||
gchar* text = gtk_clipboard_wait_for_text(clipboard_);
|
||||
|
||||
if (text == NULL)
|
||||
return;
|
||||
|
||||
// TODO(estade): do we want to handle the possible error here?
|
||||
UTF8ToUTF16(text, strlen(text), result);
|
||||
g_free(text);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
result->clear();
|
||||
gchar* text = gtk_clipboard_wait_for_text(clipboard_);
|
||||
|
||||
if (text == NULL)
|
||||
return;
|
||||
|
||||
result->assign(text);
|
||||
g_free(text);
|
||||
}
|
||||
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
*file = FilePath();
|
||||
}
|
||||
|
||||
// TODO(estade): handle different charsets.
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
markup->clear();
|
||||
|
||||
GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard_,
|
||||
StringToGdkAtom(GetHtmlFormatType()));
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
UTF8ToUTF16(reinterpret_cast<char*>(data->data),
|
||||
strlen(reinterpret_cast<char*>(data->data)),
|
||||
markup);
|
||||
gtk_selection_data_free(data);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
return GdkAtomToString(GDK_TARGET_STRING);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
return GetPlainTextFormatType();
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
return std::string(kMimeHtml);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
return std::string(kMimeWebkitSmartPaste);
|
||||
}
|
||||
|
||||
// Insert the key/value pair in the clipboard_data structure. If
|
||||
// the mapping already exists, it frees the associated data. Don't worry
|
||||
// about double freeing because if the same key is inserted into the
|
||||
// map twice, it must have come from different Write* functions and the
|
||||
// data pointer cannot be the same.
|
||||
void Clipboard::InsertMapping(const char* key,
|
||||
char* data,
|
||||
size_t data_len) {
|
||||
TargetMap::iterator iter = clipboard_data_->find(key);
|
||||
|
||||
if (iter != clipboard_data_->end()) {
|
||||
if (strcmp(kMimeBmp, key) == 0)
|
||||
g_object_unref(reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
else
|
||||
delete[] iter->second.first;
|
||||
}
|
||||
|
||||
(*clipboard_data_)[key] = std::make_pair(data, data_len);
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/sys_string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Would be nice if this were in UTCoreTypes.h, but it isn't
|
||||
const NSString* kUTTypeURLName = @"public.url-name";
|
||||
|
||||
// Tells us if WebKit was the last to write to the pasteboard. There's no
|
||||
// actual data associated with this type.
|
||||
const NSString *kWebSmartPastePboardType = @"NeXT smart paste pasteboard type";
|
||||
|
||||
NSPasteboard* GetPasteboard() {
|
||||
// The pasteboard should not be nil in a UI session, but this handy DCHECK
|
||||
// can help track down problems if someone tries using clipboard code outside
|
||||
// of a UI session.
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
DCHECK(pasteboard);
|
||||
return pasteboard;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() {
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb declareTypes:[NSArray array] owner:nil];
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
std::string text_str(text_data, text_len);
|
||||
NSString *text = base::SysUTF8ToNSString(text_str);
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
||||
[pb setString:text forType:NSStringPboardType];
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string html_fragment_str(markup_data, markup_len);
|
||||
NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str);
|
||||
|
||||
// TODO(avi): url_data?
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil];
|
||||
[pb setString:html_fragment forType:NSHTMLPboardType];
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
WriteHyperlink(title_data, title_len, url_data, url_len);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string title_str(title_data, title_len);
|
||||
NSString *title = base::SysUTF8ToNSString(title_str);
|
||||
std::string url_str(url_data, url_len);
|
||||
NSString *url = base::SysUTF8ToNSString(url_str);
|
||||
|
||||
// TODO(playmobil): In the Windows version of this function, an HTML
|
||||
// representation of the bookmark is also added to the clipboard, to support
|
||||
// drag and drop of web shortcuts. I don't think we need to do this on the
|
||||
// Mac, but we should double check later on.
|
||||
NSURL* nsurl = [NSURL URLWithString:url];
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
// passing UTIs into the pasteboard methods is valid >= 10.5
|
||||
[pb addTypes:[NSArray arrayWithObjects:NSURLPboardType,
|
||||
kUTTypeURLName,
|
||||
nil]
|
||||
owner:nil];
|
||||
[nsurl writeToPasteboard:pb];
|
||||
[pb setString:title forType:kUTTypeURLName];
|
||||
}
|
||||
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
NSMutableArray* fileList = [NSMutableArray arrayWithCapacity:1];
|
||||
|
||||
// Offset of current filename from start of file_data array.
|
||||
size_t current_filename_offset = 0;
|
||||
|
||||
// file_data is double null terminated (see table at top of clipboard.h).
|
||||
// So this loop can ignore the second null terminator, thus file_len - 1.
|
||||
// TODO(playmobil): If we need a loop like this on other platforms then split
|
||||
// this out into a common function that outputs a std::vector<const char*>.
|
||||
for (size_t i = 0; i < file_len - 1; ++i) {
|
||||
if (file_data[i] == '\0') {
|
||||
const char* filename = &file_data[current_filename_offset];
|
||||
[fileList addObject:[NSString stringWithUTF8String:filename]];
|
||||
|
||||
current_filename_offset = i + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
|
||||
[pb setPropertyList:fileList forType:NSFilenamesPboardType];
|
||||
}
|
||||
|
||||
// Write an extra flavor that signifies WebKit was the last to modify the
|
||||
// pasteboard. This flavor has no data.
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* format = base::SysUTF8ToNSString(GetWebKitSmartPasteFormatType());
|
||||
[pb addTypes:[NSArray arrayWithObject:format] owner:nil];
|
||||
[pb setData:nil forType:format];
|
||||
}
|
||||
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
NSString* format_ns = base::SysUTF8ToNSString(format);
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray* types = [pb types];
|
||||
|
||||
return [types containsObject:format_ns];
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* contents = [pb stringForType:NSStringPboardType];
|
||||
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
result);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* contents = [pb stringForType:NSStringPboardType];
|
||||
|
||||
if (!contents)
|
||||
result->clear();
|
||||
else
|
||||
result->assign([contents UTF8String]);
|
||||
}
|
||||
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
if (markup) {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray *supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType,
|
||||
NSStringPboardType,
|
||||
nil];
|
||||
NSString *bestType = [pb availableTypeFromArray:supportedTypes];
|
||||
NSString* contents = [pb stringForType:bestType];
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
markup);
|
||||
}
|
||||
|
||||
// TODO(avi): src_url?
|
||||
if (src_url)
|
||||
src_url->clear();
|
||||
}
|
||||
|
||||
void Clipboard::ReadBookmark(string16* title, std::string* url) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
|
||||
if (title) {
|
||||
NSString* contents = [pb stringForType:kUTTypeURLName];
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
title);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString];
|
||||
if (!url_string)
|
||||
url->clear();
|
||||
else
|
||||
url->assign([url_string UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
if (!file) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
*file = FilePath();
|
||||
std::vector<FilePath> files;
|
||||
ReadFiles(&files);
|
||||
|
||||
// Take the first file, if available.
|
||||
if (!files.empty())
|
||||
*file = files[0];
|
||||
}
|
||||
|
||||
void Clipboard::ReadFiles(std::vector<FilePath>* files) const {
|
||||
if (!files) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
files->clear();
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray* fileList = [pb propertyListForType:NSFilenamesPboardType];
|
||||
|
||||
for (unsigned int i = 0; i < [fileList count]; ++i) {
|
||||
std::string file = [[fileList objectAtIndex:i] UTF8String];
|
||||
files->push_back(FilePath(file));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSURLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlWFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSURLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSStringPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSStringPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(NSFilenamesPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameWFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(NSFilenamesPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSHTMLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(kWebSmartPastePboardType);
|
||||
return type;
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/clipboard.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/scoped_clipboard_writer.h"
|
||||
#include "base/string_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/platform_test.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
class ClipboardTest : public PlatformTest {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
message_loop_.reset(new MessageLoopForUI());
|
||||
}
|
||||
virtual void TearDown() {
|
||||
}
|
||||
|
||||
private:
|
||||
scoped_ptr<MessageLoop> message_loop_;
|
||||
};
|
||||
#elif defined(OS_POSIX)
|
||||
typedef PlatformTest ClipboardTest;
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
TEST_F(ClipboardTest, ClearTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteText(ASCIIToUTF16("clear me"));
|
||||
}
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(ASCIIToUTF16("<b>broom</b>"), "");
|
||||
}
|
||||
|
||||
EXPECT_FALSE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_FALSE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, TextTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 text(ASCIIToUTF16("This is a string16!#$")), text_result;
|
||||
std::string ascii_text;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteText(text);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_TRUE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
clipboard.ReadText(&text_result);
|
||||
|
||||
EXPECT_EQ(text, text_result);
|
||||
clipboard.ReadAsciiText(&ascii_text);
|
||||
EXPECT_EQ(UTF16ToUTF8(text), ascii_text);
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, HTMLTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 markup(ASCIIToUTF16("<string>Hi!</string>")), markup_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, TrickyHTMLTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 markup(ASCIIToUTF16("<em>Bye!<!--EndFragment --></em>")),
|
||||
markup_result;
|
||||
std::string url, url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
}
|
||||
|
||||
// TODO(estade): Port the following test (decide what target we use for urls)
|
||||
#if !defined(OS_LINUX)
|
||||
TEST_F(ClipboardTest, BookmarkTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 title(ASCIIToUTF16("The Example Company")), title_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteBookmark(title, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetUrlWFormatType()));
|
||||
clipboard.ReadBookmark(&title_result, &url_result);
|
||||
EXPECT_EQ(title, title_result);
|
||||
EXPECT_EQ(url, url_result);
|
||||
}
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
TEST_F(ClipboardTest, MultiFormatTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 text(ASCIIToUTF16("Hi!")), text_result;
|
||||
string16 markup(ASCIIToUTF16("<strong>Hi!</string>")), markup_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
std::string ascii_text;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
clipboard_writer.WriteText(text);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType()));
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
clipboard.ReadText(&text_result);
|
||||
EXPECT_EQ(text, text_result);
|
||||
clipboard.ReadAsciiText(&ascii_text);
|
||||
EXPECT_EQ(UTF16ToUTF8(text), ascii_text);
|
||||
}
|
||||
|
||||
// TODO(estade): Port the following tests (decide what targets we use for files)
|
||||
#if !defined(OS_LINUX)
|
||||
// Files for this test don't actually need to exist on the file system, just
|
||||
// don't try to use a non-existent file you've retrieved from the clipboard.
|
||||
TEST_F(ClipboardTest, FileTest) {
|
||||
Clipboard clipboard;
|
||||
#if defined(OS_WIN)
|
||||
FilePath file(L"C:\\Downloads\\My Downloads\\A Special File.txt");
|
||||
#elif defined(OS_MACOSX)
|
||||
// OS X will print a warning message if we stick a non-existant file on the
|
||||
// clipboard.
|
||||
FilePath file("/usr/bin/make");
|
||||
#endif // defined(OS_MACOSX)
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteFile(file);
|
||||
}
|
||||
|
||||
FilePath out_file;
|
||||
clipboard.ReadFile(&out_file);
|
||||
EXPECT_EQ(file.value(), out_file.value());
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, MultipleFilesTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
FilePath file1(L"C:\\Downloads\\My Downloads\\File 1.exe");
|
||||
FilePath file2(L"C:\\Downloads\\My Downloads\\File 2.pdf");
|
||||
FilePath file3(L"C:\\Downloads\\My Downloads\\File 3.doc");
|
||||
#elif defined(OS_MACOSX)
|
||||
// OS X will print a warning message if we stick a non-existant file on the
|
||||
// clipboard.
|
||||
FilePath file1("/usr/bin/make");
|
||||
FilePath file2("/usr/bin/man");
|
||||
FilePath file3("/usr/bin/perl");
|
||||
#endif // defined(OS_MACOSX)
|
||||
std::vector<FilePath> files;
|
||||
files.push_back(file1);
|
||||
files.push_back(file2);
|
||||
files.push_back(file3);
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteFiles(files);
|
||||
}
|
||||
|
||||
std::vector<FilePath> out_files;
|
||||
clipboard.ReadFiles(&out_files);
|
||||
|
||||
EXPECT_EQ(files.size(), out_files.size());
|
||||
for (size_t i = 0; i < out_files.size(); ++i)
|
||||
EXPECT_EQ(files[i].value(), out_files[i].value());
|
||||
}
|
||||
#endif // !defined(OS_LINUX)
|
||||
|
||||
#if defined(OS_WIN) // Windows only tests.
|
||||
TEST_F(ClipboardTest, HyperlinkTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 title(ASCIIToUTF16("The Example Company")), title_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
string16 html(ASCIIToUTF16("<a href=\"http://www.example.com/\">"
|
||||
"The Example Company</a>")), html_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHyperlink(title, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetUrlWFormatType()));
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadBookmark(&title_result, &url_result);
|
||||
EXPECT_EQ(title, title_result);
|
||||
EXPECT_EQ(url, url_result);
|
||||
clipboard.ReadHTML(&html_result, &url_result);
|
||||
EXPECT_EQ(html, html_result);
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, WebSmartPasteTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteWebSmartPaste();
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetWebKitSmartPasteFormatType()));
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, BitmapTest) {
|
||||
unsigned int fake_bitmap[] = {
|
||||
0x46155189, 0xF6A55C8D, 0x79845674, 0xFA57BD89,
|
||||
0x78FD46AE, 0x87C64F5A, 0x36EDC5AF, 0x4378F568,
|
||||
0x91E9F63A, 0xC31EA14F, 0x69AB32DF, 0x643A3FD1,
|
||||
};
|
||||
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteBitmapFromPixels(fake_bitmap, gfx::Size(3, 4));
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetBitmapFormatType()));
|
||||
}
|
||||
#endif // defined(OS_WIN)
|
|
@ -0,0 +1,488 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard_util.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <wininet.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_handle.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url,
|
||||
std::wstring* title) {
|
||||
DCHECK(data_object && url && title);
|
||||
|
||||
STGMEDIUM medium;
|
||||
if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium)))
|
||||
return false;
|
||||
|
||||
HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
|
||||
|
||||
if (!hdrop)
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
wchar_t filename[MAX_PATH];
|
||||
if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) {
|
||||
wchar_t url_buffer[INTERNET_MAX_URL_LENGTH];
|
||||
if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") &&
|
||||
GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer,
|
||||
arraysize(url_buffer), filename)) {
|
||||
*url = url_buffer;
|
||||
PathRemoveExtension(filename);
|
||||
title->assign(PathFindFileName(filename));
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
DragFinish(hdrop);
|
||||
GlobalUnlock(medium.hGlobal);
|
||||
// We don't need to call ReleaseStgMedium here because as far as I can tell,
|
||||
// DragFinish frees the hGlobal for us.
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SplitUrlAndTitle(const std::wstring& str, std::wstring* url,
|
||||
std::wstring* title) {
|
||||
DCHECK(url && title);
|
||||
size_t newline_pos = str.find('\n');
|
||||
bool success = false;
|
||||
if (newline_pos != std::string::npos) {
|
||||
*url = str.substr(0, newline_pos);
|
||||
title->assign(str.substr(newline_pos + 1));
|
||||
success = true;
|
||||
} else {
|
||||
*url = str;
|
||||
title->assign(str);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
FORMATETC* ClipboardUtil::GetUrlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetUrlWFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetMozUrlFormat() {
|
||||
// The format is "URL\nTitle"
|
||||
static UINT cf = RegisterClipboardFormat(L"text/x-moz-url");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetPlainTextFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetPlainTextWFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1,
|
||||
TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFilenameWFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFilenameFormat()
|
||||
{
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetHtmlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"HTML Format");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetTextHtmlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"text/html");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetCFHDropFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1,
|
||||
TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFileDescriptorFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFileContentFormatZero() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
|
||||
bool ClipboardUtil::HasUrl(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFilenameFormat()));
|
||||
}
|
||||
|
||||
bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat()));
|
||||
}
|
||||
|
||||
bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat()));
|
||||
}
|
||||
|
||||
|
||||
bool ClipboardUtil::GetUrl(IDataObject* data_object,
|
||||
std::wstring* url, std::wstring* title) {
|
||||
DCHECK(data_object && url && title);
|
||||
if (!HasUrl(data_object))
|
||||
return false;
|
||||
|
||||
// Try to extract a URL from |data_object| in a variety of formats.
|
||||
STGMEDIUM store;
|
||||
if (GetUrlFromHDrop(data_object, url, title)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) ||
|
||||
SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) {
|
||||
// Mozilla URL format or unicode URL
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
bool success = SplitUrlAndTitle(data.get(), url, title);
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) {
|
||||
// URL using ascii
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
bool success = SplitUrlAndTitle(UTF8ToWide(data.get()), url, title);
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetFilenameWFormat(), &store))) {
|
||||
// filename using unicode
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
bool success = false;
|
||||
if (data.get() && data.get()[0] && (PathFileExists(data.get()) ||
|
||||
PathIsUNC(data.get()))) {
|
||||
wchar_t file_url[INTERNET_MAX_URL_LENGTH];
|
||||
DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
|
||||
if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len,
|
||||
0))) {
|
||||
*url = file_url;
|
||||
title->assign(file_url);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetFilenameFormat(), &store))) {
|
||||
// filename using ascii
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
bool success = false;
|
||||
if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) ||
|
||||
PathIsUNCA(data.get()))) {
|
||||
char file_url[INTERNET_MAX_URL_LENGTH];
|
||||
DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
|
||||
if (SUCCEEDED(::UrlCreateFromPathA(data.get(),
|
||||
file_url,
|
||||
&file_url_len,
|
||||
0))) {
|
||||
*url = UTF8ToWide(file_url);
|
||||
title->assign(*url);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetFilenames(IDataObject* data_object,
|
||||
std::vector<std::wstring>* filenames) {
|
||||
DCHECK(data_object && filenames);
|
||||
if (!HasFilenames(data_object))
|
||||
return false;
|
||||
|
||||
STGMEDIUM medium;
|
||||
if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium)))
|
||||
return false;
|
||||
|
||||
HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
|
||||
if (!hdrop)
|
||||
return false;
|
||||
|
||||
const int kMaxFilenameLen = 4096;
|
||||
const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0);
|
||||
for (unsigned int i = 0; i < num_files; ++i) {
|
||||
wchar_t filename[kMaxFilenameLen];
|
||||
if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen))
|
||||
continue;
|
||||
filenames->push_back(filename);
|
||||
}
|
||||
|
||||
DragFinish(hdrop);
|
||||
GlobalUnlock(medium.hGlobal);
|
||||
// We don't need to call ReleaseStgMedium here because as far as I can tell,
|
||||
// DragFinish frees the hGlobal for us.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetPlainText(IDataObject* data_object,
|
||||
std::wstring* plain_text) {
|
||||
DCHECK(data_object && plain_text);
|
||||
if (!HasPlainText(data_object))
|
||||
return false;
|
||||
|
||||
STGMEDIUM store;
|
||||
bool success = false;
|
||||
if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) {
|
||||
// Unicode text
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
plain_text->assign(data.get());
|
||||
ReleaseStgMedium(&store);
|
||||
success = true;
|
||||
} else if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) {
|
||||
// ascii text
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
plain_text->assign(UTF8ToWide(data.get()));
|
||||
ReleaseStgMedium(&store);
|
||||
success = true;
|
||||
} else {
|
||||
//If a file is dropped on the window, it does not provide either of the
|
||||
//plain text formats, so here we try to forcibly get a url.
|
||||
std::wstring title;
|
||||
success = GetUrl(data_object, plain_text, &title);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetHtml(IDataObject* data_object,
|
||||
std::wstring* html, std::string* base_url) {
|
||||
DCHECK(data_object && html && base_url);
|
||||
|
||||
if (SUCCEEDED(data_object->QueryGetData(GetHtmlFormat()))) {
|
||||
STGMEDIUM store;
|
||||
if (SUCCEEDED(data_object->GetData(GetHtmlFormat(), &store))) {
|
||||
// MS CF html
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
|
||||
std::string html_utf8;
|
||||
CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url);
|
||||
html->assign(UTF8ToWide(html_utf8));
|
||||
|
||||
ReleaseStgMedium(&store);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(data_object->QueryGetData(GetTextHtmlFormat())))
|
||||
return false;
|
||||
|
||||
STGMEDIUM store;
|
||||
if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store)))
|
||||
return false;
|
||||
|
||||
// text/html
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
html->assign(data.get());
|
||||
ReleaseStgMedium(&store);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetFileContents(IDataObject* data_object,
|
||||
std::wstring* filename, std::string* file_contents) {
|
||||
DCHECK(data_object && filename && file_contents);
|
||||
bool has_data =
|
||||
SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat()));
|
||||
|
||||
if (!has_data)
|
||||
return false;
|
||||
|
||||
STGMEDIUM content;
|
||||
// The call to GetData can be very slow depending on what is in
|
||||
// |data_object|.
|
||||
if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) {
|
||||
if (TYMED_HGLOBAL == content.tymed) {
|
||||
ScopedHGlobal<char> data(content.hGlobal);
|
||||
file_contents->assign(data.get(), data.Size());
|
||||
}
|
||||
ReleaseStgMedium(&content);
|
||||
}
|
||||
|
||||
STGMEDIUM description;
|
||||
if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(),
|
||||
&description))) {
|
||||
ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal);
|
||||
// We expect there to be at least one file in here.
|
||||
DCHECK(fgd->cItems >= 1);
|
||||
filename->assign(fgd->fgd[0].cFileName);
|
||||
ReleaseStgMedium(&description);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
|
||||
// WebCore/platform/win/ClipboardUtilitiesWin.cpp.
|
||||
/*
|
||||
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Helper method for converting from text/html to MS CF_HTML.
|
||||
// Documentation for the CF_HTML format is available at
|
||||
// http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
|
||||
std::string ClipboardUtil::HtmlToCFHtml(const std::string& html,
|
||||
const std::string& base_url) {
|
||||
if (html.empty())
|
||||
return std::string();
|
||||
|
||||
#define MAX_DIGITS 10
|
||||
#define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
|
||||
#define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
|
||||
#define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
|
||||
|
||||
static const char* header = "Version:0.9\r\n"
|
||||
"StartHTML:" NUMBER_FORMAT "\r\n"
|
||||
"EndHTML:" NUMBER_FORMAT "\r\n"
|
||||
"StartFragment:" NUMBER_FORMAT "\r\n"
|
||||
"EndFragment:" NUMBER_FORMAT "\r\n";
|
||||
static const char* source_url_prefix = "SourceURL:";
|
||||
|
||||
static const char* start_markup =
|
||||
"<html>\r\n<body>\r\n<!--StartFragment-->\r\n";
|
||||
static const char* end_markup =
|
||||
"\r\n<!--EndFragment-->\r\n</body>\r\n</html>";
|
||||
|
||||
// Calculate offsets
|
||||
size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 +
|
||||
MAX_DIGITS * 4;
|
||||
if (!base_url.empty()) {
|
||||
start_html_offset += strlen(source_url_prefix) +
|
||||
base_url.length() + 2; // Add 2 for \r\n.
|
||||
}
|
||||
size_t start_fragment_offset = start_html_offset + strlen(start_markup);
|
||||
size_t end_fragment_offset = start_fragment_offset + html.length();
|
||||
size_t end_html_offset = end_fragment_offset + strlen(end_markup);
|
||||
|
||||
std::string result = StringPrintf(header, start_html_offset,
|
||||
end_html_offset, start_fragment_offset, end_fragment_offset);
|
||||
if (!base_url.empty()) {
|
||||
result.append(source_url_prefix);
|
||||
result.append(base_url);
|
||||
result.append("\r\n");
|
||||
}
|
||||
result.append(start_markup);
|
||||
result.append(html);
|
||||
result.append(end_markup);
|
||||
|
||||
#undef MAX_DIGITS
|
||||
#undef MAKE_NUMBER_FORMAT_1
|
||||
#undef MAKE_NUMBER_FORMAT_2
|
||||
#undef NUMBER_FORMAT
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper method for converting from MS CF_HTML to text/html.
|
||||
void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html,
|
||||
std::string* html,
|
||||
std::string* base_url) {
|
||||
// Obtain base_url if present.
|
||||
static std::string src_url_str("SourceURL:");
|
||||
size_t line_start = cf_html.find(src_url_str);
|
||||
if (line_start != std::string::npos) {
|
||||
size_t src_end = cf_html.find("\n", line_start);
|
||||
size_t src_start = line_start + src_url_str.length();
|
||||
if (src_end != std::string::npos && src_start != std::string::npos) {
|
||||
*base_url = cf_html.substr(src_start, src_end - src_start);
|
||||
TrimWhitespace(*base_url, TRIM_ALL, base_url);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the markup between "<!--StartFragment -->" and "<!--EndFragment-->".
|
||||
std::string cf_html_lower = StringToLowerASCII(cf_html);
|
||||
size_t markup_start = cf_html_lower.find("<html", 0);
|
||||
size_t tag_start = cf_html.find("StartFragment", markup_start);
|
||||
size_t fragment_start = cf_html.find('>', tag_start) + 1;
|
||||
size_t tag_end = cf_html.rfind("EndFragment", std::string::npos);
|
||||
size_t fragment_end = cf_html.rfind('<', tag_end);
|
||||
if (fragment_start != std::string::npos &&
|
||||
fragment_end != std::string::npos) {
|
||||
*html = cf_html.substr(fragment_start, fragment_end - fragment_start);
|
||||
TrimWhitespace(*html, TRIM_ALL, html);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Some helper functions for working with the clipboard and IDataObjects.
|
||||
|
||||
#ifndef BASE_CLIPBOARD_UTIL_H_
|
||||
#define BASE_CLIPBOARD_UTIL_H_
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ClipboardUtil {
|
||||
public:
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Clipboard formats.
|
||||
static FORMATETC* GetUrlFormat();
|
||||
static FORMATETC* GetUrlWFormat();
|
||||
static FORMATETC* GetMozUrlFormat();
|
||||
static FORMATETC* GetPlainTextFormat();
|
||||
static FORMATETC* GetPlainTextWFormat();
|
||||
static FORMATETC* GetFilenameFormat();
|
||||
static FORMATETC* GetFilenameWFormat();
|
||||
// MS HTML Format
|
||||
static FORMATETC* GetHtmlFormat();
|
||||
// Firefox text/html
|
||||
static FORMATETC* GetTextHtmlFormat();
|
||||
static FORMATETC* GetCFHDropFormat();
|
||||
static FORMATETC* GetFileDescriptorFormat();
|
||||
static FORMATETC* GetFileContentFormatZero();
|
||||
static FORMATETC* GetWebKitSmartPasteFormat();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// These methods check to see if |data_object| has the requested type.
|
||||
// Returns true if it does.
|
||||
static bool HasUrl(IDataObject* data_object);
|
||||
static bool HasFilenames(IDataObject* data_object);
|
||||
static bool HasPlainText(IDataObject* data_object);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Helper methods to extract information from an IDataObject. These methods
|
||||
// return true if the requested data type is found in |data_object|.
|
||||
static bool GetUrl(IDataObject* data_object,
|
||||
std::wstring* url, std::wstring* title);
|
||||
static bool GetFilenames(IDataObject* data_object,
|
||||
std::vector<std::wstring>* filenames);
|
||||
static bool GetPlainText(IDataObject* data_object, std::wstring* plain_text);
|
||||
static bool GetHtml(IDataObject* data_object, std::wstring* text_html,
|
||||
std::string* base_url);
|
||||
static bool GetFileContents(IDataObject* data_object,
|
||||
std::wstring* filename,
|
||||
std::string* file_contents);
|
||||
|
||||
// A helper method for converting between MS CF_HTML format and plain
|
||||
// text/html.
|
||||
static std::string HtmlToCFHtml(const std::string& html,
|
||||
const std::string& base_url);
|
||||
static void CFHtmlToHtml(const std::string& cf_html, std::string* html,
|
||||
std::string* base_url);
|
||||
};
|
||||
|
||||
#endif // BASE_CLIPBOARD_UTIL_H_
|
|
@ -0,0 +1,650 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Many of these functions are based on those found in
|
||||
// webkit/port/platform/PasteboardWin.cpp
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "base/clipboard_util.h"
|
||||
#include "base/lock.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/shared_memory.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A scoper to manage acquiring and automatically releasing the clipboard.
|
||||
class ScopedClipboard {
|
||||
public:
|
||||
ScopedClipboard() : opened_(false) { }
|
||||
|
||||
~ScopedClipboard() {
|
||||
if (opened_)
|
||||
Release();
|
||||
}
|
||||
|
||||
bool Acquire(HWND owner) {
|
||||
const int kMaxAttemptsToOpenClipboard = 5;
|
||||
|
||||
if (opened_) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to open the clipboard, which will acquire the Windows clipboard
|
||||
// lock. This may fail if another process currently holds this lock.
|
||||
// We're willing to try a few times in the hopes of acquiring it.
|
||||
//
|
||||
// This turns out to be an issue when using remote desktop because the
|
||||
// rdpclip.exe process likes to read what we've written to the clipboard and
|
||||
// send it to the RDP client. If we open and close the clipboard in quick
|
||||
// succession, we might be trying to open it while rdpclip.exe has it open,
|
||||
// See Bug 815425.
|
||||
//
|
||||
// In fact, we believe we'll only spin this loop over remote desktop. In
|
||||
// normal situations, the user is initiating clipboard operations and there
|
||||
// shouldn't be contention.
|
||||
|
||||
for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) {
|
||||
// If we didn't manage to open the clipboard, sleep a bit and be hopeful.
|
||||
if (attempts != 0)
|
||||
::Sleep(5);
|
||||
|
||||
if (::OpenClipboard(owner)) {
|
||||
opened_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to acquire the clipboard.
|
||||
return false;
|
||||
}
|
||||
|
||||
void Release() {
|
||||
if (opened_) {
|
||||
::CloseClipboard();
|
||||
opened_ = false;
|
||||
} else {
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool opened_;
|
||||
};
|
||||
|
||||
LRESULT CALLBACK ClipboardOwnerWndProc(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
LRESULT lresult = 0;
|
||||
|
||||
switch (message) {
|
||||
case WM_RENDERFORMAT:
|
||||
// This message comes when SetClipboardData was sent a null data handle
|
||||
// and now it's come time to put the data on the clipboard.
|
||||
// We always set data, so there isn't a need to actually do anything here.
|
||||
break;
|
||||
case WM_RENDERALLFORMATS:
|
||||
// This message comes when SetClipboardData was sent a null data handle
|
||||
// and now this application is about to quit, so it must put data on
|
||||
// the clipboard before it exits.
|
||||
// We always set data, so there isn't a need to actually do anything here.
|
||||
break;
|
||||
case WM_DRAWCLIPBOARD:
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
break;
|
||||
case WM_CHANGECBCHAIN:
|
||||
break;
|
||||
default:
|
||||
lresult = DefWindowProc(hwnd, message, wparam, lparam);
|
||||
break;
|
||||
}
|
||||
return lresult;
|
||||
}
|
||||
|
||||
template <typename charT>
|
||||
HGLOBAL CreateGlobalData(const std::basic_string<charT>& str) {
|
||||
HGLOBAL data =
|
||||
::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT)));
|
||||
if (data) {
|
||||
charT* raw_data = static_cast<charT*>(::GlobalLock(data));
|
||||
memcpy(raw_data, str.data(), str.size() * sizeof(charT));
|
||||
raw_data[str.size()] = '\0';
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() : create_window_(false) {
|
||||
if (MessageLoop::current()->type() == MessageLoop::TYPE_UI) {
|
||||
// Make a dummy HWND to be the clipboard's owner.
|
||||
WNDCLASSEX wcex = {0};
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.lpfnWndProc = ClipboardOwnerWndProc;
|
||||
wcex.hInstance = GetModuleHandle(NULL);
|
||||
wcex.lpszClassName = L"ClipboardOwnerWindowClass";
|
||||
::RegisterClassEx(&wcex);
|
||||
create_window_ = true;
|
||||
}
|
||||
|
||||
clipboard_owner_ = NULL;
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
if (clipboard_owner_)
|
||||
::DestroyWindow(clipboard_owner_);
|
||||
clipboard_owner_ = NULL;
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
WriteObjects(objects, NULL);
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects,
|
||||
base::ProcessHandle process) {
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
::EmptyClipboard();
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
if (iter->first == CBF_SMBITMAP)
|
||||
WriteBitmapFromSharedMemory(&(iter->second[0].front()),
|
||||
&(iter->second[1].front()),
|
||||
process);
|
||||
else
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
string16 text;
|
||||
UTF8ToUTF16(text_data, text_len, &text);
|
||||
HGLOBAL glob = CreateGlobalData(text);
|
||||
|
||||
WriteToClipboard(CF_UNICODETEXT, glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string markup(markup_data, markup_len);
|
||||
std::string url;
|
||||
|
||||
if (url_len > 0)
|
||||
url.assign(url_data, url_len);
|
||||
|
||||
std::string html_fragment = ClipboardUtil::HtmlToCFHtml(markup, url);
|
||||
HGLOBAL glob = CreateGlobalData(html_fragment);
|
||||
|
||||
WriteToClipboard(StringToInt(GetHtmlFormatType()), glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string bookmark(title_data, title_len);
|
||||
bookmark.append(1, L'\n');
|
||||
bookmark.append(url_data, url_len);
|
||||
|
||||
string16 wide_bookmark = UTF8ToWide(bookmark);
|
||||
HGLOBAL glob = CreateGlobalData(wide_bookmark);
|
||||
|
||||
WriteToClipboard(StringToInt(GetUrlWFormatType()), glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
// Store as a bookmark.
|
||||
WriteBookmark(title_data, title_len, url_data, url_len);
|
||||
|
||||
std::string title(title_data, title_len),
|
||||
url(url_data, url_len),
|
||||
link("<a href=\"");
|
||||
|
||||
// Construct the hyperlink.
|
||||
link.append(url);
|
||||
link.append("\">");
|
||||
link.append(title);
|
||||
link.append("</a>");
|
||||
|
||||
// Store hyperlink as html.
|
||||
WriteHTML(link.c_str(), link.size(), NULL, 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
DCHECK(clipboard_owner_);
|
||||
::SetClipboardData(StringToInt(GetWebKitSmartPasteFormatType()), NULL);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
HDC dc = ::GetDC(NULL);
|
||||
|
||||
// This doesn't actually cost us a memcpy when the bitmap comes from the
|
||||
// renderer as we load it into the bitmap using setPixels which just sets a
|
||||
// pointer. Someone has to memcpy it into GDI, it might as well be us here.
|
||||
|
||||
// TODO(darin): share data in gfx/bitmap_header.cc somehow
|
||||
BITMAPINFO bm_info = {0};
|
||||
bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bm_info.bmiHeader.biWidth = size->width();
|
||||
bm_info.bmiHeader.biHeight = -size->height(); // sets vertical orientation
|
||||
bm_info.bmiHeader.biPlanes = 1;
|
||||
bm_info.bmiHeader.biBitCount = 32;
|
||||
bm_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
// ::CreateDIBSection allocates memory for us to copy our bitmap into.
|
||||
// Unfortunately, we can't write the created bitmap to the clipboard,
|
||||
// (see http://msdn2.microsoft.com/en-us/library/ms532292.aspx)
|
||||
void *bits;
|
||||
HBITMAP source_hbitmap =
|
||||
::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||
|
||||
if (bits && source_hbitmap) {
|
||||
// Copy the bitmap out of shared memory and into GDI
|
||||
memcpy(bits, pixel_data, 4 * size->width() * size->height());
|
||||
|
||||
// Now we have an HBITMAP, we can write it to the clipboard
|
||||
WriteBitmapFromHandle(source_hbitmap, *size);
|
||||
}
|
||||
|
||||
::DeleteObject(source_hbitmap);
|
||||
::ReleaseDC(NULL, dc);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmapFromSharedMemory(const char* bitmap_data,
|
||||
const char* size_data,
|
||||
base::ProcessHandle process) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
|
||||
// bitmap_data has an encoded shared memory object. See
|
||||
// DuplicateRemoteHandles().
|
||||
char* ptr = const_cast<char*>(bitmap_data);
|
||||
scoped_ptr<const base::SharedMemory> bitmap(*
|
||||
reinterpret_cast<const base::SharedMemory**>(ptr));
|
||||
|
||||
// TODO(darin): share data in gfx/bitmap_header.cc somehow.
|
||||
BITMAPINFO bm_info = {0};
|
||||
bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bm_info.bmiHeader.biWidth = size->width();
|
||||
// Sets the vertical orientation.
|
||||
bm_info.bmiHeader.biHeight = -size->height();
|
||||
bm_info.bmiHeader.biPlanes = 1;
|
||||
bm_info.bmiHeader.biBitCount = 32;
|
||||
bm_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
HDC dc = ::GetDC(NULL);
|
||||
|
||||
// We can create an HBITMAP directly using the shared memory handle, saving
|
||||
// a memcpy.
|
||||
HBITMAP source_hbitmap =
|
||||
::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, NULL,
|
||||
bitmap->handle(), 0);
|
||||
|
||||
if (source_hbitmap) {
|
||||
// Now we can write the HBITMAP to the clipboard
|
||||
WriteBitmapFromHandle(source_hbitmap, *size);
|
||||
}
|
||||
|
||||
::DeleteObject(source_hbitmap);
|
||||
::ReleaseDC(NULL, dc);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmapFromHandle(HBITMAP source_hbitmap,
|
||||
const gfx::Size& size) {
|
||||
// We would like to just call ::SetClipboardData on the source_hbitmap,
|
||||
// but that bitmap might not be of a sort we can write to the clipboard.
|
||||
// For this reason, we create a new bitmap, copy the bits over, and then
|
||||
// write that to the clipboard.
|
||||
|
||||
HDC dc = ::GetDC(NULL);
|
||||
HDC compatible_dc = ::CreateCompatibleDC(NULL);
|
||||
HDC source_dc = ::CreateCompatibleDC(NULL);
|
||||
|
||||
// This is the HBITMAP we will eventually write to the clipboard
|
||||
HBITMAP hbitmap = ::CreateCompatibleBitmap(dc, size.width(), size.height());
|
||||
if (!hbitmap) {
|
||||
// Failed to create the bitmap
|
||||
::DeleteDC(compatible_dc);
|
||||
::DeleteDC(source_dc);
|
||||
::ReleaseDC(NULL, dc);
|
||||
return;
|
||||
}
|
||||
|
||||
HBITMAP old_hbitmap = (HBITMAP)SelectObject(compatible_dc, hbitmap);
|
||||
HBITMAP old_source = (HBITMAP)SelectObject(source_dc, source_hbitmap);
|
||||
|
||||
// Now we need to blend it into an HBITMAP we can place on the clipboard
|
||||
BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
|
||||
::GdiAlphaBlend(compatible_dc, 0, 0, size.width(), size.height(),
|
||||
source_dc, 0, 0, size.width(), size.height(), bf);
|
||||
|
||||
// Clean up all the handles we just opened
|
||||
::SelectObject(compatible_dc, old_hbitmap);
|
||||
::SelectObject(source_dc, old_source);
|
||||
::DeleteObject(old_hbitmap);
|
||||
::DeleteObject(old_source);
|
||||
::DeleteDC(compatible_dc);
|
||||
::DeleteDC(source_dc);
|
||||
::ReleaseDC(NULL, dc);
|
||||
|
||||
WriteToClipboard(CF_BITMAP, hbitmap);
|
||||
}
|
||||
|
||||
// Write a file or set of files to the clipboard in HDROP format. When the user
|
||||
// invokes a paste command (in a Windows explorer shell, for example), the files
|
||||
// will be copied to the paste location.
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
// Calculate the amount of space we'll need store the strings and
|
||||
// a DROPFILES struct.
|
||||
size_t bytes = sizeof(DROPFILES) + file_len;
|
||||
|
||||
HANDLE hdata = ::GlobalAlloc(GMEM_MOVEABLE, bytes);
|
||||
if (!hdata)
|
||||
return;
|
||||
|
||||
char* data = static_cast<char*>(::GlobalLock(hdata));
|
||||
DROPFILES* drop_files = reinterpret_cast<DROPFILES*>(data);
|
||||
drop_files->pFiles = sizeof(DROPFILES);
|
||||
drop_files->fWide = TRUE;
|
||||
|
||||
memcpy(data + sizeof(DROPFILES), file_data, file_len);
|
||||
|
||||
::GlobalUnlock(hdata);
|
||||
WriteToClipboard(CF_HDROP, hdata);
|
||||
}
|
||||
|
||||
void Clipboard::WriteToClipboard(unsigned int format, HANDLE handle) {
|
||||
DCHECK(clipboard_owner_);
|
||||
if (handle && !::SetClipboardData(format, handle)) {
|
||||
DCHECK(ERROR_CLIPBOARD_NOT_OPEN != GetLastError());
|
||||
FreeData(format, handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
return ::IsClipboardFormatAvailable(StringToInt(format)) != FALSE;
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
if (!result) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
result->assign(static_cast<const char16*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
if (!result) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(CF_TEXT);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
result->assign(static_cast<const char*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
if (markup)
|
||||
markup->clear();
|
||||
|
||||
if (src_url)
|
||||
src_url->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(StringToInt(GetHtmlFormatType()));
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
std::string html_fragment(static_cast<const char*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
|
||||
std::string markup_utf8;
|
||||
ClipboardUtil::CFHtmlToHtml(html_fragment, &markup_utf8, src_url);
|
||||
markup->assign(UTF8ToWide(markup_utf8));
|
||||
}
|
||||
|
||||
void Clipboard::ReadBookmark(string16* title, std::string* url) const {
|
||||
if (title)
|
||||
title->clear();
|
||||
|
||||
if (url)
|
||||
url->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(StringToInt(GetUrlWFormatType()));
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
string16 bookmark(static_cast<const char16*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
|
||||
ParseBookmarkClipboardFormat(bookmark, title, url);
|
||||
}
|
||||
|
||||
// Read a file in HDROP format from the clipboard.
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
if (!file) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
*file = FilePath();
|
||||
std::vector<FilePath> files;
|
||||
ReadFiles(&files);
|
||||
|
||||
// Take the first file, if available.
|
||||
if (!files.empty())
|
||||
*file = files[0];
|
||||
}
|
||||
|
||||
// Read a set of files in HDROP format from the clipboard.
|
||||
void Clipboard::ReadFiles(std::vector<FilePath>* files) const {
|
||||
if (!files) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
files->clear();
|
||||
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HDROP drop = static_cast<HDROP>(::GetClipboardData(CF_HDROP));
|
||||
if (!drop)
|
||||
return;
|
||||
|
||||
// Count of files in the HDROP.
|
||||
int count = ::DragQueryFile(drop, 0xffffffff, NULL, 0);
|
||||
|
||||
if (count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int size = ::DragQueryFile(drop, i, NULL, 0) + 1;
|
||||
std::wstring file;
|
||||
::DragQueryFile(drop, i, WriteInto(&file, size), size);
|
||||
files->push_back(FilePath(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::ParseBookmarkClipboardFormat(const string16& bookmark,
|
||||
string16* title,
|
||||
std::string* url) {
|
||||
const string16 kDelim = ASCIIToUTF16("\r\n");
|
||||
|
||||
const size_t title_end = bookmark.find_first_of(kDelim);
|
||||
if (title)
|
||||
title->assign(bookmark.substr(0, title_end));
|
||||
|
||||
if (url) {
|
||||
const size_t url_start = bookmark.find_first_not_of(kDelim, title_end);
|
||||
if (url_start != string16::npos)
|
||||
*url = UTF16ToUTF8(bookmark.substr(url_start, string16::npos));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetUrlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetUrlWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetMozUrlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetMozUrlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
return IntToString(ClipboardUtil::GetPlainTextFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetPlainTextWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFilenameFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFilenameWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// MS HTML Format
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetHtmlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetBitmapFormatType() {
|
||||
return IntToString(CF_BITMAP);
|
||||
}
|
||||
|
||||
// Firefox text/html
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetTextHtmlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetTextHtmlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetCFHDropFormatType() {
|
||||
return IntToString(ClipboardUtil::GetCFHDropFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFileDescriptorFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFileDescriptorFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFileContentFormatZeroType() {
|
||||
return IntToString(ClipboardUtil::GetFileContentFormatZero()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::DuplicateRemoteHandles(base::ProcessHandle process,
|
||||
ObjectMap* objects) {
|
||||
for (ObjectMap::iterator iter = objects->begin(); iter != objects->end();
|
||||
++iter) {
|
||||
if (iter->first == CBF_SMBITMAP) {
|
||||
// There is a shared memory handle encoded on the first ObjectMapParam.
|
||||
// Use it to open a local handle to the memory.
|
||||
char* bitmap_data = &(iter->second[0].front());
|
||||
base::SharedMemoryHandle* remote_bitmap_handle =
|
||||
reinterpret_cast<base::SharedMemoryHandle*>(bitmap_data);
|
||||
|
||||
base::SharedMemory* bitmap = new base::SharedMemory(*remote_bitmap_handle,
|
||||
false, process);
|
||||
|
||||
// We store the object where the remote handle was located so it can
|
||||
// be retrieved by the UI thread (see WriteBitmapFromSharedMemory()).
|
||||
iter->second[0].clear();
|
||||
for (size_t i = 0; i < sizeof(bitmap); i++)
|
||||
iter->second[0].push_back(reinterpret_cast<char*>(&bitmap)[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
return IntToString(ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::FreeData(unsigned int format, HANDLE data) {
|
||||
if (format == CF_BITMAP)
|
||||
::DeleteObject(static_cast<HBITMAP>(data));
|
||||
else
|
||||
::GlobalFree(data);
|
||||
}
|
||||
|
||||
HWND Clipboard::GetClipboardWindow() const {
|
||||
if (!clipboard_owner_ && create_window_) {
|
||||
clipboard_owner_ = ::CreateWindow(L"ClipboardOwnerWindowClass",
|
||||
L"ClipboardOwnerWindow",
|
||||
0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE,
|
||||
0, 0, 0);
|
||||
}
|
||||
return clipboard_owner_;
|
||||
}
|
|
@ -0,0 +1,362 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/command_line.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/singleton.h"
|
||||
#include "base/string_piece.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/sys_string_conversions.h"
|
||||
|
||||
CommandLine* CommandLine::current_process_commandline_ = NULL;
|
||||
|
||||
// Since we use a lazy match, make sure that longer versions (like L"--")
|
||||
// are listed before shorter versions (like L"-") of similar prefixes.
|
||||
#if defined(OS_WIN)
|
||||
const wchar_t* const kSwitchPrefixes[] = {L"--", L"-", L"/"};
|
||||
const wchar_t kSwitchTerminator[] = L"--";
|
||||
const wchar_t kSwitchValueSeparator[] = L"=";
|
||||
#elif defined(OS_POSIX)
|
||||
// Unixes don't use slash as a switch.
|
||||
const char* const kSwitchPrefixes[] = {"--", "-"};
|
||||
const char kSwitchTerminator[] = "--";
|
||||
const char kSwitchValueSeparator[] = "=";
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Lowercase a string. This is used to lowercase switch names.
|
||||
// Is this what we really want? It seems crazy to me. I've left it in
|
||||
// for backwards compatibility on Windows.
|
||||
static void Lowercase(std::wstring* parameter) {
|
||||
transform(parameter->begin(), parameter->end(), parameter->begin(),
|
||||
tolower);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
void CommandLine::ParseFromString(const std::wstring& command_line) {
|
||||
TrimWhitespace(command_line, TRIM_ALL, &command_line_string_);
|
||||
|
||||
if (command_line_string_.empty())
|
||||
return;
|
||||
|
||||
int num_args = 0;
|
||||
wchar_t** args = NULL;
|
||||
|
||||
args = CommandLineToArgvW(command_line_string_.c_str(), &num_args);
|
||||
|
||||
// Populate program_ with the trimmed version of the first arg.
|
||||
TrimWhitespace(args[0], TRIM_ALL, &program_);
|
||||
|
||||
bool parse_switches = true;
|
||||
for (int i = 1; i < num_args; ++i) {
|
||||
std::wstring arg;
|
||||
TrimWhitespace(args[i], TRIM_ALL, &arg);
|
||||
|
||||
if (!parse_switches) {
|
||||
loose_values_.push_back(arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == kSwitchTerminator) {
|
||||
parse_switches = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string switch_string;
|
||||
std::wstring switch_value;
|
||||
if (IsSwitch(arg, &switch_string, &switch_value)) {
|
||||
switches_[switch_string] = switch_value;
|
||||
} else {
|
||||
loose_values_.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (args)
|
||||
LocalFree(args);
|
||||
}
|
||||
CommandLine::CommandLine(const std::wstring& program) {
|
||||
if (!program.empty()) {
|
||||
program_ = program;
|
||||
command_line_string_ = L'"' + program + L'"';
|
||||
}
|
||||
}
|
||||
#elif defined(OS_POSIX)
|
||||
CommandLine::CommandLine(int argc, const char* const* argv) {
|
||||
for (int i = 0; i < argc; ++i)
|
||||
argv_.push_back(argv[i]);
|
||||
InitFromArgv();
|
||||
}
|
||||
CommandLine::CommandLine(const std::vector<std::string>& argv) {
|
||||
argv_ = argv;
|
||||
InitFromArgv();
|
||||
}
|
||||
|
||||
void CommandLine::InitFromArgv() {
|
||||
bool parse_switches = true;
|
||||
for (size_t i = 1; i < argv_.size(); ++i) {
|
||||
const std::string& arg = argv_[i];
|
||||
|
||||
if (!parse_switches) {
|
||||
loose_values_.push_back(arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == kSwitchTerminator) {
|
||||
parse_switches = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string switch_string;
|
||||
std::string switch_value;
|
||||
if (IsSwitch(arg, &switch_string, &switch_value)) {
|
||||
switches_[switch_string] = switch_value;
|
||||
} else {
|
||||
loose_values_.push_back(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommandLine::CommandLine(const std::wstring& program) {
|
||||
argv_.push_back(WideToASCII(program));
|
||||
}
|
||||
#endif
|
||||
|
||||
// static
|
||||
bool CommandLine::IsSwitch(const StringType& parameter_string,
|
||||
std::string* switch_string,
|
||||
StringType* switch_value) {
|
||||
switch_string->clear();
|
||||
switch_value->clear();
|
||||
|
||||
for (size_t i = 0; i < arraysize(kSwitchPrefixes); ++i) {
|
||||
StringType prefix(kSwitchPrefixes[i]);
|
||||
if (parameter_string.find(prefix) != 0)
|
||||
continue;
|
||||
|
||||
const size_t switch_start = prefix.length();
|
||||
const size_t equals_position = parameter_string.find(
|
||||
kSwitchValueSeparator, switch_start);
|
||||
StringType switch_native;
|
||||
if (equals_position == StringType::npos) {
|
||||
switch_native = parameter_string.substr(switch_start);
|
||||
} else {
|
||||
switch_native = parameter_string.substr(
|
||||
switch_start, equals_position - switch_start);
|
||||
*switch_value = parameter_string.substr(equals_position + 1);
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
Lowercase(&switch_native);
|
||||
*switch_string = WideToASCII(switch_native);
|
||||
#else
|
||||
*switch_string = switch_native;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
void CommandLine::Init(int argc, const char* const* argv) {
|
||||
DCHECK(current_process_commandline_ == NULL);
|
||||
#if defined(OS_WIN)
|
||||
current_process_commandline_ = new CommandLine;
|
||||
current_process_commandline_->ParseFromString(::GetCommandLineW());
|
||||
#elif defined(OS_POSIX)
|
||||
current_process_commandline_ = new CommandLine(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandLine::Terminate() {
|
||||
DCHECK(current_process_commandline_ != NULL);
|
||||
delete current_process_commandline_;
|
||||
current_process_commandline_ = NULL;
|
||||
}
|
||||
|
||||
bool CommandLine::HasSwitch(const std::wstring& switch_string) const {
|
||||
std::wstring lowercased_switch(switch_string);
|
||||
#if defined(OS_WIN)
|
||||
Lowercase(&lowercased_switch);
|
||||
#endif
|
||||
return switches_.find(WideToASCII(lowercased_switch)) != switches_.end();
|
||||
}
|
||||
|
||||
std::wstring CommandLine::GetSwitchValue(
|
||||
const std::wstring& switch_string) const {
|
||||
std::wstring lowercased_switch(switch_string);
|
||||
#if defined(OS_WIN)
|
||||
Lowercase(&lowercased_switch);
|
||||
#endif
|
||||
|
||||
std::map<std::string, StringType>::const_iterator result =
|
||||
switches_.find(WideToASCII(lowercased_switch));
|
||||
|
||||
if (result == switches_.end()) {
|
||||
return L"";
|
||||
} else {
|
||||
#if defined(OS_WIN)
|
||||
return result->second;
|
||||
#else
|
||||
return ASCIIToWide(result->second);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
std::vector<std::wstring> CommandLine::GetLooseValues() const {
|
||||
return loose_values_;
|
||||
}
|
||||
std::wstring CommandLine::program() const {
|
||||
return program_;
|
||||
}
|
||||
#else
|
||||
std::vector<std::wstring> CommandLine::GetLooseValues() const {
|
||||
std::vector<std::wstring> values;
|
||||
for (size_t i = 0; i < loose_values_.size(); ++i)
|
||||
values.push_back(ASCIIToWide(loose_values_[i]));
|
||||
return values;
|
||||
}
|
||||
std::wstring CommandLine::program() const {
|
||||
DCHECK(argv_.size() > 0);
|
||||
return ASCIIToWide(argv_[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// static
|
||||
std::wstring CommandLine::PrefixedSwitchString(
|
||||
const std::wstring& switch_string) {
|
||||
return StringPrintf(L"%ls%ls",
|
||||
kSwitchPrefixes[0],
|
||||
switch_string.c_str());
|
||||
}
|
||||
|
||||
// static
|
||||
std::wstring CommandLine::PrefixedSwitchStringWithValue(
|
||||
const std::wstring& switch_string, const std::wstring& value_string) {
|
||||
if (value_string.empty()) {
|
||||
return PrefixedSwitchString(switch_string);
|
||||
}
|
||||
|
||||
return StringPrintf(L"%ls%ls%ls%ls",
|
||||
kSwitchPrefixes[0],
|
||||
switch_string.c_str(),
|
||||
kSwitchValueSeparator,
|
||||
value_string.c_str());
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
void CommandLine::AppendSwitch(const std::wstring& switch_string) {
|
||||
std::wstring prefixed_switch_string = PrefixedSwitchString(switch_string);
|
||||
command_line_string_.append(L" ");
|
||||
command_line_string_.append(prefixed_switch_string);
|
||||
switches_[WideToASCII(switch_string)] = L"";
|
||||
}
|
||||
|
||||
void CommandLine::AppendSwitchWithValue(const std::wstring& switch_string,
|
||||
const std::wstring& value_string) {
|
||||
std::wstring value_string_edit;
|
||||
|
||||
// NOTE(jhughes): If the value contains a quotation mark at one
|
||||
// end but not both, you may get unusable output.
|
||||
if (!value_string.empty() &&
|
||||
(value_string.find(L" ") != std::wstring::npos) &&
|
||||
(value_string[0] != L'"') &&
|
||||
(value_string[value_string.length() - 1] != L'"')) {
|
||||
// need to provide quotes
|
||||
value_string_edit = StringPrintf(L"\"%ls\"", value_string.c_str());
|
||||
} else {
|
||||
value_string_edit = value_string;
|
||||
}
|
||||
|
||||
std::wstring combined_switch_string =
|
||||
PrefixedSwitchStringWithValue(switch_string, value_string_edit);
|
||||
|
||||
command_line_string_.append(L" ");
|
||||
command_line_string_.append(combined_switch_string);
|
||||
|
||||
switches_[WideToASCII(switch_string)] = value_string;
|
||||
}
|
||||
|
||||
void CommandLine::AppendLooseValue(const std::wstring& value) {
|
||||
// TODO(evan): quoting?
|
||||
command_line_string_.append(L" ");
|
||||
command_line_string_.append(value);
|
||||
}
|
||||
|
||||
void CommandLine::AppendArguments(const CommandLine& other,
|
||||
bool include_program) {
|
||||
// Verify include_program is used correctly.
|
||||
// Logic could be shorter but this is clearer.
|
||||
DCHECK(include_program ? !other.program().empty() : other.program().empty());
|
||||
command_line_string_ += L" " + other.command_line_string_;
|
||||
std::map<std::string, StringType>::const_iterator i;
|
||||
for (i = other.switches_.begin(); i != other.switches_.end(); ++i)
|
||||
switches_[i->first] = i->second;
|
||||
}
|
||||
|
||||
void CommandLine::PrependWrapper(const std::wstring& wrapper) {
|
||||
// The wrapper may have embedded arguments (like "gdb --args"). In this case,
|
||||
// we don't pretend to do anything fancy, we just split on spaces.
|
||||
std::vector<std::wstring> wrapper_and_args;
|
||||
SplitString(wrapper, ' ', &wrapper_and_args);
|
||||
program_ = wrapper_and_args[0];
|
||||
command_line_string_ = wrapper + L" " + command_line_string_;
|
||||
}
|
||||
|
||||
#elif defined(OS_POSIX)
|
||||
void CommandLine::AppendSwitch(const std::wstring& switch_string) {
|
||||
std::string ascii_switch = WideToASCII(switch_string);
|
||||
argv_.push_back(kSwitchPrefixes[0] + ascii_switch);
|
||||
switches_[ascii_switch] = "";
|
||||
}
|
||||
|
||||
void CommandLine::AppendSwitchWithValue(const std::wstring& switch_string,
|
||||
const std::wstring& value_string) {
|
||||
std::string ascii_switch = WideToASCII(switch_string);
|
||||
std::string ascii_value = WideToASCII(value_string);
|
||||
|
||||
argv_.push_back(kSwitchPrefixes[0] + ascii_switch +
|
||||
kSwitchValueSeparator + ascii_value);
|
||||
switches_[ascii_switch] = ascii_value;
|
||||
}
|
||||
|
||||
void CommandLine::AppendLooseValue(const std::wstring& value) {
|
||||
argv_.push_back(WideToASCII(value));
|
||||
}
|
||||
|
||||
void CommandLine::AppendArguments(const CommandLine& other,
|
||||
bool include_program) {
|
||||
// Verify include_program is used correctly.
|
||||
// Logic could be shorter but this is clearer.
|
||||
DCHECK(include_program ? !other.program().empty() : other.program().empty());
|
||||
|
||||
size_t first_arg = include_program ? 0 : 1;
|
||||
for (size_t i = first_arg; i < other.argv_.size(); ++i)
|
||||
argv_.push_back(other.argv_[i]);
|
||||
std::map<std::string, StringType>::const_iterator i;
|
||||
for (i = other.switches_.begin(); i != other.switches_.end(); ++i)
|
||||
switches_[i->first] = i->second;
|
||||
}
|
||||
|
||||
void CommandLine::PrependWrapper(const std::wstring& wrapper_wide) {
|
||||
// The wrapper may have embedded arguments (like "gdb --args"). In this case,
|
||||
// we don't pretend to do anything fancy, we just split on spaces.
|
||||
const std::string wrapper = WideToASCII(wrapper_wide);
|
||||
std::vector<std::string> wrapper_and_args;
|
||||
SplitString(wrapper, ' ', &wrapper_and_args);
|
||||
argv_.insert(argv_.begin(), wrapper_and_args.begin(), wrapper_and_args.end());
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This class works with command lines: building and parsing.
|
||||
// Switches can optionally have a value attached using an equals sign,
|
||||
// as in "-switch=value". Arguments that aren't prefixed with a
|
||||
// switch prefix are considered "loose parameters". Switch names are
|
||||
// case-insensitive. An argument of "--" will terminate switch
|
||||
// parsing, causing everything after to be considered as loose
|
||||
// parameters.
|
||||
|
||||
// There is a singleton read-only CommandLine that represents the command
|
||||
// line that the current process was started with. It must be initialized
|
||||
// in main() (or whatever the platform's equivalent function is).
|
||||
|
||||
#ifndef BASE_COMMAND_LINE_H_
|
||||
#define BASE_COMMAND_LINE_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
class InProcessBrowserTest;
|
||||
|
||||
class CommandLine {
|
||||
public:
|
||||
#if defined(OS_WIN)
|
||||
// Creates a parsed version of the given command-line string.
|
||||
// The program name is assumed to be the first item in the string.
|
||||
void ParseFromString(const std::wstring& command_line);
|
||||
#elif defined(OS_POSIX)
|
||||
// Initialize from an argv vector (or directly from main()'s argv).
|
||||
CommandLine(int argc, const char* const* argv);
|
||||
explicit CommandLine(const std::vector<std::string>& argv);
|
||||
#endif
|
||||
|
||||
// Construct a new, empty command line.
|
||||
// |program| is the name of the program to run (aka argv[0]).
|
||||
// TODO(port): should be a FilePath.
|
||||
explicit CommandLine(const std::wstring& program);
|
||||
|
||||
// Initialize the current process CommandLine singleton. On Windows,
|
||||
// ignores its arguments (we instead parse GetCommandLineW()
|
||||
// directly) because we don't trust the CRT's parsing of the command
|
||||
// line, but it still must be called to set up the command line.
|
||||
static void Init(int argc, const char* const* argv);
|
||||
|
||||
// Destroys the current process CommandLine singleton. This is necessary if
|
||||
// you want to reset the base library to its initial state (for example in an
|
||||
// outer library that needs to be able to terminate, and be re-initialized).
|
||||
// If Init is called only once, e.g. in main(), calling Terminate() is not
|
||||
// necessary.
|
||||
static void Terminate();
|
||||
|
||||
// Get the singleton CommandLine representing the current process's
|
||||
// command line.
|
||||
static const CommandLine* ForCurrentProcess() {
|
||||
DCHECK(current_process_commandline_);
|
||||
return current_process_commandline_;
|
||||
}
|
||||
|
||||
// Returns true if this command line contains the given switch.
|
||||
// (Switch names are case-insensitive.)
|
||||
bool HasSwitch(const std::wstring& switch_string) const;
|
||||
|
||||
// Returns the value associated with the given switch. If the
|
||||
// switch has no value or isn't present, this method returns
|
||||
// the empty string.
|
||||
std::wstring GetSwitchValue(const std::wstring& switch_string) const;
|
||||
|
||||
// Get the remaining arguments to the command.
|
||||
// WARNING: this is incorrect on POSIX; we must do string conversions.
|
||||
std::vector<std::wstring> GetLooseValues() const;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Returns the original command line string.
|
||||
const std::wstring& command_line_string() const {
|
||||
return command_line_string_;
|
||||
}
|
||||
#elif defined(OS_POSIX)
|
||||
// Returns the original command line string as a vector of strings.
|
||||
const std::vector<std::string>& argv() const {
|
||||
return argv_;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Returns the program part of the command line string (the first item).
|
||||
std::wstring program() const;
|
||||
|
||||
// Return a copy of the string prefixed with a switch prefix.
|
||||
// Used internally.
|
||||
static std::wstring PrefixedSwitchString(const std::wstring& switch_string);
|
||||
|
||||
// Return a copy of the string prefixed with a switch prefix,
|
||||
// and appended with the given value. Used internally.
|
||||
static std::wstring PrefixedSwitchStringWithValue(
|
||||
const std::wstring& switch_string,
|
||||
const std::wstring& value_string);
|
||||
|
||||
// Appends the given switch string (preceded by a space and a switch
|
||||
// prefix) to the given string.
|
||||
void AppendSwitch(const std::wstring& switch_string);
|
||||
|
||||
// Appends the given switch string (preceded by a space and a switch
|
||||
// prefix) to the given string, with the given value attached.
|
||||
void AppendSwitchWithValue(const std::wstring& switch_string,
|
||||
const std::wstring& value_string);
|
||||
|
||||
// Append a loose value to the command line.
|
||||
void AppendLooseValue(const std::wstring& value);
|
||||
|
||||
// Append the arguments from another command line to this one.
|
||||
// If |include_program| is true, include |other|'s program as well.
|
||||
void AppendArguments(const CommandLine& other,
|
||||
bool include_program);
|
||||
|
||||
// On POSIX systems it's common to run processes via a wrapper (like
|
||||
// "valgrind" or "gdb --args").
|
||||
void PrependWrapper(const std::wstring& wrapper);
|
||||
|
||||
private:
|
||||
friend class InProcessBrowserTest;
|
||||
|
||||
CommandLine() {}
|
||||
|
||||
// Used by InProcessBrowserTest.
|
||||
static CommandLine* ForCurrentProcessMutable() {
|
||||
DCHECK(current_process_commandline_);
|
||||
return current_process_commandline_;
|
||||
}
|
||||
|
||||
// The singleton CommandLine instance representing the current process's
|
||||
// command line.
|
||||
static CommandLine* current_process_commandline_;
|
||||
|
||||
// We store a platform-native version of the command line, used when building
|
||||
// up a new command line to be executed. This ifdef delimits that code.
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// The quoted, space-separated command-line string.
|
||||
std::wstring command_line_string_;
|
||||
|
||||
// The name of the program.
|
||||
std::wstring program_;
|
||||
|
||||
// The type of native command line arguments.
|
||||
typedef std::wstring StringType;
|
||||
|
||||
#elif defined(OS_POSIX)
|
||||
// The argv array, with the program name in argv_[0].
|
||||
std::vector<std::string> argv_;
|
||||
|
||||
// The type of native command line arguments.
|
||||
typedef std::string StringType;
|
||||
|
||||
// Shared by the two POSIX constructor forms. Initalize from argv_.
|
||||
void InitFromArgv();
|
||||
#endif
|
||||
|
||||
// Returns true and fills in |switch_string| and |switch_value|
|
||||
// if |parameter_string| represents a switch.
|
||||
static bool IsSwitch(const StringType& parameter_string,
|
||||
std::string* switch_string,
|
||||
StringType* switch_value);
|
||||
|
||||
// Parsed-out values.
|
||||
std::map<std::string, StringType> switches_;
|
||||
|
||||
// Non-switch command-line arguments.
|
||||
std::vector<StringType> loose_values_;
|
||||
|
||||
// We allow copy constructors, because a common pattern is to grab a
|
||||
// copy of the current process's command line and then add some
|
||||
// flags to it. E.g.:
|
||||
// CommandLine cl(*CommandLine::ForCurrentProcess());
|
||||
// cl.AppendSwitch(...);
|
||||
};
|
||||
|
||||
#endif // BASE_COMMAND_LINE_H_
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/basictypes.h"
|
||||
#include "base/string_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
TEST(CommandLineTest, CommandLineConstructor) {
|
||||
#if defined(OS_WIN)
|
||||
CommandLine cl(L"");
|
||||
cl.ParseFromString(L"program --foo= -bAr /Spaetzel=pierogi /Baz flim "
|
||||
L"--other-switches=\"--dog=canine --cat=feline\" "
|
||||
L"-spaetzle=Crepe -=loosevalue flan "
|
||||
L"--input-translation=\"45\"--output-rotation "
|
||||
L"-- -- --not-a-switch "
|
||||
L"\"in the time of submarines...\"");
|
||||
EXPECT_FALSE(cl.command_line_string().empty());
|
||||
#elif defined(OS_POSIX)
|
||||
const char* argv[] = {"program", "--foo=", "-bar",
|
||||
"-spaetzel=pierogi", "-baz", "flim",
|
||||
"--other-switches=--dog=canine --cat=feline",
|
||||
"-spaetzle=Crepe", "-=loosevalue", "flan",
|
||||
"--input-translation=45--output-rotation",
|
||||
"--", "--", "--not-a-switch",
|
||||
"in the time of submarines..."};
|
||||
CommandLine cl(arraysize(argv), argv);
|
||||
#endif
|
||||
EXPECT_FALSE(cl.HasSwitch(L"cruller"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"flim"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"program"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"dog"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"cat"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"output-rotation"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"not-a-switch"));
|
||||
EXPECT_FALSE(cl.HasSwitch(L"--"));
|
||||
|
||||
EXPECT_EQ(L"program", cl.program());
|
||||
|
||||
EXPECT_TRUE(cl.HasSwitch(L"foo"));
|
||||
EXPECT_TRUE(cl.HasSwitch(L"bar"));
|
||||
EXPECT_TRUE(cl.HasSwitch(L"baz"));
|
||||
EXPECT_TRUE(cl.HasSwitch(L"spaetzle"));
|
||||
#if defined(OS_WIN)
|
||||
EXPECT_TRUE(cl.HasSwitch(L"SPAETZLE"));
|
||||
#endif
|
||||
EXPECT_TRUE(cl.HasSwitch(L"other-switches"));
|
||||
EXPECT_TRUE(cl.HasSwitch(L"input-translation"));
|
||||
|
||||
EXPECT_EQ(L"Crepe", cl.GetSwitchValue(L"spaetzle"));
|
||||
EXPECT_EQ(L"", cl.GetSwitchValue(L"Foo"));
|
||||
EXPECT_EQ(L"", cl.GetSwitchValue(L"bar"));
|
||||
EXPECT_EQ(L"", cl.GetSwitchValue(L"cruller"));
|
||||
EXPECT_EQ(L"--dog=canine --cat=feline", cl.GetSwitchValue(L"other-switches"));
|
||||
EXPECT_EQ(L"45--output-rotation", cl.GetSwitchValue(L"input-translation"));
|
||||
|
||||
std::vector<std::wstring> loose_values = cl.GetLooseValues();
|
||||
ASSERT_EQ(5U, loose_values.size());
|
||||
|
||||
std::vector<std::wstring>::const_iterator iter = loose_values.begin();
|
||||
EXPECT_EQ(L"flim", *iter);
|
||||
++iter;
|
||||
EXPECT_EQ(L"flan", *iter);
|
||||
++iter;
|
||||
EXPECT_EQ(L"--", *iter);
|
||||
++iter;
|
||||
EXPECT_EQ(L"--not-a-switch", *iter);
|
||||
++iter;
|
||||
EXPECT_EQ(L"in the time of submarines...", *iter);
|
||||
++iter;
|
||||
EXPECT_TRUE(iter == loose_values.end());
|
||||
#if defined(OS_POSIX)
|
||||
const std::vector<std::string>& argvec = cl.argv();
|
||||
|
||||
for (size_t i = 0; i < argvec.size(); i++) {
|
||||
EXPECT_EQ(0, argvec[i].compare(argv[i]));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Tests behavior with an empty input string.
|
||||
TEST(CommandLineTest, EmptyString) {
|
||||
#if defined(OS_WIN)
|
||||
CommandLine cl(L"");
|
||||
EXPECT_TRUE(cl.command_line_string().empty());
|
||||
EXPECT_TRUE(cl.program().empty());
|
||||
#elif defined(OS_POSIX)
|
||||
CommandLine cl(0, NULL);
|
||||
EXPECT_TRUE(cl.argv().size() == 0);
|
||||
#endif
|
||||
EXPECT_EQ(0U, cl.GetLooseValues().size());
|
||||
}
|
||||
|
||||
// Test methods for appending switches to a command line.
|
||||
TEST(CommandLineTest, AppendSwitches) {
|
||||
std::wstring switch1 = L"switch1";
|
||||
std::wstring switch2 = L"switch2";
|
||||
std::wstring value = L"value";
|
||||
std::wstring switch3 = L"switch3";
|
||||
std::wstring value3 = L"a value with spaces";
|
||||
std::wstring switch4 = L"switch4";
|
||||
std::wstring value4 = L"\"a value with quotes\"";
|
||||
|
||||
#if defined(OS_WIN)
|
||||
CommandLine cl(L"Program");
|
||||
#elif defined(OS_POSIX)
|
||||
std::vector<std::string> argv;
|
||||
argv.push_back(std::string("Program"));
|
||||
CommandLine cl(argv);
|
||||
#endif
|
||||
|
||||
cl.AppendSwitch(switch1);
|
||||
cl.AppendSwitchWithValue(switch2, value);
|
||||
cl.AppendSwitchWithValue(switch3, value3);
|
||||
cl.AppendSwitchWithValue(switch4, value4);
|
||||
|
||||
EXPECT_TRUE(cl.HasSwitch(switch1));
|
||||
EXPECT_TRUE(cl.HasSwitch(switch2));
|
||||
EXPECT_EQ(value, cl.GetSwitchValue(switch2));
|
||||
EXPECT_TRUE(cl.HasSwitch(switch3));
|
||||
EXPECT_EQ(value3, cl.GetSwitchValue(switch3));
|
||||
EXPECT_TRUE(cl.HasSwitch(switch4));
|
||||
EXPECT_EQ(value4, cl.GetSwitchValue(switch4));
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_COMPILER_SPECIFIC_H_
|
||||
#define BASE_COMPILER_SPECIFIC_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
|
||||
// Macros for suppressing and disabling warnings on MSVC.
|
||||
//
|
||||
// Warning numbers are enumerated at:
|
||||
// http://msdn.microsoft.com/en-us/library/8x5x43k7(VS.80).aspx
|
||||
//
|
||||
// The warning pragma:
|
||||
// http://msdn.microsoft.com/en-us/library/2c8f766e(VS.80).aspx
|
||||
//
|
||||
// Using __pragma instead of #pragma inside macros:
|
||||
// http://msdn.microsoft.com/en-us/library/d9x1s805.aspx
|
||||
|
||||
// MSVC_SUPPRESS_WARNING disables warning |n| for the remainder of the line and
|
||||
// for the next line of the source file.
|
||||
#define MSVC_SUPPRESS_WARNING(n) __pragma(warning(suppress:n))
|
||||
|
||||
// MSVC_PUSH_DISABLE_WARNING pushes |n| onto a stack of warnings to be disabled.
|
||||
// The warning remains disabled until popped by MSVC_POP_WARNING.
|
||||
#define MSVC_PUSH_DISABLE_WARNING(n) __pragma(warning(push)) \
|
||||
__pragma(warning(disable:n))
|
||||
|
||||
// MSVC_PUSH_WARNING_LEVEL pushes |n| as the global warning level. The level
|
||||
// remains in effect until popped by MSVC_POP_WARNING(). Use 0 to disable all
|
||||
// warnings.
|
||||
#define MSVC_PUSH_WARNING_LEVEL(n) __pragma(warning(push, n))
|
||||
|
||||
// Pop effects of innermost MSVC_PUSH_* macro.
|
||||
#define MSVC_POP_WARNING() __pragma(warning(pop))
|
||||
|
||||
#define MSVC_DISABLE_OPTIMIZE() __pragma(optimize("", off))
|
||||
#define MSVC_ENABLE_OPTIMIZE() __pragma(optimize("", on))
|
||||
|
||||
// Allows |this| to be passed as an argument in constructor initializer lists.
|
||||
// This uses push/pop instead of the seemingly simpler suppress feature to avoid
|
||||
// having the warning be disabled for more than just |code|.
|
||||
//
|
||||
// Example usage:
|
||||
// Foo::Foo() : x(NULL), ALLOW_THIS_IN_INITIALIZER_LIST(y(this)), z(3) {}
|
||||
//
|
||||
// Compiler warning C4355: 'this': used in base member initializer list:
|
||||
// http://msdn.microsoft.com/en-us/library/3c594ae3(VS.80).aspx
|
||||
#define ALLOW_THIS_IN_INITIALIZER_LIST(code) MSVC_PUSH_DISABLE_WARNING(4355) \
|
||||
code \
|
||||
MSVC_POP_WARNING()
|
||||
|
||||
#else // Not MSVC
|
||||
|
||||
#define MSVC_SUPPRESS_WARNING(n)
|
||||
#define MSVC_PUSH_DISABLE_WARNING(n)
|
||||
#define MSVC_PUSH_WARNING_LEVEL(n)
|
||||
#define MSVC_POP_WARNING()
|
||||
#define MSVC_DISABLE_OPTIMIZE()
|
||||
#define MSVC_ENABLE_OPTIMIZE()
|
||||
#define ALLOW_THIS_IN_INITIALIZER_LIST(code) code
|
||||
|
||||
#endif // COMPILER_MSVC
|
||||
|
||||
|
||||
#if defined(COMPILER_GCC)
|
||||
#define ALLOW_UNUSED __attribute__((unused))
|
||||
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#else // Not GCC
|
||||
#define ALLOW_UNUSED
|
||||
#define WARN_UNUSED_RESULT
|
||||
#endif
|
||||
|
||||
#endif // BASE_COMPILER_SPECIFIC_H_
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ConditionVariable wraps pthreads condition variable synchronization or, on
|
||||
// Windows, simulates it. This functionality is very helpful for having
|
||||
// several threads wait for an event, as is common with a thread pool managed
|
||||
// by a master. The meaning of such an event in the (worker) thread pool
|
||||
// scenario is that additional tasks are now available for processing. It is
|
||||
// used in Chrome in the DNS prefetching system to notify worker threads that
|
||||
// a queue now has items (tasks) which need to be tended to. A related use
|
||||
// would have a pool manager waiting on a ConditionVariable, waiting for a
|
||||
// thread in the pool to announce (signal) that there is now more room in a
|
||||
// (bounded size) communications queue for the manager to deposit tasks, or,
|
||||
// as a second example, that the queue of tasks is completely empty and all
|
||||
// workers are waiting.
|
||||
//
|
||||
// USAGE NOTE 1: spurious signal events are possible with this and
|
||||
// most implementations of condition variables. As a result, be
|
||||
// *sure* to retest your condition before proceeding. The following
|
||||
// is a good example of doing this correctly:
|
||||
//
|
||||
// while (!work_to_be_done()) Wait(...);
|
||||
//
|
||||
// In contrast do NOT do the following:
|
||||
//
|
||||
// if (!work_to_be_done()) Wait(...); // Don't do this.
|
||||
//
|
||||
// Especially avoid the above if you are relying on some other thread only
|
||||
// issuing a signal up *if* there is work-to-do. There can/will
|
||||
// be spurious signals. Recheck state on waiting thread before
|
||||
// assuming the signal was intentional. Caveat caller ;-).
|
||||
//
|
||||
// USAGE NOTE 2: Broadcast() frees up all waiting threads at once,
|
||||
// which leads to contention for the locks they all held when they
|
||||
// called Wait(). This results in POOR performance. A much better
|
||||
// approach to getting a lot of threads out of Wait() is to have each
|
||||
// thread (upon exiting Wait()) call Signal() to free up another
|
||||
// Wait'ing thread. Look at condition_variable_unittest.cc for
|
||||
// both examples.
|
||||
//
|
||||
// Broadcast() can be used nicely during teardown, as it gets the job
|
||||
// done, and leaves no sleeping threads... and performance is less
|
||||
// critical at that point.
|
||||
//
|
||||
// The semantics of Broadcast() are carefully crafted so that *all*
|
||||
// threads that were waiting when the request was made will indeed
|
||||
// get signaled. Some implementations mess up, and don't signal them
|
||||
// all, while others allow the wait to be effectively turned off (for
|
||||
// a while while waiting threads come around). This implementation
|
||||
// appears correct, as it will not "lose" any signals, and will guarantee
|
||||
// that all threads get signaled by Broadcast().
|
||||
//
|
||||
// This implementation offers support for "performance" in its selection of
|
||||
// which thread to revive. Performance, in direct contrast with "fairness,"
|
||||
// assures that the thread that most recently began to Wait() is selected by
|
||||
// Signal to revive. Fairness would (if publicly supported) assure that the
|
||||
// thread that has Wait()ed the longest is selected. The default policy
|
||||
// may improve performance, as the selected thread may have a greater chance of
|
||||
// having some of its stack data in various CPU caches.
|
||||
//
|
||||
// For a discussion of the many very subtle implementation details, see the FAQ
|
||||
// at the end of condition_variable_win.cc.
|
||||
|
||||
#ifndef BASE_CONDITION_VARIABLE_H_
|
||||
#define BASE_CONDITION_VARIABLE_H_
|
||||
|
||||
#include "base/lock.h"
|
||||
|
||||
namespace base {
|
||||
class TimeDelta;
|
||||
}
|
||||
|
||||
class ConditionVariable {
|
||||
public:
|
||||
// Construct a cv for use with ONLY one user lock.
|
||||
explicit ConditionVariable(Lock* user_lock);
|
||||
|
||||
~ConditionVariable();
|
||||
|
||||
// Wait() releases the caller's critical section atomically as it starts to
|
||||
// sleep, and the reacquires it when it is signaled.
|
||||
void Wait();
|
||||
void TimedWait(const base::TimeDelta& max_time);
|
||||
|
||||
// Broadcast() revives all waiting threads.
|
||||
void Broadcast();
|
||||
// Signal() revives one waiting thread.
|
||||
void Signal();
|
||||
|
||||
private:
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
||||
// Define Event class that is used to form circularly linked lists.
|
||||
// The list container is an element with NULL as its handle_ value.
|
||||
// The actual list elements have a non-zero handle_ value.
|
||||
// All calls to methods MUST be done under protection of a lock so that links
|
||||
// can be validated. Without the lock, some links might asynchronously
|
||||
// change, and the assertions would fail (as would list change operations).
|
||||
class Event {
|
||||
public:
|
||||
// Default constructor with no arguments creates a list container.
|
||||
Event();
|
||||
~Event();
|
||||
|
||||
// InitListElement transitions an instance from a container, to an element.
|
||||
void InitListElement();
|
||||
|
||||
// Methods for use on lists.
|
||||
bool IsEmpty() const;
|
||||
void PushBack(Event* other);
|
||||
Event* PopFront();
|
||||
Event* PopBack();
|
||||
|
||||
// Methods for use on list elements.
|
||||
// Accessor method.
|
||||
HANDLE handle() const;
|
||||
// Pull an element from a list (if it's in one).
|
||||
Event* Extract();
|
||||
|
||||
// Method for use on a list element or on a list.
|
||||
bool IsSingleton() const;
|
||||
|
||||
private:
|
||||
// Provide pre/post conditions to validate correct manipulations.
|
||||
bool ValidateAsDistinct(Event* other) const;
|
||||
bool ValidateAsItem() const;
|
||||
bool ValidateAsList() const;
|
||||
bool ValidateLinks() const;
|
||||
|
||||
HANDLE handle_;
|
||||
Event* next_;
|
||||
Event* prev_;
|
||||
DISALLOW_COPY_AND_ASSIGN(Event);
|
||||
};
|
||||
|
||||
// Note that RUNNING is an unlikely number to have in RAM by accident.
|
||||
// This helps with defensive destructor coding in the face of user error.
|
||||
enum RunState { SHUTDOWN = 0, RUNNING = 64213 };
|
||||
|
||||
// Internal implementation methods supporting Wait().
|
||||
Event* GetEventForWaiting();
|
||||
void RecycleEvent(Event* used_event);
|
||||
|
||||
RunState run_state_;
|
||||
|
||||
// Private critical section for access to member data.
|
||||
Lock internal_lock_;
|
||||
|
||||
// Lock that is acquired before calling Wait().
|
||||
Lock& user_lock_;
|
||||
|
||||
// Events that threads are blocked on.
|
||||
Event waiting_list_;
|
||||
|
||||
// Free list for old events.
|
||||
Event recycling_list_;
|
||||
int recycling_list_size_;
|
||||
|
||||
// The number of allocated, but not yet deleted events.
|
||||
int allocation_counter_;
|
||||
|
||||
#elif defined(OS_POSIX)
|
||||
|
||||
pthread_cond_t condition_;
|
||||
pthread_mutex_t* user_mutex_;
|
||||
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ConditionVariable);
|
||||
};
|
||||
|
||||
#endif // BASE_CONDITION_VARIABLE_H_
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/condition_variable.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "base/lock.h"
|
||||
#include "base/lock_impl.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/time.h"
|
||||
|
||||
using base::Time;
|
||||
using base::TimeDelta;
|
||||
|
||||
ConditionVariable::ConditionVariable(Lock* user_lock)
|
||||
: user_mutex_(user_lock->lock_impl()->os_lock()) {
|
||||
int rv = pthread_cond_init(&condition_, NULL);
|
||||
DCHECK(rv == 0);
|
||||
}
|
||||
|
||||
ConditionVariable::~ConditionVariable() {
|
||||
int rv = pthread_cond_destroy(&condition_);
|
||||
DCHECK(rv == 0);
|
||||
}
|
||||
|
||||
void ConditionVariable::Wait() {
|
||||
int rv = pthread_cond_wait(&condition_, user_mutex_);
|
||||
DCHECK(rv == 0);
|
||||
}
|
||||
|
||||
void ConditionVariable::TimedWait(const TimeDelta& max_time) {
|
||||
int64 usecs = max_time.InMicroseconds();
|
||||
|
||||
// The timeout argument to pthread_cond_timedwait is in absolute time.
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
struct timespec abstime;
|
||||
abstime.tv_sec = now.tv_sec + (usecs / Time::kMicrosecondsPerSecond);
|
||||
abstime.tv_nsec = (now.tv_usec + (usecs % Time::kMicrosecondsPerSecond)) *
|
||||
Time::kNanosecondsPerMicrosecond;
|
||||
abstime.tv_sec += abstime.tv_nsec / Time::kNanosecondsPerSecond;
|
||||
abstime.tv_nsec %= Time::kNanosecondsPerSecond;
|
||||
DCHECK(abstime.tv_sec >= now.tv_sec); // Overflow paranoia
|
||||
|
||||
int rv = pthread_cond_timedwait(&condition_, user_mutex_, &abstime);
|
||||
DCHECK(rv == 0 || rv == ETIMEDOUT);
|
||||
}
|
||||
|
||||
void ConditionVariable::Broadcast() {
|
||||
int rv = pthread_cond_broadcast(&condition_);
|
||||
DCHECK(rv == 0);
|
||||
}
|
||||
|
||||
void ConditionVariable::Signal() {
|
||||
int rv = pthread_cond_signal(&condition_);
|
||||
DCHECK(rv == 0);
|
||||
}
|
|
@ -0,0 +1,738 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Multi-threaded tests of ConditionVariable class.
|
||||
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "base/condition_variable.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/platform_thread.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/spin_wait.h"
|
||||
#include "base/thread_collision_warner.h"
|
||||
#include "base/time.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/platform_test.h"
|
||||
|
||||
using base::TimeDelta;
|
||||
using base::TimeTicks;
|
||||
|
||||
namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// Define our test class, with several common variables.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ConditionVariableTest : public PlatformTest {
|
||||
public:
|
||||
const TimeDelta kZeroMs;
|
||||
const TimeDelta kTenMs;
|
||||
const TimeDelta kThirtyMs;
|
||||
const TimeDelta kFortyFiveMs;
|
||||
const TimeDelta kSixtyMs;
|
||||
const TimeDelta kOneHundredMs;
|
||||
|
||||
explicit ConditionVariableTest()
|
||||
: kZeroMs(TimeDelta::FromMilliseconds(0)),
|
||||
kTenMs(TimeDelta::FromMilliseconds(10)),
|
||||
kThirtyMs(TimeDelta::FromMilliseconds(30)),
|
||||
kFortyFiveMs(TimeDelta::FromMilliseconds(45)),
|
||||
kSixtyMs(TimeDelta::FromMilliseconds(60)),
|
||||
kOneHundredMs(TimeDelta::FromMilliseconds(100)) {
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Define a class that will control activities an several multi-threaded tests.
|
||||
// The general structure of multi-threaded tests is that a test case will
|
||||
// construct an instance of a WorkQueue. The WorkQueue will spin up some
|
||||
// threads and control them throughout their lifetime, as well as maintaining
|
||||
// a central repository of the work thread's activity. Finally, the WorkQueue
|
||||
// will command the the worker threads to terminate. At that point, the test
|
||||
// cases will validate that the WorkQueue has records showing that the desired
|
||||
// activities were performed.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Callers are responsible for synchronizing access to the following class.
|
||||
// The WorkQueue::lock_, as accessed via WorkQueue::lock(), should be used for
|
||||
// all synchronized access.
|
||||
class WorkQueue : public PlatformThread::Delegate {
|
||||
public:
|
||||
explicit WorkQueue(int thread_count);
|
||||
~WorkQueue();
|
||||
|
||||
// PlatformThread::Delegate interface.
|
||||
void ThreadMain();
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Worker threads only call the following methods.
|
||||
// They should use the lock to get exclusive access.
|
||||
int GetThreadId(); // Get an ID assigned to a thread..
|
||||
bool EveryIdWasAllocated() const; // Indicates that all IDs were handed out.
|
||||
TimeDelta GetAnAssignment(int thread_id); // Get a work task duration.
|
||||
void WorkIsCompleted(int thread_id);
|
||||
|
||||
int task_count() const;
|
||||
bool allow_help_requests() const; // Workers can signal more workers.
|
||||
bool shutdown() const; // Check if shutdown has been requested.
|
||||
|
||||
void thread_shutting_down();
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Worker threads can call them but not needed to acquire a lock.
|
||||
Lock* lock();
|
||||
|
||||
ConditionVariable* work_is_available();
|
||||
ConditionVariable* all_threads_have_ids();
|
||||
ConditionVariable* no_more_tasks();
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// The rest of the methods are for use by the controlling master thread (the
|
||||
// test case code).
|
||||
void ResetHistory();
|
||||
int GetMinCompletionsByWorkerThread() const;
|
||||
int GetMaxCompletionsByWorkerThread() const;
|
||||
int GetNumThreadsTakingAssignments() const;
|
||||
int GetNumThreadsCompletingTasks() const;
|
||||
int GetNumberOfCompletedTasks() const;
|
||||
TimeDelta GetWorkTime() const;
|
||||
|
||||
void SetWorkTime(TimeDelta delay);
|
||||
void SetTaskCount(int count);
|
||||
void SetAllowHelp(bool allow);
|
||||
|
||||
// Caller must acquire lock before calling.
|
||||
void SetShutdown();
|
||||
|
||||
// Compares the |shutdown_task_count_| to the |thread_count| and returns true
|
||||
// if they are equal. This check will acquire the |lock_| so the caller
|
||||
// should not hold the lock when calling this method.
|
||||
bool ThreadSafeCheckShutdown(int thread_count);
|
||||
|
||||
private:
|
||||
// Both worker threads and controller use the following to synchronize.
|
||||
Lock lock_;
|
||||
ConditionVariable work_is_available_; // To tell threads there is work.
|
||||
|
||||
// Conditions to notify the controlling process (if it is interested).
|
||||
ConditionVariable all_threads_have_ids_; // All threads are running.
|
||||
ConditionVariable no_more_tasks_; // Task count is zero.
|
||||
|
||||
const int thread_count_;
|
||||
scoped_array<PlatformThreadHandle> thread_handles_;
|
||||
std::vector<int> assignment_history_; // Number of assignment per worker.
|
||||
std::vector<int> completion_history_; // Number of completions per worker.
|
||||
int thread_started_counter_; // Used to issue unique id to workers.
|
||||
int shutdown_task_count_; // Number of tasks told to shutdown
|
||||
int task_count_; // Number of assignment tasks waiting to be processed.
|
||||
TimeDelta worker_delay_; // Time each task takes to complete.
|
||||
bool allow_help_requests_; // Workers can signal more workers.
|
||||
bool shutdown_; // Set when threads need to terminate.
|
||||
|
||||
DFAKE_MUTEX(locked_methods_);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// The next section contains the actual tests.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TEST_F(ConditionVariableTest, StartupShutdownTest) {
|
||||
Lock lock;
|
||||
|
||||
// First try trivial startup/shutdown.
|
||||
{
|
||||
ConditionVariable cv1(&lock);
|
||||
} // Call for cv1 destruction.
|
||||
|
||||
// Exercise with at least a few waits.
|
||||
ConditionVariable cv(&lock);
|
||||
|
||||
lock.Acquire();
|
||||
cv.TimedWait(kTenMs); // Wait for 10 ms.
|
||||
cv.TimedWait(kTenMs); // Wait for 10 ms.
|
||||
lock.Release();
|
||||
|
||||
lock.Acquire();
|
||||
cv.TimedWait(kTenMs); // Wait for 10 ms.
|
||||
cv.TimedWait(kTenMs); // Wait for 10 ms.
|
||||
cv.TimedWait(kTenMs); // Wait for 10 ms.
|
||||
lock.Release();
|
||||
} // Call for cv destruction.
|
||||
|
||||
TEST_F(ConditionVariableTest, TimeoutTest) {
|
||||
Lock lock;
|
||||
ConditionVariable cv(&lock);
|
||||
lock.Acquire();
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
const TimeDelta WAIT_TIME = TimeDelta::FromMilliseconds(300);
|
||||
// Allow for clocking rate granularity.
|
||||
const TimeDelta FUDGE_TIME = TimeDelta::FromMilliseconds(50);
|
||||
|
||||
cv.TimedWait(WAIT_TIME + FUDGE_TIME);
|
||||
TimeDelta duration = TimeTicks::Now() - start;
|
||||
// We can't use EXPECT_GE here as the TimeDelta class does not support the
|
||||
// required stream conversion.
|
||||
EXPECT_TRUE(duration >= WAIT_TIME);
|
||||
|
||||
lock.Release();
|
||||
}
|
||||
|
||||
// Test serial task servicing, as well as two parallel task servicing methods.
|
||||
// TODO(maruel): http://crbug.com/10607
|
||||
TEST_F(ConditionVariableTest, DISABLED_MultiThreadConsumerTest) {
|
||||
const int kThreadCount = 10;
|
||||
WorkQueue queue(kThreadCount); // Start the threads.
|
||||
|
||||
const int kTaskCount = 10; // Number of tasks in each mini-test here.
|
||||
|
||||
base::Time start_time; // Used to time task processing.
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while (!queue.EveryIdWasAllocated())
|
||||
queue.all_threads_have_ids()->Wait();
|
||||
}
|
||||
|
||||
// Wait a bit more to allow threads to reach their wait state.
|
||||
// If threads aren't in a wait state, they may start to gobble up tasks in
|
||||
// parallel, short-circuiting (breaking) this test.
|
||||
PlatformThread::Sleep(100);
|
||||
|
||||
{
|
||||
// Since we have no tasks yet, all threads should be waiting by now.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make one worker do 30ms tasks sequentially.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(kTaskCount);
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(false);
|
||||
|
||||
start_time = base::Time::Now();
|
||||
}
|
||||
|
||||
queue.work_is_available()->Signal(); // Start up one thread.
|
||||
|
||||
|
||||
{
|
||||
// Wait until all 10 work tasks have at least been assigned.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while(queue.task_count())
|
||||
queue.no_more_tasks()->Wait();
|
||||
// The last of the tasks *might* still be running, but... all but one should
|
||||
// be done by now, since tasks are being done serially.
|
||||
EXPECT_LE(queue.GetWorkTime().InMilliseconds() * (kTaskCount - 1),
|
||||
(base::Time::Now() - start_time).InMilliseconds());
|
||||
|
||||
EXPECT_EQ(1, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(1, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_LE(kTaskCount - 1, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_LE(kTaskCount - 1, queue.GetNumberOfCompletedTasks());
|
||||
}
|
||||
|
||||
// Wait to be sure all tasks are done.
|
||||
while (1) {
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
if (kTaskCount == queue.GetNumberOfCompletedTasks())
|
||||
break;
|
||||
}
|
||||
PlatformThread::Sleep(30); // Wait a little.
|
||||
}
|
||||
|
||||
{
|
||||
// Check that all work was done by one thread id.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(1, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(1, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(kTaskCount, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(kTaskCount, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make each task include getting help from another worker, so
|
||||
// so that the work gets done in paralell.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(kTaskCount);
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(true);
|
||||
|
||||
start_time = base::Time::Now();
|
||||
}
|
||||
|
||||
queue.work_is_available()->Signal(); // But each worker can signal another.
|
||||
// Wait to allow the all workers to get done.
|
||||
while (1) {
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
if (kTaskCount == queue.GetNumberOfCompletedTasks())
|
||||
break;
|
||||
}
|
||||
PlatformThread::Sleep(30); // Wait a little.
|
||||
}
|
||||
|
||||
{
|
||||
// Wait until all work tasks have at least been assigned.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while(queue.task_count())
|
||||
queue.no_more_tasks()->Wait();
|
||||
// Since they can all run almost in parallel, there is no guarantee that all
|
||||
// tasks are finished, but we should have gotten here faster than it would
|
||||
// take to run all tasks serially.
|
||||
EXPECT_GT(queue.GetWorkTime().InMilliseconds() * (kTaskCount - 1),
|
||||
(base::Time::Now() - start_time).InMilliseconds());
|
||||
|
||||
// To avoid racy assumptions, we'll just assert that at least 2 threads
|
||||
// did work.
|
||||
EXPECT_LE(2, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(kTaskCount, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Try to ask all workers to help, and only a few will do the work.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(3);
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(false);
|
||||
}
|
||||
queue.work_is_available()->Broadcast(); // Make them all try.
|
||||
// Wait to allow the 3 workers to get done.
|
||||
PlatformThread::Sleep(45);
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(3, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make each task get help from another worker.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(3);
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(true); // Allow (unnecessary) help requests.
|
||||
}
|
||||
queue.work_is_available()->Broadcast(); // We already signal all threads.
|
||||
// Wait to allow the 3 workers to get done.
|
||||
PlatformThread::Sleep(100);
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(3, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(3, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(1, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(3, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make each task get help from another worker.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(20);
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(true);
|
||||
}
|
||||
queue.work_is_available()->Signal(); // But each worker can signal another.
|
||||
// Wait to allow the 10 workers to get done.
|
||||
PlatformThread::Sleep(100); // Should take about 60 ms.
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(2, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(2, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(20, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Same as last test, but with Broadcast().
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(20); // 2 tasks per process.
|
||||
queue.SetWorkTime(kThirtyMs);
|
||||
queue.SetAllowHelp(true);
|
||||
}
|
||||
queue.work_is_available()->Broadcast();
|
||||
// Wait to allow the 10 workers to get done.
|
||||
PlatformThread::Sleep(100); // Should take about 60 ms.
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(10, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(10, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(2, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(2, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(20, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
queue.SetShutdown();
|
||||
}
|
||||
queue.work_is_available()->Broadcast(); // Force check for shutdown.
|
||||
|
||||
SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1),
|
||||
queue.ThreadSafeCheckShutdown(kThreadCount));
|
||||
PlatformThread::Sleep(10); // Be sure they're all shutdown.
|
||||
}
|
||||
|
||||
TEST_F(ConditionVariableTest, LargeFastTaskTest) {
|
||||
const int kThreadCount = 200;
|
||||
WorkQueue queue(kThreadCount); // Start the threads.
|
||||
|
||||
Lock private_lock; // Used locally for master to wait.
|
||||
AutoLock private_held_lock(private_lock);
|
||||
ConditionVariable private_cv(&private_lock);
|
||||
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while (!queue.EveryIdWasAllocated())
|
||||
queue.all_threads_have_ids()->Wait();
|
||||
}
|
||||
|
||||
// Wait a bit more to allow threads to reach their wait state.
|
||||
private_cv.TimedWait(kThirtyMs);
|
||||
|
||||
{
|
||||
// Since we have no tasks, all threads should be waiting by now.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(0, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(0, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_EQ(0, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetMinCompletionsByWorkerThread());
|
||||
EXPECT_EQ(0, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make all workers do (an average of) 20 tasks.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(20 * kThreadCount);
|
||||
queue.SetWorkTime(kFortyFiveMs);
|
||||
queue.SetAllowHelp(false);
|
||||
}
|
||||
queue.work_is_available()->Broadcast(); // Start up all threads.
|
||||
// Wait until we've handed out all tasks.
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while (queue.task_count() != 0)
|
||||
queue.no_more_tasks()->Wait();
|
||||
}
|
||||
|
||||
// Wait till the last of the tasks complete.
|
||||
// Don't bother to use locks: We may not get info in time... but we'll see it
|
||||
// eventually.
|
||||
SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1),
|
||||
20 * kThreadCount ==
|
||||
queue.GetNumberOfCompletedTasks());
|
||||
|
||||
{
|
||||
// With Broadcast(), every thread should have participated.
|
||||
// but with racing.. they may not all have done equal numbers of tasks.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_LE(20, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(20 * kThreadCount, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
// Set up to make all workers do (an average of) 4 tasks.
|
||||
queue.ResetHistory();
|
||||
queue.SetTaskCount(kThreadCount * 4);
|
||||
queue.SetWorkTime(kFortyFiveMs);
|
||||
queue.SetAllowHelp(true); // Might outperform Broadcast().
|
||||
}
|
||||
queue.work_is_available()->Signal(); // Start up one thread.
|
||||
|
||||
// Wait until we've handed out all tasks
|
||||
{
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
while (queue.task_count() != 0)
|
||||
queue.no_more_tasks()->Wait();
|
||||
}
|
||||
|
||||
// Wait till the last of the tasks complete.
|
||||
// Don't bother to use locks: We may not get info in time... but we'll see it
|
||||
// eventually.
|
||||
SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1),
|
||||
4 * kThreadCount ==
|
||||
queue.GetNumberOfCompletedTasks());
|
||||
|
||||
{
|
||||
// With Signal(), every thread should have participated.
|
||||
// but with racing.. they may not all have done four tasks.
|
||||
AutoLock auto_lock(*queue.lock());
|
||||
EXPECT_EQ(kThreadCount, queue.GetNumThreadsTakingAssignments());
|
||||
EXPECT_EQ(kThreadCount, queue.GetNumThreadsCompletingTasks());
|
||||
EXPECT_EQ(0, queue.task_count());
|
||||
EXPECT_LE(4, queue.GetMaxCompletionsByWorkerThread());
|
||||
EXPECT_EQ(4 * kThreadCount, queue.GetNumberOfCompletedTasks());
|
||||
|
||||
queue.SetShutdown();
|
||||
}
|
||||
queue.work_is_available()->Broadcast(); // Force check for shutdown.
|
||||
|
||||
// Wait for shutdowns to complete.
|
||||
SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(1),
|
||||
queue.ThreadSafeCheckShutdown(kThreadCount));
|
||||
PlatformThread::Sleep(10); // Be sure they're all shutdown.
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Finally we provide the implementation for the methods in the WorkQueue class.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
WorkQueue::WorkQueue(int thread_count)
|
||||
: lock_(),
|
||||
work_is_available_(&lock_),
|
||||
all_threads_have_ids_(&lock_),
|
||||
no_more_tasks_(&lock_),
|
||||
thread_count_(thread_count),
|
||||
thread_handles_(new PlatformThreadHandle[thread_count]),
|
||||
assignment_history_(thread_count),
|
||||
completion_history_(thread_count),
|
||||
thread_started_counter_(0),
|
||||
shutdown_task_count_(0),
|
||||
task_count_(0),
|
||||
allow_help_requests_(false),
|
||||
shutdown_(false) {
|
||||
EXPECT_GE(thread_count_, 1);
|
||||
ResetHistory();
|
||||
SetTaskCount(0);
|
||||
SetWorkTime(TimeDelta::FromMilliseconds(30));
|
||||
|
||||
for (int i = 0; i < thread_count_; ++i) {
|
||||
PlatformThreadHandle pth;
|
||||
EXPECT_TRUE(PlatformThread::Create(0, this, &pth));
|
||||
thread_handles_[i] = pth;
|
||||
}
|
||||
}
|
||||
|
||||
WorkQueue::~WorkQueue() {
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
SetShutdown();
|
||||
}
|
||||
work_is_available_.Broadcast(); // Tell them all to terminate.
|
||||
|
||||
for (int i = 0; i < thread_count_; ++i) {
|
||||
PlatformThread::Join(thread_handles_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int WorkQueue::GetThreadId() {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
DCHECK(!EveryIdWasAllocated());
|
||||
return thread_started_counter_++; // Give out Unique IDs.
|
||||
}
|
||||
|
||||
bool WorkQueue::EveryIdWasAllocated() const {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
return thread_count_ == thread_started_counter_;
|
||||
}
|
||||
|
||||
TimeDelta WorkQueue::GetAnAssignment(int thread_id) {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
DCHECK_LT(0, task_count_);
|
||||
assignment_history_[thread_id]++;
|
||||
if (0 == --task_count_) {
|
||||
no_more_tasks_.Signal();
|
||||
}
|
||||
return worker_delay_;
|
||||
}
|
||||
|
||||
void WorkQueue::WorkIsCompleted(int thread_id) {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
completion_history_[thread_id]++;
|
||||
}
|
||||
|
||||
int WorkQueue::task_count() const {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
return task_count_;
|
||||
}
|
||||
|
||||
bool WorkQueue::allow_help_requests() const {
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
return allow_help_requests_;
|
||||
}
|
||||
|
||||
bool WorkQueue::shutdown() const {
|
||||
lock_.AssertAcquired();
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
return shutdown_;
|
||||
}
|
||||
|
||||
// Because this method is called from the test's main thread we need to actually
|
||||
// take the lock. Threads will call the thread_shutting_down() method with the
|
||||
// lock already acquired.
|
||||
bool WorkQueue::ThreadSafeCheckShutdown(int thread_count) {
|
||||
bool all_shutdown;
|
||||
AutoLock auto_lock(lock_);
|
||||
{
|
||||
// Declare in scope so DFAKE is guranteed to be destroyed before AutoLock.
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
all_shutdown = (shutdown_task_count_ == thread_count);
|
||||
}
|
||||
return all_shutdown;
|
||||
}
|
||||
|
||||
void WorkQueue::thread_shutting_down() {
|
||||
lock_.AssertAcquired();
|
||||
DFAKE_SCOPED_RECURSIVE_LOCK(locked_methods_);
|
||||
shutdown_task_count_++;
|
||||
}
|
||||
|
||||
Lock* WorkQueue::lock() {
|
||||
return &lock_;
|
||||
}
|
||||
|
||||
ConditionVariable* WorkQueue::work_is_available() {
|
||||
return &work_is_available_;
|
||||
}
|
||||
|
||||
ConditionVariable* WorkQueue::all_threads_have_ids() {
|
||||
return &all_threads_have_ids_;
|
||||
}
|
||||
|
||||
ConditionVariable* WorkQueue::no_more_tasks() {
|
||||
return &no_more_tasks_;
|
||||
}
|
||||
|
||||
void WorkQueue::ResetHistory() {
|
||||
for (int i = 0; i < thread_count_; ++i) {
|
||||
assignment_history_[i] = 0;
|
||||
completion_history_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int WorkQueue::GetMinCompletionsByWorkerThread() const {
|
||||
int minumum = completion_history_[0];
|
||||
for (int i = 0; i < thread_count_; ++i)
|
||||
minumum = std::min(minumum, completion_history_[i]);
|
||||
return minumum;
|
||||
}
|
||||
|
||||
int WorkQueue::GetMaxCompletionsByWorkerThread() const {
|
||||
int maximum = completion_history_[0];
|
||||
for (int i = 0; i < thread_count_; ++i)
|
||||
maximum = std::max(maximum, completion_history_[i]);
|
||||
return maximum;
|
||||
}
|
||||
|
||||
int WorkQueue::GetNumThreadsTakingAssignments() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < thread_count_; ++i)
|
||||
if (assignment_history_[i])
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
int WorkQueue::GetNumThreadsCompletingTasks() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < thread_count_; ++i)
|
||||
if (completion_history_[i])
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
int WorkQueue::GetNumberOfCompletedTasks() const {
|
||||
int total = 0;
|
||||
for (int i = 0; i < thread_count_; ++i)
|
||||
total += completion_history_[i];
|
||||
return total;
|
||||
}
|
||||
|
||||
TimeDelta WorkQueue::GetWorkTime() const {
|
||||
return worker_delay_;
|
||||
}
|
||||
|
||||
void WorkQueue::SetWorkTime(TimeDelta delay) {
|
||||
worker_delay_ = delay;
|
||||
}
|
||||
|
||||
void WorkQueue::SetTaskCount(int count) {
|
||||
task_count_ = count;
|
||||
}
|
||||
|
||||
void WorkQueue::SetAllowHelp(bool allow) {
|
||||
allow_help_requests_ = allow;
|
||||
}
|
||||
|
||||
void WorkQueue::SetShutdown() {
|
||||
lock_.AssertAcquired();
|
||||
shutdown_ = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Define the standard worker task. Several tests will spin out many of these
|
||||
// threads.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// The multithread tests involve several threads with a task to perform as
|
||||
// directed by an instance of the class WorkQueue.
|
||||
// The task is to:
|
||||
// a) Check to see if there are more tasks (there is a task counter).
|
||||
// a1) Wait on condition variable if there are no tasks currently.
|
||||
// b) Call a function to see what should be done.
|
||||
// c) Do some computation based on the number of milliseconds returned in (b).
|
||||
// d) go back to (a).
|
||||
|
||||
// WorkQueue::ThreadMain() implements the above task for all threads.
|
||||
// It calls the controlling object to tell the creator about progress, and to
|
||||
// ask about tasks.
|
||||
|
||||
void WorkQueue::ThreadMain() {
|
||||
int thread_id;
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
thread_id = GetThreadId();
|
||||
if (EveryIdWasAllocated())
|
||||
all_threads_have_ids()->Signal(); // Tell creator we're ready.
|
||||
}
|
||||
|
||||
Lock private_lock; // Used to waste time on "our work".
|
||||
while (1) { // This is the main consumer loop.
|
||||
TimeDelta work_time;
|
||||
bool could_use_help;
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
while (0 == task_count() && !shutdown()) {
|
||||
work_is_available()->Wait();
|
||||
}
|
||||
if (shutdown()) {
|
||||
// Ack the notification of a shutdown message back to the controller.
|
||||
thread_shutting_down();
|
||||
return; // Terminate.
|
||||
}
|
||||
// Get our task duration from the queue.
|
||||
work_time = GetAnAssignment(thread_id);
|
||||
could_use_help = (task_count() > 0) && allow_help_requests();
|
||||
} // Release lock
|
||||
|
||||
// Do work (outside of locked region.
|
||||
if (could_use_help)
|
||||
work_is_available()->Signal(); // Get help from other threads.
|
||||
|
||||
if (work_time > TimeDelta::FromMilliseconds(0)) {
|
||||
// We could just sleep(), but we'll instead further exercise the
|
||||
// condition variable class, and do a timed wait.
|
||||
AutoLock auto_lock(private_lock);
|
||||
ConditionVariable private_cv(&private_lock);
|
||||
private_cv.TimedWait(work_time); // Unsynchronized waiting.
|
||||
}
|
||||
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
// Send notification that we completed our "work."
|
||||
WorkIsCompleted(thread_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,446 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/condition_variable.h"
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "base/lock.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/time.h"
|
||||
|
||||
using base::TimeDelta;
|
||||
|
||||
ConditionVariable::ConditionVariable(Lock* user_lock)
|
||||
: user_lock_(*user_lock),
|
||||
run_state_(RUNNING),
|
||||
allocation_counter_(0),
|
||||
recycling_list_size_(0) {
|
||||
DCHECK(user_lock);
|
||||
}
|
||||
|
||||
ConditionVariable::~ConditionVariable() {
|
||||
AutoLock auto_lock(internal_lock_);
|
||||
run_state_ = SHUTDOWN; // Prevent any more waiting.
|
||||
|
||||
DCHECK_EQ(recycling_list_size_, allocation_counter_);
|
||||
if (recycling_list_size_ != allocation_counter_) { // Rare shutdown problem.
|
||||
// There are threads of execution still in this->TimedWait() and yet the
|
||||
// caller has instigated the destruction of this instance :-/.
|
||||
// A common reason for such "overly hasty" destruction is that the caller
|
||||
// was not willing to wait for all the threads to terminate. Such hasty
|
||||
// actions are a violation of our usage contract, but we'll give the
|
||||
// waiting thread(s) one last chance to exit gracefully (prior to our
|
||||
// destruction).
|
||||
// Note: waiting_list_ *might* be empty, but recycling is still pending.
|
||||
AutoUnlock auto_unlock(internal_lock_);
|
||||
Broadcast(); // Make sure all waiting threads have been signaled.
|
||||
Sleep(10); // Give threads a chance to grab internal_lock_.
|
||||
// All contained threads should be blocked on user_lock_ by now :-).
|
||||
} // Reacquire internal_lock_.
|
||||
|
||||
DCHECK_EQ(recycling_list_size_, allocation_counter_);
|
||||
}
|
||||
|
||||
void ConditionVariable::Wait() {
|
||||
// Default to "wait forever" timing, which means have to get a Signal()
|
||||
// or Broadcast() to come out of this wait state.
|
||||
TimedWait(TimeDelta::FromMilliseconds(INFINITE));
|
||||
}
|
||||
|
||||
void ConditionVariable::TimedWait(const TimeDelta& max_time) {
|
||||
Event* waiting_event;
|
||||
HANDLE handle;
|
||||
{
|
||||
AutoLock auto_lock(internal_lock_);
|
||||
if (RUNNING != run_state_) return; // Destruction in progress.
|
||||
waiting_event = GetEventForWaiting();
|
||||
handle = waiting_event->handle();
|
||||
DCHECK(handle);
|
||||
} // Release internal_lock.
|
||||
|
||||
{
|
||||
AutoUnlock unlock(user_lock_); // Release caller's lock
|
||||
WaitForSingleObject(handle, static_cast<DWORD>(max_time.InMilliseconds()));
|
||||
// Minimize spurious signal creation window by recycling asap.
|
||||
AutoLock auto_lock(internal_lock_);
|
||||
RecycleEvent(waiting_event);
|
||||
// Release internal_lock_
|
||||
} // Reacquire callers lock to depth at entry.
|
||||
}
|
||||
|
||||
// Broadcast() is guaranteed to signal all threads that were waiting (i.e., had
|
||||
// a cv_event internally allocated for them) before Broadcast() was called.
|
||||
void ConditionVariable::Broadcast() {
|
||||
std::stack<HANDLE> handles; // See FAQ-question-10.
|
||||
{
|
||||
AutoLock auto_lock(internal_lock_);
|
||||
if (waiting_list_.IsEmpty())
|
||||
return;
|
||||
while (!waiting_list_.IsEmpty())
|
||||
// This is not a leak from waiting_list_. See FAQ-question 12.
|
||||
handles.push(waiting_list_.PopBack()->handle());
|
||||
} // Release internal_lock_.
|
||||
while (!handles.empty()) {
|
||||
SetEvent(handles.top());
|
||||
handles.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Signal() will select one of the waiting threads, and signal it (signal its
|
||||
// cv_event). For better performance we signal the thread that went to sleep
|
||||
// most recently (LIFO). If we want fairness, then we wake the thread that has
|
||||
// been sleeping the longest (FIFO).
|
||||
void ConditionVariable::Signal() {
|
||||
HANDLE handle;
|
||||
{
|
||||
AutoLock auto_lock(internal_lock_);
|
||||
if (waiting_list_.IsEmpty())
|
||||
return; // No one to signal.
|
||||
// Only performance option should be used.
|
||||
// This is not a leak from waiting_list. See FAQ-question 12.
|
||||
handle = waiting_list_.PopBack()->handle(); // LIFO.
|
||||
} // Release internal_lock_.
|
||||
SetEvent(handle);
|
||||
}
|
||||
|
||||
// GetEventForWaiting() provides a unique cv_event for any caller that needs to
|
||||
// wait. This means that (worst case) we may over time create as many cv_event
|
||||
// objects as there are threads simultaneously using this instance's Wait()
|
||||
// functionality.
|
||||
ConditionVariable::Event* ConditionVariable::GetEventForWaiting() {
|
||||
// We hold internal_lock, courtesy of Wait().
|
||||
Event* cv_event;
|
||||
if (0 == recycling_list_size_) {
|
||||
DCHECK(recycling_list_.IsEmpty());
|
||||
cv_event = new Event();
|
||||
cv_event->InitListElement();
|
||||
allocation_counter_++;
|
||||
// CHECK_NE is not defined in our codebase, so we have to use CHECK
|
||||
CHECK(cv_event->handle());
|
||||
} else {
|
||||
cv_event = recycling_list_.PopFront();
|
||||
recycling_list_size_--;
|
||||
}
|
||||
waiting_list_.PushBack(cv_event);
|
||||
return cv_event;
|
||||
}
|
||||
|
||||
// RecycleEvent() takes a cv_event that was previously used for Wait()ing, and
|
||||
// recycles it for use in future Wait() calls for this or other threads.
|
||||
// Note that there is a tiny chance that the cv_event is still signaled when we
|
||||
// obtain it, and that can cause spurious signals (if/when we re-use the
|
||||
// cv_event), but such is quite rare (see FAQ-question-5).
|
||||
void ConditionVariable::RecycleEvent(Event* used_event) {
|
||||
// We hold internal_lock, courtesy of Wait().
|
||||
// If the cv_event timed out, then it is necessary to remove it from
|
||||
// waiting_list_. If it was selected by Broadcast() or Signal(), then it is
|
||||
// already gone.
|
||||
used_event->Extract(); // Possibly redundant
|
||||
recycling_list_.PushBack(used_event);
|
||||
recycling_list_size_++;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// The next section provides the implementation for the private Event class.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Event provides a doubly-linked-list of events for use exclusively by the
|
||||
// ConditionVariable class.
|
||||
|
||||
// This custom container was crafted because no simple combination of STL
|
||||
// classes appeared to support the functionality required. The specific
|
||||
// unusual requirement for a linked-list-class is support for the Extract()
|
||||
// method, which can remove an element from a list, potentially for insertion
|
||||
// into a second list. Most critically, the Extract() method is idempotent,
|
||||
// turning the indicated element into an extracted singleton whether it was
|
||||
// contained in a list or not. This functionality allows one (or more) of
|
||||
// threads to do the extraction. The iterator that identifies this extractable
|
||||
// element (in this case, a pointer to the list element) can be used after
|
||||
// arbitrary manipulation of the (possibly) enclosing list container. In
|
||||
// general, STL containers do not provide iterators that can be used across
|
||||
// modifications (insertions/extractions) of the enclosing containers, and
|
||||
// certainly don't provide iterators that can be used if the identified
|
||||
// element is *deleted* (removed) from the container.
|
||||
|
||||
// It is possible to use multiple redundant containers, such as an STL list,
|
||||
// and an STL map, to achieve similar container semantics. This container has
|
||||
// only O(1) methods, while the corresponding (multiple) STL container approach
|
||||
// would have more complex O(log(N)) methods (yeah... N isn't that large).
|
||||
// Multiple containers also makes correctness more difficult to assert, as
|
||||
// data is redundantly stored and maintained, which is generally evil.
|
||||
|
||||
ConditionVariable::Event::Event() : handle_(0) {
|
||||
next_ = prev_ = this; // Self referencing circular.
|
||||
}
|
||||
|
||||
ConditionVariable::Event::~Event() {
|
||||
if (0 == handle_) {
|
||||
// This is the list holder
|
||||
while (!IsEmpty()) {
|
||||
Event* cv_event = PopFront();
|
||||
DCHECK(cv_event->ValidateAsItem());
|
||||
delete cv_event;
|
||||
}
|
||||
}
|
||||
DCHECK(IsSingleton());
|
||||
if (0 != handle_) {
|
||||
int ret_val = CloseHandle(handle_);
|
||||
DCHECK(ret_val);
|
||||
}
|
||||
}
|
||||
|
||||
// Change a container instance permanently into an element of a list.
|
||||
void ConditionVariable::Event::InitListElement() {
|
||||
DCHECK(!handle_);
|
||||
handle_ = CreateEvent(NULL, false, false, NULL);
|
||||
CHECK(handle_);
|
||||
}
|
||||
|
||||
// Methods for use on lists.
|
||||
bool ConditionVariable::Event::IsEmpty() const {
|
||||
DCHECK(ValidateAsList());
|
||||
return IsSingleton();
|
||||
}
|
||||
|
||||
void ConditionVariable::Event::PushBack(Event* other) {
|
||||
DCHECK(ValidateAsList());
|
||||
DCHECK(other->ValidateAsItem());
|
||||
DCHECK(other->IsSingleton());
|
||||
// Prepare other for insertion.
|
||||
other->prev_ = prev_;
|
||||
other->next_ = this;
|
||||
// Cut into list.
|
||||
prev_->next_ = other;
|
||||
prev_ = other;
|
||||
DCHECK(ValidateAsDistinct(other));
|
||||
}
|
||||
|
||||
ConditionVariable::Event* ConditionVariable::Event::PopFront() {
|
||||
DCHECK(ValidateAsList());
|
||||
DCHECK(!IsSingleton());
|
||||
return next_->Extract();
|
||||
}
|
||||
|
||||
ConditionVariable::Event* ConditionVariable::Event::PopBack() {
|
||||
DCHECK(ValidateAsList());
|
||||
DCHECK(!IsSingleton());
|
||||
return prev_->Extract();
|
||||
}
|
||||
|
||||
// Methods for use on list elements.
|
||||
// Accessor method.
|
||||
HANDLE ConditionVariable::Event::handle() const {
|
||||
DCHECK(ValidateAsItem());
|
||||
return handle_;
|
||||
}
|
||||
|
||||
// Pull an element from a list (if it's in one).
|
||||
ConditionVariable::Event* ConditionVariable::Event::Extract() {
|
||||
DCHECK(ValidateAsItem());
|
||||
if (!IsSingleton()) {
|
||||
// Stitch neighbors together.
|
||||
next_->prev_ = prev_;
|
||||
prev_->next_ = next_;
|
||||
// Make extractee into a singleton.
|
||||
prev_ = next_ = this;
|
||||
}
|
||||
DCHECK(IsSingleton());
|
||||
return this;
|
||||
}
|
||||
|
||||
// Method for use on a list element or on a list.
|
||||
bool ConditionVariable::Event::IsSingleton() const {
|
||||
DCHECK(ValidateLinks());
|
||||
return next_ == this;
|
||||
}
|
||||
|
||||
// Provide pre/post conditions to validate correct manipulations.
|
||||
bool ConditionVariable::Event::ValidateAsDistinct(Event* other) const {
|
||||
return ValidateLinks() && other->ValidateLinks() && (this != other);
|
||||
}
|
||||
|
||||
bool ConditionVariable::Event::ValidateAsItem() const {
|
||||
return (0 != handle_) && ValidateLinks();
|
||||
}
|
||||
|
||||
bool ConditionVariable::Event::ValidateAsList() const {
|
||||
return (0 == handle_) && ValidateLinks();
|
||||
}
|
||||
|
||||
bool ConditionVariable::Event::ValidateLinks() const {
|
||||
// Make sure both of our neighbors have links that point back to us.
|
||||
// We don't do the O(n) check and traverse the whole loop, and instead only
|
||||
// do a local check to (and returning from) our immediate neighbors.
|
||||
return (next_->prev_ == this) && (prev_->next_ == this);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
FAQ On subtle implementation details:
|
||||
|
||||
1) What makes this problem subtle? Please take a look at "Strategies
|
||||
for Implementing POSIX Condition Variables on Win32" by Douglas
|
||||
C. Schmidt and Irfan Pyarali.
|
||||
http://www.cs.wustl.edu/~schmidt/win32-cv-1.html It includes
|
||||
discussions of numerous flawed strategies for implementing this
|
||||
functionality. I'm not convinced that even the final proposed
|
||||
implementation has semantics that are as nice as this implementation
|
||||
(especially with regard to Broadcast() and the impact on threads that
|
||||
try to Wait() after a Broadcast() has been called, but before all the
|
||||
original waiting threads have been signaled).
|
||||
|
||||
2) Why can't you use a single wait_event for all threads that call
|
||||
Wait()? See FAQ-question-1, or consider the following: If a single
|
||||
event were used, then numerous threads calling Wait() could release
|
||||
their cs locks, and be preempted just before calling
|
||||
WaitForSingleObject(). If a call to Broadcast() was then presented on
|
||||
a second thread, it would be impossible to actually signal all
|
||||
waiting(?) threads. Some number of SetEvent() calls *could* be made,
|
||||
but there could be no guarantee that those led to to more than one
|
||||
signaled thread (SetEvent()'s may be discarded after the first!), and
|
||||
there could be no guarantee that the SetEvent() calls didn't just
|
||||
awaken "other" threads that hadn't even started waiting yet (oops).
|
||||
Without any limit on the number of requisite SetEvent() calls, the
|
||||
system would be forced to do many such calls, allowing many new waits
|
||||
to receive spurious signals.
|
||||
|
||||
3) How does this implementation cause spurious signal events? The
|
||||
cause in this implementation involves a race between a signal via
|
||||
time-out and a signal via Signal() or Broadcast(). The series of
|
||||
actions leading to this are:
|
||||
|
||||
a) Timer fires, and a waiting thread exits the line of code:
|
||||
|
||||
WaitForSingleObject(waiting_event, max_time.InMilliseconds());
|
||||
|
||||
b) That thread (in (a)) is randomly pre-empted after the above line,
|
||||
leaving the waiting_event reset (unsignaled) and still in the
|
||||
waiting_list_.
|
||||
|
||||
c) A call to Signal() (or Broadcast()) on a second thread proceeds, and
|
||||
selects the waiting cv_event (identified in step (b)) as the event to revive
|
||||
via a call to SetEvent().
|
||||
|
||||
d) The Signal() method (step c) calls SetEvent() on waiting_event (step b).
|
||||
|
||||
e) The waiting cv_event (step b) is now signaled, but no thread is
|
||||
waiting on it.
|
||||
|
||||
f) When that waiting_event (step b) is reused, it will immediately
|
||||
be signaled (spuriously).
|
||||
|
||||
|
||||
4) Why do you recycle events, and cause spurious signals? First off,
|
||||
the spurious events are very rare. They can only (I think) appear
|
||||
when the race described in FAQ-question-3 takes place. This should be
|
||||
very rare. Most(?) uses will involve only timer expiration, or only
|
||||
Signal/Broadcast() actions. When both are used, it will be rare that
|
||||
the race will appear, and it would require MANY Wait() and signaling
|
||||
activities. If this implementation did not recycle events, then it
|
||||
would have to create and destroy events for every call to Wait().
|
||||
That allocation/deallocation and associated construction/destruction
|
||||
would be costly (per wait), and would only be a rare benefit (when the
|
||||
race was "lost" and a spurious signal took place). That would be bad
|
||||
(IMO) optimization trade-off. Finally, such spurious events are
|
||||
allowed by the specification of condition variables (such as
|
||||
implemented in Vista), and hence it is better if any user accommodates
|
||||
such spurious events (see usage note in condition_variable.h).
|
||||
|
||||
5) Why don't you reset events when you are about to recycle them, or
|
||||
about to reuse them, so that the spurious signals don't take place?
|
||||
The thread described in FAQ-question-3 step c may be pre-empted for an
|
||||
arbitrary length of time before proceeding to step d. As a result,
|
||||
the wait_event may actually be re-used *before* step (e) is reached.
|
||||
As a result, calling reset would not help significantly.
|
||||
|
||||
6) How is it that the callers lock is released atomically with the
|
||||
entry into a wait state? We commit to the wait activity when we
|
||||
allocate the wait_event for use in a given call to Wait(). This
|
||||
allocation takes place before the caller's lock is released (and
|
||||
actually before our internal_lock_ is released). That allocation is
|
||||
the defining moment when "the wait state has been entered," as that
|
||||
thread *can* now be signaled by a call to Broadcast() or Signal().
|
||||
Hence we actually "commit to wait" before releasing the lock, making
|
||||
the pair effectively atomic.
|
||||
|
||||
8) Why do you need to lock your data structures during waiting, as the
|
||||
caller is already in possession of a lock? We need to Acquire() and
|
||||
Release() our internal lock during Signal() and Broadcast(). If we tried
|
||||
to use a callers lock for this purpose, we might conflict with their
|
||||
external use of the lock. For example, the caller may use to consistently
|
||||
hold a lock on one thread while calling Signal() on another, and that would
|
||||
block Signal().
|
||||
|
||||
9) Couldn't a more efficient implementation be provided if you
|
||||
preclude using more than one external lock in conjunction with a
|
||||
single ConditionVariable instance? Yes, at least it could be viewed
|
||||
as a simpler API (since you don't have to reiterate the lock argument
|
||||
in each Wait() call). One of the constructors now takes a specific
|
||||
lock as an argument, and a there are corresponding Wait() calls that
|
||||
don't specify a lock now. It turns that the resulting implmentation
|
||||
can't be made more efficient, as the internal lock needs to be used by
|
||||
Signal() and Broadcast(), to access internal data structures. As a
|
||||
result, I was not able to utilize the user supplied lock (which is
|
||||
being used by the user elsewhere presumably) to protect the private
|
||||
member access.
|
||||
|
||||
9) Since you have a second lock, how can be be sure that there is no
|
||||
possible deadlock scenario? Our internal_lock_ is always the last
|
||||
lock acquired, and the first one released, and hence a deadlock (due
|
||||
to critical section problems) is impossible as a consequence of our
|
||||
lock.
|
||||
|
||||
10) When doing a Broadcast(), why did you copy all the events into
|
||||
an STL queue, rather than making a linked-loop, and iterating over it?
|
||||
The iterating during Broadcast() is done so outside the protection
|
||||
of the internal lock. As a result, other threads, such as the thread
|
||||
wherein a related event is waiting, could asynchronously manipulate
|
||||
the links around a cv_event. As a result, the link structure cannot
|
||||
be used outside a lock. Broadcast() could iterate over waiting
|
||||
events by cycling in-and-out of the protection of the internal_lock,
|
||||
but that appears more expensive than copying the list into an STL
|
||||
stack.
|
||||
|
||||
11) Why did the lock.h file need to be modified so much for this
|
||||
change? Central to a Condition Variable is the atomic release of a
|
||||
lock during a Wait(). This places Wait() functionality exactly
|
||||
mid-way between the two classes, Lock and Condition Variable. Given
|
||||
that there can be nested Acquire()'s of locks, and Wait() had to
|
||||
Release() completely a held lock, it was necessary to augment the Lock
|
||||
class with a recursion counter. Even more subtle is the fact that the
|
||||
recursion counter (in a Lock) must be protected, as many threads can
|
||||
access it asynchronously. As a positive fallout of this, there are
|
||||
now some DCHECKS to be sure no one Release()s a Lock more than they
|
||||
Acquire()ed it, and there is ifdef'ed functionality that can detect
|
||||
nested locks (legal under windows, but not under Posix).
|
||||
|
||||
12) Why is it that the cv_events removed from list in Broadcast() and Signal()
|
||||
are not leaked? How are they recovered?? The cv_events that appear to leak are
|
||||
taken from the waiting_list_. For each element in that list, there is currently
|
||||
a thread in or around the WaitForSingleObject() call of Wait(), and those
|
||||
threads have references to these otherwise leaked events. They are passed as
|
||||
arguments to be recycled just aftre returning from WaitForSingleObject().
|
||||
|
||||
13) Why did you use a custom container class (the linked list), when STL has
|
||||
perfectly good containers, such as an STL list? The STL list, as with any
|
||||
container, does not guarantee the utility of an iterator across manipulation
|
||||
(such as insertions and deletions) of the underlying container. The custom
|
||||
double-linked-list container provided that assurance. I don't believe any
|
||||
combination of STL containers provided the services that were needed at the same
|
||||
O(1) efficiency as the custom linked list. The unusual requirement
|
||||
for the container class is that a reference to an item within a container (an
|
||||
iterator) needed to be maintained across an arbitrary manipulation of the
|
||||
container. This requirement exposes itself in the Wait() method, where a
|
||||
waiting_event must be selected prior to the WaitForSingleObject(), and then it
|
||||
must be used as part of recycling to remove the related instance from the
|
||||
waiting_list. A hash table (STL map) could be used, but I was embarrased to
|
||||
use a complex and relatively low efficiency container when a doubly linked list
|
||||
provided O(1) performance in all required operations. Since other operations
|
||||
to provide performance-and/or-fairness required queue (FIFO) and list (LIFO)
|
||||
containers, I would also have needed to use an STL list/queue as well as an STL
|
||||
map. In the end I decided it would be "fun" to just do it right, and I
|
||||
put so many assertions (DCHECKs) into the container class that it is trivial to
|
||||
code review and validate its correctness.
|
||||
|
||||
*/
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/cpu.h"
|
||||
#include <intrin.h>
|
||||
#include <string>
|
||||
|
||||
namespace base {
|
||||
|
||||
CPU::CPU()
|
||||
: type_(0),
|
||||
family_(0),
|
||||
model_(0),
|
||||
stepping_(0),
|
||||
ext_model_(0),
|
||||
ext_family_(0),
|
||||
cpu_vendor_("unknown") {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void CPU::Initialize() {
|
||||
int cpu_info[4] = {-1};
|
||||
char cpu_string[0x20];
|
||||
|
||||
// __cpuid with an InfoType argument of 0 returns the number of
|
||||
// valid Ids in CPUInfo[0] and the CPU identification string in
|
||||
// the other three array elements. The CPU identification string is
|
||||
// not in linear order. The code below arranges the information
|
||||
// in a human readable form.
|
||||
//
|
||||
// More info can be found here:
|
||||
// http://msdn.microsoft.com/en-us/library/hskdteyh.aspx
|
||||
__cpuid(cpu_info, 0);
|
||||
int num_ids = cpu_info[0];
|
||||
memset(cpu_string, 0, sizeof(cpu_string));
|
||||
*(reinterpret_cast<int*>(cpu_string)) = cpu_info[1];
|
||||
*(reinterpret_cast<int*>(cpu_string+4)) = cpu_info[3];
|
||||
*(reinterpret_cast<int*>(cpu_string+8)) = cpu_info[2];
|
||||
|
||||
// Interpret CPU feature information.
|
||||
if (num_ids > 0) {
|
||||
__cpuid(cpu_info, 1);
|
||||
stepping_ = cpu_info[0] & 0xf;
|
||||
model_ = (cpu_info[0] >> 4) & 0xf;
|
||||
family_ = (cpu_info[0] >> 8) & 0xf;
|
||||
type_ = (cpu_info[0] >> 12) & 0x3;
|
||||
ext_model_ = (cpu_info[0] >> 16) & 0xf;
|
||||
ext_family_ = (cpu_info[0] >> 20) & 0xff;
|
||||
cpu_vendor_ = cpu_string;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CPU_H_
|
||||
#define BASE_CPU_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace base {
|
||||
|
||||
// Query information about the processor.
|
||||
class CPU {
|
||||
public:
|
||||
// Constructor
|
||||
CPU();
|
||||
|
||||
// Accessors for CPU information.
|
||||
const std::string& vendor_name() const { return cpu_vendor_; }
|
||||
int stepping() const { return stepping_; }
|
||||
int model() const { return model_; }
|
||||
int family() const { return family_; }
|
||||
int type() const { return type_; }
|
||||
int extended_model() const { return ext_model_; }
|
||||
int extended_family() const { return ext_family_; }
|
||||
|
||||
private:
|
||||
// Query the processor for CPUID information.
|
||||
void Initialize();
|
||||
|
||||
int type_; // process type
|
||||
int family_; // family of the processor
|
||||
int model_; // model of processor
|
||||
int stepping_; // processor revision number
|
||||
int ext_model_;
|
||||
int ext_family_;
|
||||
std::string cpu_vendor_;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_CPU_H_
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/crypto/cssm_init.h"
|
||||
|
||||
#include <Security/cssm.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/singleton.h"
|
||||
|
||||
// When writing crypto code for Mac OS X, you may find the following
|
||||
// documentation useful:
|
||||
// - Common Security: CDSA and CSSM, Version 2 (with corrigenda)
|
||||
// http://www.opengroup.org/security/cdsa.htm
|
||||
// - Apple Cryptographic Service Provider Functional Specification
|
||||
// - CryptoSample: http://developer.apple.com/SampleCode/CryptoSample/
|
||||
|
||||
namespace {
|
||||
|
||||
class CSSMInitSingleton {
|
||||
public:
|
||||
CSSMInitSingleton() : inited_(false), loaded_(false) {
|
||||
static CSSM_VERSION version = {2, 0};
|
||||
// TODO(wtc): what should our caller GUID be?
|
||||
static const CSSM_GUID test_guid = {
|
||||
0xFADE, 0, 0, { 1, 2, 3, 4, 5, 6, 7, 0 }
|
||||
};
|
||||
CSSM_RETURN crtn;
|
||||
CSSM_PVC_MODE pvc_policy = CSSM_PVC_NONE;
|
||||
crtn = CSSM_Init(&version, CSSM_PRIVILEGE_SCOPE_NONE, &test_guid,
|
||||
CSSM_KEY_HIERARCHY_NONE, &pvc_policy, NULL);
|
||||
if (crtn) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
inited_ = true;
|
||||
|
||||
crtn = CSSM_ModuleLoad(&gGuidAppleCSP, CSSM_KEY_HIERARCHY_NONE, NULL, NULL);
|
||||
if (crtn) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
loaded_ = true;
|
||||
}
|
||||
|
||||
~CSSMInitSingleton() {
|
||||
CSSM_RETURN crtn;
|
||||
if (loaded_) {
|
||||
crtn = CSSM_ModuleUnload(&gGuidAppleCSP, NULL, NULL);
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
}
|
||||
if (inited_) {
|
||||
crtn = CSSM_Terminate();
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool inited_; // True if CSSM_Init has been called successfully.
|
||||
bool loaded_; // True if CSSM_ModuleLoad has been called successfully.
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
void EnsureCSSMInit() {
|
||||
Singleton<CSSMInitSingleton>::get();
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CRYPTO_CSSM_INIT_H_
|
||||
#define BASE_CRYPTO_CSSM_INIT_H_
|
||||
|
||||
namespace base {
|
||||
|
||||
// Initialize CSSM if it isn't already initialized. This must be called before
|
||||
// any other CSSM functions. This function is thread-safe, and CSSM will only
|
||||
// ever be initialized once. CSSM will be properly shut down on program exit.
|
||||
void EnsureCSSMInit();
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_CRYPTO_CSSM_INIT_H_
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CRYPTO_SIGNATURE_VERIFIER_H_
|
||||
#define BASE_CRYPTO_SIGNATURE_VERIFIER_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#include <cryptoht.h>
|
||||
#elif defined(OS_MACOSX)
|
||||
#include <Security/cssm.h>
|
||||
#elif defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// The SignatureVerifier class verifies a signature using a bare public key
|
||||
// (as opposed to a certificate).
|
||||
class SignatureVerifier {
|
||||
public:
|
||||
SignatureVerifier();
|
||||
~SignatureVerifier();
|
||||
|
||||
// Streaming interface:
|
||||
|
||||
// Initiates a signature verification operation. This should be followed
|
||||
// by one or more VerifyUpdate calls and a VerifyFinal call.
|
||||
//
|
||||
// The signature algorithm is specified as a DER encoded ASN.1
|
||||
// AlgorithmIdentifier structure:
|
||||
// AlgorithmIdentifier ::= SEQUENCE {
|
||||
// algorithm OBJECT IDENTIFIER,
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||
//
|
||||
// The signature is encoded according to the signature algorithm, but it
|
||||
// must not be further encoded in an ASN.1 BIT STRING.
|
||||
// Note: An RSA signatures is actually a big integer. It must be in the
|
||||
// big-endian byte order.
|
||||
//
|
||||
// The public key is specified as a DER encoded ASN.1 SubjectPublicKeyInfo
|
||||
// structure, which contains not only the public key but also its type
|
||||
// (algorithm):
|
||||
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// subjectPublicKey BIT STRING }
|
||||
bool VerifyInit(const uint8* signature_algorithm,
|
||||
int signature_algorithm_len,
|
||||
const uint8* signature,
|
||||
int signature_len,
|
||||
const uint8* public_key_info,
|
||||
int public_key_info_len);
|
||||
|
||||
// Feeds a piece of the data to the signature verifier.
|
||||
void VerifyUpdate(const uint8* data_part, int data_part_len);
|
||||
|
||||
// Concludes a signature verification operation. Returns true if the
|
||||
// signature is valid. Returns false if the signature is invalid or an
|
||||
// error occurred.
|
||||
bool VerifyFinal();
|
||||
|
||||
// Note: we can provide a one-shot interface if there is interest:
|
||||
// bool Verify(const uint8* data,
|
||||
// int data_len,
|
||||
// const uint8* signature_algorithm,
|
||||
// int signature_algorithm_len,
|
||||
// const uint8* signature,
|
||||
// int signature_len,
|
||||
// const uint8* public_key_info,
|
||||
// int public_key_info_len);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
|
||||
std::vector<uint8> signature_;
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
VFYContext* vfy_context_;
|
||||
#elif defined(OS_MACOSX)
|
||||
std::vector<uint8> public_key_info_;
|
||||
|
||||
CSSM_CSP_HANDLE csp_handle_;
|
||||
|
||||
CSSM_CC_HANDLE sig_handle_;
|
||||
|
||||
CSSM_KEY public_key_;
|
||||
#elif defined(OS_WIN)
|
||||
HCRYPTPROV provider_;
|
||||
|
||||
HCRYPTHASH hash_object_;
|
||||
|
||||
HCRYPTKEY public_key_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_CRYPTO_SIGNATURE_VERIFIER_H_
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/crypto/signature_verifier.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "base/crypto/cssm_init.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void* AppMalloc(CSSM_SIZE size, void *alloc_ref) {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void AppFree(void* mem_ptr, void* alloc_ref) {
|
||||
free(mem_ptr);
|
||||
}
|
||||
|
||||
void* AppRealloc(void* ptr, CSSM_SIZE size, void* alloc_ref) {
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
void* AppCalloc(uint32 num, CSSM_SIZE size, void* alloc_ref) {
|
||||
return calloc(num, size);
|
||||
}
|
||||
|
||||
const CSSM_API_MEMORY_FUNCS mem_funcs = {
|
||||
AppMalloc,
|
||||
AppFree,
|
||||
AppRealloc,
|
||||
AppCalloc,
|
||||
NULL
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
SignatureVerifier::SignatureVerifier() : csp_handle_(0), sig_handle_(0) {
|
||||
EnsureCSSMInit();
|
||||
|
||||
static CSSM_VERSION version = {2, 0};
|
||||
CSSM_RETURN crtn;
|
||||
crtn = CSSM_ModuleAttach(&gGuidAppleCSP, &version, &mem_funcs, 0,
|
||||
CSSM_SERVICE_CSP, 0, CSSM_KEY_HIERARCHY_NONE,
|
||||
NULL, 0, NULL, &csp_handle_);
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
}
|
||||
|
||||
SignatureVerifier::~SignatureVerifier() {
|
||||
Reset();
|
||||
if (csp_handle_) {
|
||||
CSSM_RETURN crtn = CSSM_ModuleDetach(csp_handle_);
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
}
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm,
|
||||
int signature_algorithm_len,
|
||||
const uint8* signature,
|
||||
int signature_len,
|
||||
const uint8* public_key_info,
|
||||
int public_key_info_len) {
|
||||
signature_.assign(signature, signature + signature_len);
|
||||
public_key_info_.assign(public_key_info,
|
||||
public_key_info + public_key_info_len);
|
||||
|
||||
CSSM_ALGORITHMS key_alg = CSSM_ALGID_RSA; // TODO(wtc): hardcoded.
|
||||
|
||||
memset(&public_key_, 0, sizeof(public_key_));
|
||||
public_key_.KeyData.Data = const_cast<uint8*>(&public_key_info_[0]);
|
||||
public_key_.KeyData.Length = public_key_info_.size();
|
||||
public_key_.KeyHeader.HeaderVersion = CSSM_KEYHEADER_VERSION;
|
||||
public_key_.KeyHeader.BlobType = CSSM_KEYBLOB_RAW;
|
||||
public_key_.KeyHeader.Format = CSSM_KEYBLOB_RAW_FORMAT_X509;
|
||||
public_key_.KeyHeader.AlgorithmId = key_alg;
|
||||
public_key_.KeyHeader.KeyClass = CSSM_KEYCLASS_PUBLIC_KEY;
|
||||
public_key_.KeyHeader.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
|
||||
public_key_.KeyHeader.KeyUsage = CSSM_KEYUSE_VERIFY;
|
||||
CSSM_KEY_SIZE key_size;
|
||||
CSSM_RETURN crtn;
|
||||
crtn = CSSM_QueryKeySizeInBits(csp_handle_, NULL, &public_key_, &key_size);
|
||||
if (crtn) {
|
||||
NOTREACHED() << "CSSM_QueryKeySizeInBits failed: " << crtn;
|
||||
return false;
|
||||
}
|
||||
public_key_.KeyHeader.LogicalKeySizeInBits = key_size.LogicalKeySizeInBits;
|
||||
|
||||
// TODO(wtc): decode signature_algorithm...
|
||||
CSSM_ALGORITHMS sig_alg = CSSM_ALGID_SHA1WithRSA;
|
||||
|
||||
crtn = CSSM_CSP_CreateSignatureContext(csp_handle_, sig_alg, NULL,
|
||||
&public_key_, &sig_handle_);
|
||||
if (crtn) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
crtn = CSSM_VerifyDataInit(sig_handle_);
|
||||
if (crtn) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SignatureVerifier::VerifyUpdate(const uint8* data_part,
|
||||
int data_part_len) {
|
||||
CSSM_DATA data;
|
||||
data.Data = const_cast<uint8*>(data_part);
|
||||
data.Length = data_part_len;
|
||||
CSSM_RETURN crtn = CSSM_VerifyDataUpdate(sig_handle_, &data, 1);
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyFinal() {
|
||||
CSSM_DATA sig;
|
||||
sig.Data = const_cast<uint8*>(&signature_[0]);
|
||||
sig.Length = signature_.size();
|
||||
CSSM_RETURN crtn = CSSM_VerifyDataFinal(sig_handle_, &sig);
|
||||
Reset();
|
||||
|
||||
// crtn is CSSMERR_CSP_VERIFY_FAILED if signature verification fails.
|
||||
return (crtn == CSSM_OK);
|
||||
}
|
||||
|
||||
void SignatureVerifier::Reset() {
|
||||
CSSM_RETURN crtn;
|
||||
if (sig_handle_) {
|
||||
crtn = CSSM_DeleteContext(sig_handle_);
|
||||
DCHECK(crtn == CSSM_OK);
|
||||
sig_handle_ = 0;
|
||||
}
|
||||
signature_.clear();
|
||||
|
||||
// Can't call CSSM_FreeKey on public_key_ because we constructed
|
||||
// public_key_ manually.
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/crypto/signature_verifier.h"
|
||||
|
||||
#include <cryptohi.h>
|
||||
#include <keyhi.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/nss_init.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
SignatureVerifier::SignatureVerifier() : vfy_context_(NULL) {
|
||||
EnsureNSSInit();
|
||||
}
|
||||
|
||||
SignatureVerifier::~SignatureVerifier() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm,
|
||||
int signature_algorithm_len,
|
||||
const uint8* signature,
|
||||
int signature_len,
|
||||
const uint8* public_key_info,
|
||||
int public_key_info_len) {
|
||||
signature_.assign(signature, signature + signature_len);
|
||||
|
||||
CERTSubjectPublicKeyInfo* spki = NULL;
|
||||
SECItem spki_der;
|
||||
spki_der.type = siBuffer;
|
||||
spki_der.data = const_cast<uint8*>(public_key_info);
|
||||
spki_der.len = public_key_info_len;
|
||||
spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_der);
|
||||
if (!spki)
|
||||
return false;
|
||||
SECKEYPublicKey* public_key = SECKEY_ExtractPublicKey(spki);
|
||||
SECKEY_DestroySubjectPublicKeyInfo(spki); // Done with spki.
|
||||
if (!public_key)
|
||||
return false;
|
||||
|
||||
PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
||||
if (!arena) {
|
||||
SECKEY_DestroyPublicKey(public_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
SECItem sig_alg_der;
|
||||
sig_alg_der.type = siBuffer;
|
||||
sig_alg_der.data = const_cast<uint8*>(signature_algorithm);
|
||||
sig_alg_der.len = signature_algorithm_len;
|
||||
SECAlgorithmID sig_alg_id;
|
||||
SECStatus rv;
|
||||
rv = SEC_QuickDERDecodeItem(arena, &sig_alg_id, SECOID_AlgorithmIDTemplate,
|
||||
&sig_alg_der);
|
||||
if (rv != SECSuccess) {
|
||||
SECKEY_DestroyPublicKey(public_key);
|
||||
PORT_FreeArena(arena, PR_TRUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
SECItem sig;
|
||||
sig.type = siBuffer;
|
||||
sig.data = const_cast<uint8*>(signature);
|
||||
sig.len = signature_len;
|
||||
SECOidTag hash_alg_tag;
|
||||
vfy_context_ = VFY_CreateContextWithAlgorithmID(public_key, &sig,
|
||||
&sig_alg_id, &hash_alg_tag,
|
||||
NULL);
|
||||
SECKEY_DestroyPublicKey(public_key); // Done with public_key.
|
||||
PORT_FreeArena(arena, PR_TRUE); // Done with sig_alg_id.
|
||||
if (!vfy_context_) {
|
||||
// A corrupted RSA signature could be detected without the data, so
|
||||
// VFY_CreateContextWithAlgorithmID may fail with SEC_ERROR_BAD_SIGNATURE
|
||||
// (-8182).
|
||||
return false;
|
||||
}
|
||||
|
||||
rv = VFY_Begin(vfy_context_);
|
||||
if (rv != SECSuccess) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SignatureVerifier::VerifyUpdate(const uint8* data_part,
|
||||
int data_part_len) {
|
||||
SECStatus rv = VFY_Update(vfy_context_, data_part, data_part_len);
|
||||
DCHECK(rv == SECSuccess);
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyFinal() {
|
||||
SECStatus rv = VFY_End(vfy_context_);
|
||||
Reset();
|
||||
|
||||
// If signature verification fails, the error code is
|
||||
// SEC_ERROR_BAD_SIGNATURE (-8182).
|
||||
return (rv == SECSuccess);
|
||||
}
|
||||
|
||||
void SignatureVerifier::Reset() {
|
||||
if (vfy_context_) {
|
||||
VFY_DestroyContext(vfy_context_, PR_TRUE);
|
||||
vfy_context_ = NULL;
|
||||
}
|
||||
signature_.clear();
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/crypto/signature_verifier.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
TEST(SignatureVerifierTest, BasicTest) {
|
||||
// The input data in this test comes from real certificates.
|
||||
//
|
||||
// tbs_certificate ("to-be-signed certificate", the part of a certificate
|
||||
// that is signed), signature_algorithm, and algorithm come from the
|
||||
// certificate of bugs.webkit.org.
|
||||
//
|
||||
// public_key_info comes from the certificate of the issuer, Go Daddy Secure
|
||||
// Certification Authority.
|
||||
//
|
||||
// The bytes in the array initializers are formatted to expose the DER
|
||||
// encoding of the ASN.1 structures.
|
||||
|
||||
// The data that is signed is the following ASN.1 structure:
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// ... -- omitted, not important
|
||||
// }
|
||||
const uint8 tbs_certificate[1017] = {
|
||||
0x30, 0x82, 0x03, 0xf5, // a SEQUENCE of length 1013 (0x3f5)
|
||||
0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x03, 0x43, 0xdd, 0x63, 0x30, 0x0d,
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
|
||||
0x00, 0x30, 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
|
||||
0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55,
|
||||
0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31,
|
||||
0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63,
|
||||
0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18,
|
||||
0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64,
|
||||
0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
|
||||
0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68,
|
||||
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64,
|
||||
0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
|
||||
0x69, 0x74, 0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55,
|
||||
0x04, 0x03, 0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79,
|
||||
0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
|
||||
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06,
|
||||
0x03, 0x55, 0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32,
|
||||
0x38, 0x37, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x33, 0x31, 0x38,
|
||||
0x32, 0x33, 0x33, 0x35, 0x31, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30,
|
||||
0x33, 0x31, 0x38, 0x32, 0x33, 0x33, 0x35, 0x31, 0x39, 0x5a, 0x30, 0x79,
|
||||
0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
|
||||
0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a,
|
||||
0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x12,
|
||||
0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x43, 0x75, 0x70,
|
||||
0x65, 0x72, 0x74, 0x69, 0x6e, 0x6f, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
|
||||
0x55, 0x04, 0x0a, 0x13, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x49,
|
||||
0x6e, 0x63, 0x2e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0b,
|
||||
0x13, 0x0c, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x46, 0x6f, 0x72,
|
||||
0x67, 0x65, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
|
||||
0x0c, 0x2a, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6f, 0x72,
|
||||
0x67, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
|
||||
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
|
||||
0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xa7, 0x62, 0x79, 0x41, 0xda, 0x28,
|
||||
0xf2, 0xc0, 0x4f, 0xe0, 0x25, 0xaa, 0xa1, 0x2e, 0x3b, 0x30, 0x94, 0xb5,
|
||||
0xc9, 0x26, 0x3a, 0x1b, 0xe2, 0xd0, 0xcc, 0xa2, 0x95, 0xe2, 0x91, 0xc0,
|
||||
0xf0, 0x40, 0x9e, 0x27, 0x6e, 0xbd, 0x6e, 0xde, 0x7c, 0xb6, 0x30, 0x5c,
|
||||
0xb8, 0x9b, 0x01, 0x2f, 0x92, 0x04, 0xa1, 0xef, 0x4a, 0xb1, 0x6c, 0xb1,
|
||||
0x7e, 0x8e, 0xcd, 0xa6, 0xf4, 0x40, 0x73, 0x1f, 0x2c, 0x96, 0xad, 0xff,
|
||||
0x2a, 0x6d, 0x0e, 0xba, 0x52, 0x84, 0x83, 0xb0, 0x39, 0xee, 0xc9, 0x39,
|
||||
0xdc, 0x1e, 0x34, 0xd0, 0xd8, 0x5d, 0x7a, 0x09, 0xac, 0xa9, 0xee, 0xca,
|
||||
0x65, 0xf6, 0x85, 0x3a, 0x6b, 0xee, 0xe4, 0x5c, 0x5e, 0xf8, 0xda, 0xd1,
|
||||
0xce, 0x88, 0x47, 0xcd, 0x06, 0x21, 0xe0, 0xb9, 0x4b, 0xe4, 0x07, 0xcb,
|
||||
0x57, 0xdc, 0xca, 0x99, 0x54, 0xf7, 0x0e, 0xd5, 0x17, 0x95, 0x05, 0x2e,
|
||||
0xe9, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xce, 0x30,
|
||||
0x82, 0x01, 0xca, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02,
|
||||
0x30, 0x00, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03,
|
||||
0x02, 0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16,
|
||||
0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
|
||||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x57,
|
||||
0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x50, 0x30, 0x4e, 0x30, 0x4c, 0xa0,
|
||||
0x4a, 0xa0, 0x48, 0x86, 0x46, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
|
||||
0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73,
|
||||
0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f,
|
||||
0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x65, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x64, 0x65, 0x64, 0x69, 0x73, 0x73, 0x75, 0x69, 0x6e, 0x67, 0x33, 0x2e,
|
||||
0x63, 0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b,
|
||||
0x30, 0x49, 0x30, 0x47, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd,
|
||||
0x6d, 0x01, 0x07, 0x17, 0x02, 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b,
|
||||
0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74,
|
||||
0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
|
||||
0x6f, 0x72, 0x79, 0x30, 0x7f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
|
||||
0x07, 0x01, 0x01, 0x04, 0x73, 0x30, 0x71, 0x30, 0x23, 0x06, 0x08, 0x2b,
|
||||
0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74,
|
||||
0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64,
|
||||
0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4a, 0x06, 0x08,
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, 0x74,
|
||||
0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64,
|
||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
|
||||
0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72, 0x74,
|
||||
0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x48,
|
||||
0xdf, 0x60, 0x32, 0xcc, 0x89, 0x01, 0xb6, 0xdc, 0x2f, 0xe3, 0x73, 0xb5,
|
||||
0x9c, 0x16, 0x58, 0x32, 0x68, 0xa9, 0xc3, 0x30, 0x1f, 0x06, 0x03, 0x55,
|
||||
0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32,
|
||||
0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76,
|
||||
0x99, 0x68, 0xcc, 0xe7, 0x30, 0x23, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04,
|
||||
0x1c, 0x30, 0x1a, 0x82, 0x0c, 0x2a, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69,
|
||||
0x74, 0x2e, 0x6f, 0x72, 0x67, 0x82, 0x0a, 0x77, 0x65, 0x62, 0x6b, 0x69,
|
||||
0x74, 0x2e, 0x6f, 0x72, 0x67
|
||||
};
|
||||
|
||||
// The signature algorithm is specified as the following ASN.1 structure:
|
||||
// AlgorithmIdentifier ::= SEQUENCE {
|
||||
// algorithm OBJECT IDENTIFIER,
|
||||
// parameters ANY DEFINED BY algorithm OPTIONAL }
|
||||
//
|
||||
const uint8 signature_algorithm[15] = {
|
||||
0x30, 0x0d, // a SEQUENCE of length 13 (0xd)
|
||||
0x06, 0x09, // an OBJECT IDENTIFIER of length 9
|
||||
// 1.2.840.113549.1.1.5 - sha1WithRSAEncryption
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
|
||||
0x05, 0x00, // a NULL of length 0
|
||||
};
|
||||
|
||||
// RSA signature, a big integer in the big-endian byte order.
|
||||
const uint8 signature[256] = {
|
||||
0x1e, 0x6a, 0xe7, 0xe0, 0x4f, 0xe7, 0x4d, 0xd0, 0x69, 0x7c, 0xf8, 0x8f,
|
||||
0x99, 0xb4, 0x18, 0x95, 0x36, 0x24, 0x0f, 0x0e, 0xa3, 0xea, 0x34, 0x37,
|
||||
0xf4, 0x7d, 0xd5, 0x92, 0x35, 0x53, 0x72, 0x76, 0x3f, 0x69, 0xf0, 0x82,
|
||||
0x56, 0xe3, 0x94, 0x7a, 0x1d, 0x1a, 0x81, 0xaf, 0x9f, 0xc7, 0x43, 0x01,
|
||||
0x64, 0xd3, 0x7c, 0x0d, 0xc8, 0x11, 0x4e, 0x4a, 0xe6, 0x1a, 0xc3, 0x01,
|
||||
0x74, 0xe8, 0x35, 0x87, 0x5c, 0x61, 0xaa, 0x8a, 0x46, 0x06, 0xbe, 0x98,
|
||||
0x95, 0x24, 0x9e, 0x01, 0xe3, 0xe6, 0xa0, 0x98, 0xee, 0x36, 0x44, 0x56,
|
||||
0x8d, 0x23, 0x9c, 0x65, 0xea, 0x55, 0x6a, 0xdf, 0x66, 0xee, 0x45, 0xe8,
|
||||
0xa0, 0xe9, 0x7d, 0x9a, 0xba, 0x94, 0xc5, 0xc8, 0xc4, 0x4b, 0x98, 0xff,
|
||||
0x9a, 0x01, 0x31, 0x6d, 0xf9, 0x2b, 0x58, 0xe7, 0xe7, 0x2a, 0xc5, 0x4d,
|
||||
0xbb, 0xbb, 0xcd, 0x0d, 0x70, 0xe1, 0xad, 0x03, 0xf5, 0xfe, 0xf4, 0x84,
|
||||
0x71, 0x08, 0xd2, 0xbc, 0x04, 0x7b, 0x26, 0x1c, 0xa8, 0x0f, 0x9c, 0xd8,
|
||||
0x12, 0x6a, 0x6f, 0x2b, 0x67, 0xa1, 0x03, 0x80, 0x9a, 0x11, 0x0b, 0xe9,
|
||||
0xe0, 0xb5, 0xb3, 0xb8, 0x19, 0x4e, 0x0c, 0xa4, 0xd9, 0x2b, 0x3b, 0xc2,
|
||||
0xca, 0x20, 0xd3, 0x0c, 0xa4, 0xff, 0x93, 0x13, 0x1f, 0xfc, 0xba, 0x94,
|
||||
0x93, 0x8c, 0x64, 0x15, 0x2e, 0x28, 0xa9, 0x55, 0x8c, 0x2c, 0x48, 0xd3,
|
||||
0xd3, 0xc1, 0x50, 0x69, 0x19, 0xe8, 0x34, 0xd3, 0xf1, 0x04, 0x9f, 0x0a,
|
||||
0x7a, 0x21, 0x87, 0xbf, 0xb9, 0x59, 0x37, 0x2e, 0xf4, 0x71, 0xa5, 0x3e,
|
||||
0xbe, 0xcd, 0x70, 0x83, 0x18, 0xf8, 0x8a, 0x72, 0x85, 0x45, 0x1f, 0x08,
|
||||
0x01, 0x6f, 0x37, 0xf5, 0x2b, 0x7b, 0xea, 0xb9, 0x8b, 0xa3, 0xcc, 0xfd,
|
||||
0x35, 0x52, 0xdd, 0x66, 0xde, 0x4f, 0x30, 0xc5, 0x73, 0x81, 0xb6, 0xe8,
|
||||
0x3c, 0xd8, 0x48, 0x8a
|
||||
};
|
||||
|
||||
// The public key is specified as the following ASN.1 structure:
|
||||
// SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
// algorithm AlgorithmIdentifier,
|
||||
// subjectPublicKey BIT STRING }
|
||||
const uint8 public_key_info[294] = {
|
||||
0x30, 0x82, 0x01, 0x22, // a SEQUENCE of length 290 (0x122)
|
||||
// algorithm
|
||||
0x30, 0x0d, // a SEQUENCE of length 13
|
||||
0x06, 0x09, // an OBJECT IDENTIFIER of length 9
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
||||
0x05, 0x00, // a NULL of length 0
|
||||
// subjectPublicKey
|
||||
0x03, 0x82, 0x01, 0x0f, // a BIT STRING of length 271 (0x10f)
|
||||
0x00, // number of unused bits
|
||||
0x30, 0x82, 0x01, 0x0a, // a SEQUENCE of length 266 (0x10a)
|
||||
// modulus
|
||||
0x02, 0x82, 0x01, 0x01, // an INTEGER of length 257 (0x101)
|
||||
0x00, 0xc4, 0x2d, 0xd5, 0x15, 0x8c, 0x9c, 0x26, 0x4c, 0xec,
|
||||
0x32, 0x35, 0xeb, 0x5f, 0xb8, 0x59, 0x01, 0x5a, 0xa6, 0x61,
|
||||
0x81, 0x59, 0x3b, 0x70, 0x63, 0xab, 0xe3, 0xdc, 0x3d, 0xc7,
|
||||
0x2a, 0xb8, 0xc9, 0x33, 0xd3, 0x79, 0xe4, 0x3a, 0xed, 0x3c,
|
||||
0x30, 0x23, 0x84, 0x8e, 0xb3, 0x30, 0x14, 0xb6, 0xb2, 0x87,
|
||||
0xc3, 0x3d, 0x95, 0x54, 0x04, 0x9e, 0xdf, 0x99, 0xdd, 0x0b,
|
||||
0x25, 0x1e, 0x21, 0xde, 0x65, 0x29, 0x7e, 0x35, 0xa8, 0xa9,
|
||||
0x54, 0xeb, 0xf6, 0xf7, 0x32, 0x39, 0xd4, 0x26, 0x55, 0x95,
|
||||
0xad, 0xef, 0xfb, 0xfe, 0x58, 0x86, 0xd7, 0x9e, 0xf4, 0x00,
|
||||
0x8d, 0x8c, 0x2a, 0x0c, 0xbd, 0x42, 0x04, 0xce, 0xa7, 0x3f,
|
||||
0x04, 0xf6, 0xee, 0x80, 0xf2, 0xaa, 0xef, 0x52, 0xa1, 0x69,
|
||||
0x66, 0xda, 0xbe, 0x1a, 0xad, 0x5d, 0xda, 0x2c, 0x66, 0xea,
|
||||
0x1a, 0x6b, 0xbb, 0xe5, 0x1a, 0x51, 0x4a, 0x00, 0x2f, 0x48,
|
||||
0xc7, 0x98, 0x75, 0xd8, 0xb9, 0x29, 0xc8, 0xee, 0xf8, 0x66,
|
||||
0x6d, 0x0a, 0x9c, 0xb3, 0xf3, 0xfc, 0x78, 0x7c, 0xa2, 0xf8,
|
||||
0xa3, 0xf2, 0xb5, 0xc3, 0xf3, 0xb9, 0x7a, 0x91, 0xc1, 0xa7,
|
||||
0xe6, 0x25, 0x2e, 0x9c, 0xa8, 0xed, 0x12, 0x65, 0x6e, 0x6a,
|
||||
0xf6, 0x12, 0x44, 0x53, 0x70, 0x30, 0x95, 0xc3, 0x9c, 0x2b,
|
||||
0x58, 0x2b, 0x3d, 0x08, 0x74, 0x4a, 0xf2, 0xbe, 0x51, 0xb0,
|
||||
0xbf, 0x87, 0xd0, 0x4c, 0x27, 0x58, 0x6b, 0xb5, 0x35, 0xc5,
|
||||
0x9d, 0xaf, 0x17, 0x31, 0xf8, 0x0b, 0x8f, 0xee, 0xad, 0x81,
|
||||
0x36, 0x05, 0x89, 0x08, 0x98, 0xcf, 0x3a, 0xaf, 0x25, 0x87,
|
||||
0xc0, 0x49, 0xea, 0xa7, 0xfd, 0x67, 0xf7, 0x45, 0x8e, 0x97,
|
||||
0xcc, 0x14, 0x39, 0xe2, 0x36, 0x85, 0xb5, 0x7e, 0x1a, 0x37,
|
||||
0xfd, 0x16, 0xf6, 0x71, 0x11, 0x9a, 0x74, 0x30, 0x16, 0xfe,
|
||||
0x13, 0x94, 0xa3, 0x3f, 0x84, 0x0d, 0x4f,
|
||||
// public exponent
|
||||
0x02, 0x03, // an INTEGER of length 3
|
||||
0x01, 0x00, 0x01
|
||||
};
|
||||
|
||||
// We use the signature verifier to perform four signature verification
|
||||
// tests.
|
||||
base::SignatureVerifier verifier;
|
||||
bool ok;
|
||||
|
||||
// Test 1: feed all of the data to the verifier at once (a single
|
||||
// VerifyUpdate call).
|
||||
ok = verifier.VerifyInit(signature_algorithm,
|
||||
sizeof(signature_algorithm),
|
||||
signature, sizeof(signature),
|
||||
public_key_info, sizeof(public_key_info));
|
||||
EXPECT_TRUE(ok);
|
||||
verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate));
|
||||
ok = verifier.VerifyFinal();
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
// Test 2: feed the data to the verifier in three parts (three VerifyUpdate
|
||||
// calls).
|
||||
ok = verifier.VerifyInit(signature_algorithm,
|
||||
sizeof(signature_algorithm),
|
||||
signature, sizeof(signature),
|
||||
public_key_info, sizeof(public_key_info));
|
||||
EXPECT_TRUE(ok);
|
||||
verifier.VerifyUpdate(tbs_certificate, 256);
|
||||
verifier.VerifyUpdate(tbs_certificate + 256, 256);
|
||||
verifier.VerifyUpdate(tbs_certificate + 512, sizeof(tbs_certificate) - 512);
|
||||
ok = verifier.VerifyFinal();
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
// Test 3: verify the signature with incorrect data.
|
||||
uint8 bad_tbs_certificate[sizeof(tbs_certificate)];
|
||||
memcpy(bad_tbs_certificate, tbs_certificate, sizeof(tbs_certificate));
|
||||
bad_tbs_certificate[10] += 1; // Corrupt one byte of the data.
|
||||
ok = verifier.VerifyInit(signature_algorithm,
|
||||
sizeof(signature_algorithm),
|
||||
signature, sizeof(signature),
|
||||
public_key_info, sizeof(public_key_info));
|
||||
EXPECT_TRUE(ok);
|
||||
verifier.VerifyUpdate(bad_tbs_certificate, sizeof(bad_tbs_certificate));
|
||||
ok = verifier.VerifyFinal();
|
||||
|
||||
// Purify disables digital signature verification, causing the Windows
|
||||
// CryptoAPI function CryptVerifySignature to always succeed. So we can't
|
||||
// check the signature verification results of the negative tests when
|
||||
// running inside Purify. See http://crbug.com/10031.
|
||||
#ifndef PURIFY
|
||||
EXPECT_FALSE(ok);
|
||||
#endif
|
||||
|
||||
// Test 4: verify a bad signature.
|
||||
uint8 bad_signature[sizeof(signature)];
|
||||
memcpy(bad_signature, signature, sizeof(signature));
|
||||
bad_signature[10] += 1; // Corrupt one byte of the signature.
|
||||
ok = verifier.VerifyInit(signature_algorithm,
|
||||
sizeof(signature_algorithm),
|
||||
bad_signature, sizeof(bad_signature),
|
||||
public_key_info, sizeof(public_key_info));
|
||||
|
||||
// A crypto library (e.g., NSS) may detect that the signature is corrupted
|
||||
// and cause VerifyInit to return false, so it is fine for 'ok' to be false.
|
||||
if (ok) {
|
||||
verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate));
|
||||
ok = verifier.VerifyFinal();
|
||||
#ifndef PURIFY
|
||||
EXPECT_FALSE(ok);
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/crypto/signature_verifier.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
|
||||
namespace {
|
||||
|
||||
// Wrappers of malloc and free for CRYPT_DECODE_PARA, which requires the
|
||||
// WINAPI calling convention.
|
||||
void* WINAPI MyCryptAlloc(size_t size) {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void WINAPI MyCryptFree(void* p) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
SignatureVerifier::SignatureVerifier() : hash_object_(0), public_key_(0) {
|
||||
if (!CryptAcquireContext(&provider_, NULL, NULL,
|
||||
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
|
||||
provider_ = 0;
|
||||
}
|
||||
|
||||
SignatureVerifier::~SignatureVerifier() {
|
||||
Reset();
|
||||
if (provider_) {
|
||||
BOOL ok = CryptReleaseContext(provider_, 0);
|
||||
DCHECK(ok);
|
||||
}
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm,
|
||||
int signature_algorithm_len,
|
||||
const uint8* signature,
|
||||
int signature_len,
|
||||
const uint8* public_key_info,
|
||||
int public_key_info_len) {
|
||||
signature_.reserve(signature_len);
|
||||
// CryptoAPI uses big integers in the little-endian byte order, so we need
|
||||
// to first swap the order of signature bytes.
|
||||
for (int i = signature_len - 1; i >= 0; --i)
|
||||
signature_.push_back(signature[i]);
|
||||
|
||||
CRYPT_DECODE_PARA decode_para;
|
||||
decode_para.cbSize = sizeof(decode_para);
|
||||
decode_para.pfnAlloc = MyCryptAlloc;
|
||||
decode_para.pfnFree = MyCryptFree;
|
||||
CERT_PUBLIC_KEY_INFO* cert_public_key_info = NULL;
|
||||
DWORD struct_len = 0;
|
||||
BOOL ok;
|
||||
ok = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
X509_PUBLIC_KEY_INFO,
|
||||
public_key_info,
|
||||
public_key_info_len,
|
||||
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
|
||||
&decode_para,
|
||||
&cert_public_key_info,
|
||||
&struct_len);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
ok = CryptImportPublicKeyInfo(provider_,
|
||||
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
cert_public_key_info, &public_key_);
|
||||
free(cert_public_key_info);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
CRYPT_ALGORITHM_IDENTIFIER* signature_algorithm_id;
|
||||
struct_len = 0;
|
||||
ok = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
X509_ALGORITHM_IDENTIFIER,
|
||||
signature_algorithm,
|
||||
signature_algorithm_len,
|
||||
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
|
||||
&decode_para,
|
||||
&signature_algorithm_id,
|
||||
&struct_len);
|
||||
DCHECK(ok || GetLastError() == ERROR_FILE_NOT_FOUND);
|
||||
ALG_ID hash_alg_id;
|
||||
if (ok) {
|
||||
hash_alg_id = CALG_MD4; // Initialize to a weak hash algorithm that we
|
||||
// don't support.
|
||||
if (!strcmp(signature_algorithm_id->pszObjId, szOID_RSA_SHA1RSA))
|
||||
hash_alg_id = CALG_SHA1;
|
||||
else if (!strcmp(signature_algorithm_id->pszObjId, szOID_RSA_MD5RSA))
|
||||
hash_alg_id = CALG_MD5;
|
||||
free(signature_algorithm_id);
|
||||
DCHECK(hash_alg_id != CALG_MD4);
|
||||
if (hash_alg_id == CALG_MD4)
|
||||
return false; // Unsupported hash algorithm.
|
||||
} else if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||
// TODO(wtc): X509_ALGORITHM_IDENTIFIER isn't supported on XP SP2. We
|
||||
// may be able to encapsulate signature_algorithm in a dummy SignedContent
|
||||
// and decode it with X509_CERT into a CERT_SIGNED_CONTENT_INFO. For now,
|
||||
// just hardcode the hash algorithm to be SHA-1.
|
||||
hash_alg_id = CALG_SHA1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = CryptCreateHash(provider_, hash_alg_id, 0, 0, &hash_object_);
|
||||
if (!ok)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SignatureVerifier::VerifyUpdate(const uint8* data_part,
|
||||
int data_part_len) {
|
||||
BOOL ok = CryptHashData(hash_object_, data_part, data_part_len, 0);
|
||||
DCHECK(ok) << "CryptHashData failed: " << GetLastError();
|
||||
}
|
||||
|
||||
bool SignatureVerifier::VerifyFinal() {
|
||||
BOOL ok = CryptVerifySignature(hash_object_, &signature_[0],
|
||||
signature_.size(), public_key_, NULL, 0);
|
||||
Reset();
|
||||
if (!ok)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SignatureVerifier::Reset() {
|
||||
BOOL ok;
|
||||
if (hash_object_) {
|
||||
ok = CryptDestroyHash(hash_object_);
|
||||
DCHECK(ok);
|
||||
hash_object_ = 0;
|
||||
}
|
||||
if (public_key_) {
|
||||
ok = CryptDestroyKey(public_key_);
|
||||
DCHECK(ok);
|
||||
public_key_ = 0;
|
||||
}
|
||||
signature_.clear();
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -0,0 +1 @@
|
|||
This file is different.
|
|
@ -0,0 +1 @@
|
|||
this file is the same.
|
|
@ -0,0 +1 @@
|
|||
This file is the same.
|
|
@ -0,0 +1 @@
|
|||
This file is the same.
|
|
@ -0,0 +1 @@
|
|||
This file is the same.
|
|
@ -0,0 +1 @@
|
|||
This file is not same.
|
|
@ -0,0 +1 @@
|
|||
This file is the
|
Двоичные данные
ipc/chromium/src/base/data/file_version_info_unittest/FileVersionInfoTest1.dll
Normal file
Двоичные данные
ipc/chromium/src/base/data/file_version_info_unittest/FileVersionInfoTest1.dll
Normal file
Двоичный файл не отображается.
Двоичные данные
ipc/chromium/src/base/data/file_version_info_unittest/FileVersionInfoTest2.dll
Normal file
Двоичные данные
ipc/chromium/src/base/data/file_version_info_unittest/FileVersionInfoTest2.dll
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,30 @@
|
|||
# this test causes Purify to get completely confused, aborting the test and
|
||||
# popping up 10 or more error dialogs
|
||||
StatsTableTest.MultipleProcesses
|
||||
|
||||
# see bug 1151158
|
||||
# causes purify to occasionally crash, possibly the same reason as 1110206 below
|
||||
StatsTableTest.MultipleThreads
|
||||
|
||||
# this test takes a really long time to run in Purify
|
||||
TimeTicks.Rollover
|
||||
TimeTicks.WinRollover
|
||||
|
||||
# see bug 1110206
|
||||
ConditionVariableTest.LargeFastTaskTest
|
||||
|
||||
# see bug 1150075
|
||||
MessageLoopTest.Crasher*
|
||||
|
||||
# see bug 1195707
|
||||
WMIUtilTest.*
|
||||
|
||||
# see issue 7412
|
||||
ScopedTempDir.*
|
||||
|
||||
# see issue 7477
|
||||
ObserverListThreadSafeTest.CrossThreadObserver
|
||||
ObserverListThreadSafeTest.CrossThreadNotifications
|
||||
|
||||
# see bug 10031
|
||||
SignatureVerifierTest.BasicTest
|
|
@ -0,0 +1,97 @@
|
|||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::_Mutex::_Mutex(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::ios_base::_Init(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::~LogMessage(void)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::_Mutex::_Mutex(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::ios_base::_Init(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::~LogMessage(void)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
CoTaskMemAlloc [OLE32.DLL]
|
||||
Alloc Location
|
||||
...
|
||||
base/wmi_util.cc WMIUtil::CreateLocalConnection(bool)
|
||||
base/wmi_util.cc WMIProcessUtil::Launch(class std::basic_string const &,int *)
|
||||
base/wmi_util_unittest.cc WMIUtilTest_TestLaunchProcess_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
CoTaskMemAlloc [OLE32.DLL]
|
||||
Alloc Location
|
||||
...
|
||||
base/wmi_util.cc WMIUtil::CreateLocalConnection(bool)
|
||||
base/wmi_util.cc WMIProcessUtil::Launch(class std::basic_string const &,int *)
|
||||
base/wmi_util_unittest.cc WMIUtilTest_TestLaunchProcess_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
base::ObjectWatcher::StartWatching(void *,Delegate::ObjectWatcher::base *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/object_watcher.cc base::ObjectWatcher::StartWatching(void *,Delegate::ObjectWatcher::base *)
|
||||
base/directory_watcher_win.cc DirectoryWatcher::Impl::OnObjectSignaled(void *)
|
||||
base/object_watcher.cc base::ObjectWatcher::Watch::Run(void)
|
||||
base/message_loop.cc MessageLoop::RunTask(Task *)
|
||||
^^^
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# -----
|
||||
# Leaks in ::RaiseException, called when we log a fatal error. See bug 1078612.
|
||||
|
||||
std::strstreambuf::overflow(int) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::_Mutex::_Mutex(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::ios_base::_Init(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::~LogMessage(void)
|
||||
base/check_handler_unittest.cc ThisFunctionAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckFunc_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::strstreambuf::overflow(int) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::_Mutex::_Mutex(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::basic_streambuf<char,char_traits<char>::std>::basic_streambuf<char,char_traits<char>::std>(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::ios_base::_Init(void) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::LogMessage(char const*,int,int)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
std::D::_Allocate(unsigned int,char *) [base_unittests.exe]
|
||||
Alloc Location
|
||||
...
|
||||
base/logging.cc logging::LogMessage::~LogMessage(void)
|
||||
base/check_handler_unittest.cc SimpleTestClass::ThisMethodAsserts(void)
|
||||
base/check_handler_unittest.cc CheckHandlerTest_TestMacroCheckObj_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
||||
# End of leaks in ::RaiseException
|
||||
# -----
|
|
@ -0,0 +1,8 @@
|
|||
# Probably a Purify error. See bug 1076843.
|
||||
WideCharToMultiByte: Invalid size (0x27) for destination buffer.
|
||||
Error Location
|
||||
...
|
||||
base/file_util_unittest.cc FileUtilTest_ResolveShortcutTest_Test::TestBody(void)
|
||||
testing/gtest/src/gtest.cc testing::Test::Run(void)
|
||||
^^^
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# This test currently times out in valgrind, see http://crbug.com/9194
|
||||
WatchdogTest.AlarmTest
|
||||
|
||||
# These tests occassionally hangs under Valgrind on Mac. valgrind-darwin r9573
|
||||
# Revisit with better valgrind.
|
||||
# Valgrind bug: https://bugs.kde.org/show_bug.cgi?id=189661
|
||||
TimerTest.RepeatingTimer
|
||||
TimerTest.RepeatingTimer_Cancel
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) 2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/data_pack.h"
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/string_piece.h"
|
||||
|
||||
// For details of the file layout, see
|
||||
// http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
|
||||
|
||||
namespace {
|
||||
static const uint32_t kFileFormatVersion = 1;
|
||||
// Length of file header: version and entry count.
|
||||
static const size_t kHeaderLength = 2 * sizeof(uint32_t);
|
||||
|
||||
struct DataPackEntry {
|
||||
uint32_t resource_id;
|
||||
uint32_t file_offset;
|
||||
uint32_t length;
|
||||
|
||||
static int CompareById(const void* void_key, const void* void_entry) {
|
||||
uint32_t key = *reinterpret_cast<const uint32_t*>(void_key);
|
||||
const DataPackEntry* entry =
|
||||
reinterpret_cast<const DataPackEntry*>(void_entry);
|
||||
if (key < entry->resource_id) {
|
||||
return -1;
|
||||
} else if (key > entry->resource_id) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} __attribute((packed));
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
// In .cc for MemoryMappedFile dtor.
|
||||
DataPack::DataPack() : resource_count_(0) {
|
||||
}
|
||||
DataPack::~DataPack() {
|
||||
}
|
||||
|
||||
bool DataPack::Load(const FilePath& path) {
|
||||
mmap_.reset(new file_util::MemoryMappedFile);
|
||||
if (!mmap_->Initialize(path)) {
|
||||
mmap_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the header of the file.
|
||||
// First uint32_t: version; second: resource count.
|
||||
const uint32* ptr = reinterpret_cast<const uint32_t*>(mmap_->data());
|
||||
uint32 version = ptr[0];
|
||||
if (version != kFileFormatVersion) {
|
||||
LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
|
||||
<< kFileFormatVersion;
|
||||
mmap_.reset();
|
||||
return false;
|
||||
}
|
||||
resource_count_ = ptr[1];
|
||||
|
||||
// Sanity check the file.
|
||||
// 1) Check we have enough entries.
|
||||
if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) >
|
||||
mmap_->length()) {
|
||||
LOG(ERROR) << "Data pack file corruption: too short for number of "
|
||||
"entries specified.";
|
||||
mmap_.reset();
|
||||
return false;
|
||||
}
|
||||
// 2) Verify the entries are within the appropriate bounds.
|
||||
for (size_t i = 0; i < resource_count_; ++i) {
|
||||
const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
|
||||
mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
|
||||
if (entry->file_offset + entry->length > mmap_->length()) {
|
||||
LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
|
||||
<< "Was the file corrupted?";
|
||||
mmap_.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataPack::Get(uint32_t resource_id, StringPiece* data) {
|
||||
// It won't be hard to make this endian-agnostic, but it's not worth
|
||||
// bothering to do right now.
|
||||
#if defined(__BYTE_ORDER)
|
||||
// Linux check
|
||||
COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
|
||||
datapack_assumes_little_endian);
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
// Mac check
|
||||
#error DataPack assumes little endian
|
||||
#endif
|
||||
|
||||
DataPackEntry* target = reinterpret_cast<DataPackEntry*>(
|
||||
bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
|
||||
sizeof(DataPackEntry), DataPackEntry::CompareById));
|
||||
if (!target) {
|
||||
LOG(ERROR) << "No resource found with id: " << resource_id;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->set(mmap_->data() + target->file_offset, target->length);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// DataPack represents a read-only view onto an on-disk file that contains
|
||||
// (key, value) pairs of data. It's used to store static resources like
|
||||
// translation strings and images.
|
||||
|
||||
#ifndef BASE_DATA_PACK_H_
|
||||
#define BASE_DATA_PACK_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
|
||||
namespace file_util {
|
||||
class MemoryMappedFile;
|
||||
}
|
||||
class FilePath;
|
||||
class StringPiece;
|
||||
|
||||
namespace base {
|
||||
|
||||
class DataPack {
|
||||
public:
|
||||
DataPack();
|
||||
~DataPack();
|
||||
|
||||
// Load a pack file from |path|, returning false on error.
|
||||
bool Load(const FilePath& path);
|
||||
|
||||
// Get resource by id |resource_id|, filling in |data|.
|
||||
// The data is owned by the DataPack object and should not be modified.
|
||||
// Returns false if the resource id isn't found.
|
||||
bool Get(uint32_t resource_id, StringPiece* data);
|
||||
|
||||
private:
|
||||
// The memory-mapped data.
|
||||
scoped_ptr<file_util::MemoryMappedFile> mmap_;
|
||||
|
||||
// Number of resources in the data.
|
||||
size_t resource_count_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DataPack);
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DATA_PACK_H_
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/data_pack.h"
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/string_piece.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
class DataPackTest : public testing::Test {
|
||||
public:
|
||||
DataPackTest() {
|
||||
PathService::Get(base::DIR_SOURCE_ROOT, &data_path_);
|
||||
data_path_ = data_path_.Append(
|
||||
FILE_PATH_LITERAL("base/data/data_pack_unittest/sample.pak"));
|
||||
}
|
||||
|
||||
FilePath data_path_;
|
||||
};
|
||||
|
||||
TEST_F(DataPackTest, Load) {
|
||||
base::DataPack pack;
|
||||
ASSERT_TRUE(pack.Load(data_path_));
|
||||
|
||||
StringPiece data;
|
||||
ASSERT_TRUE(pack.Get(4, &data));
|
||||
EXPECT_EQ("this is id 4", data);
|
||||
ASSERT_TRUE(pack.Get(6, &data));
|
||||
EXPECT_EQ("this is id 6", data);
|
||||
|
||||
// Try reading zero-length data blobs, just in case.
|
||||
ASSERT_TRUE(pack.Get(1, &data));
|
||||
EXPECT_EQ(0U, data.length());
|
||||
ASSERT_TRUE(pack.Get(10, &data));
|
||||
EXPECT_EQ(0U, data.length());
|
||||
|
||||
// Try looking up an invalid key.
|
||||
ASSERT_FALSE(pack.Get(140, &data));
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// Display the command line. This program is designed to be called from
|
||||
// another process to display assertions. Since the other process has
|
||||
// complete control of our command line, we assume that it did *not*
|
||||
// add the program name as the first parameter. This allows us to just
|
||||
// show the command line directly as the message.
|
||||
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
||||
LPSTR lpCmdLine, int nCmdShow) {
|
||||
LPWSTR cmdline = GetCommandLineW();
|
||||
MessageBox(NULL, cmdline, L"Kr\x00d8m", MB_TOPMOST);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/debug_on_start.h"
|
||||
|
||||
#include "base/base_switches.h"
|
||||
#include "base/basictypes.h"
|
||||
#include "base/debug_util.h"
|
||||
|
||||
// Minimalist implementation to try to find a command line argument. We can use
|
||||
// kernel32 exported functions but not the CRT functions because we're too early
|
||||
// in the process startup.
|
||||
// The code is not that bright and will find things like ---argument or
|
||||
// /-/argument.
|
||||
// Note: command_line is non-destructively modified.
|
||||
bool DebugOnStart::FindArgument(wchar_t* command_line, const wchar_t* argument)
|
||||
{
|
||||
int argument_len = lstrlen(argument);
|
||||
int command_line_len = lstrlen(command_line);
|
||||
while (command_line_len > argument_len) {
|
||||
wchar_t first_char = command_line[0];
|
||||
wchar_t last_char = command_line[argument_len+1];
|
||||
// Try to find an argument.
|
||||
if ((first_char == L'-' || first_char == L'/') &&
|
||||
(last_char == L' ' || last_char == 0 || last_char == L'=')) {
|
||||
command_line[argument_len+1] = 0;
|
||||
// Skip the - or /
|
||||
if (lstrcmpi(command_line+1, argument) == 0) {
|
||||
// Found it.
|
||||
command_line[argument_len+1] = last_char;
|
||||
return true;
|
||||
}
|
||||
// Fix back.
|
||||
command_line[argument_len+1] = last_char;
|
||||
}
|
||||
// Continue searching.
|
||||
++command_line;
|
||||
--command_line_len;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
int __cdecl DebugOnStart::Init() {
|
||||
// Try to find the argument.
|
||||
if (FindArgument(GetCommandLine(), switches::kDebugOnStart)) {
|
||||
// We can do 2 things here:
|
||||
// - Ask for a debugger to attach to us. This involve reading the registry
|
||||
// key and creating the process.
|
||||
// - Do a int3.
|
||||
|
||||
// It will fails if we run in a sandbox. That is expected.
|
||||
DebugUtil::SpawnDebuggerOnProcess(GetCurrentProcessId());
|
||||
|
||||
// Wait for a debugger to come take us.
|
||||
DebugUtil::WaitForDebugger(60, false);
|
||||
} else if (FindArgument(GetCommandLine(), switches::kWaitForDebugger)) {
|
||||
// Wait for a debugger to come take us.
|
||||
DebugUtil::WaitForDebugger(60, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Define the necessary code and global data to look for kDebugOnStart command
|
||||
// line argument. When the command line argument is detected, it invokes the
|
||||
// debugger, if no system-wide debugger is registered, a debug break is done.
|
||||
|
||||
#ifndef BASE_DEBUG_ON_START_H_
|
||||
#define BASE_DEBUG_ON_START_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
// This only works on Windows.
|
||||
#if defined(OS_WIN)
|
||||
|
||||
#ifndef DECLSPEC_SELECTANY
|
||||
#define DECLSPEC_SELECTANY __declspec(selectany)
|
||||
#endif
|
||||
|
||||
// Debug on start functions and data.
|
||||
class DebugOnStart {
|
||||
public:
|
||||
// Expected function type in the .CRT$XI* section.
|
||||
// Note: See VC\crt\src\internal.h for reference.
|
||||
typedef int (__cdecl *PIFV)(void);
|
||||
|
||||
// Looks at the command line for kDebugOnStart argument. If found, it invokes
|
||||
// the debugger, if this fails, it crashes.
|
||||
static int __cdecl Init();
|
||||
|
||||
// Returns true if the 'argument' is present in the 'command_line'. It does
|
||||
// not use the CRT, only Kernel32 functions.
|
||||
static bool FindArgument(wchar_t* command_line, const wchar_t* argument);
|
||||
};
|
||||
|
||||
// Set the function pointer to our function to look for a crash on start. The
|
||||
// XIB section is started pretty early in the program initialization so in
|
||||
// theory it should be called before any user created global variable
|
||||
// initialization code and CRT initialization code.
|
||||
// Note: See VC\crt\src\defsects.inc and VC\crt\src\crt0.c for reference.
|
||||
#ifdef _WIN64
|
||||
|
||||
// "Fix" the segment. On x64, the .CRT segment is merged into the .rdata segment
|
||||
// so it contains const data only.
|
||||
#pragma const_seg(push, ".CRT$XIB")
|
||||
// Declare the pointer so the CRT will find it.
|
||||
extern const DebugOnStart::PIFV debug_on_start;
|
||||
DECLSPEC_SELECTANY const DebugOnStart::PIFV debug_on_start =
|
||||
&DebugOnStart::Init;
|
||||
// Fix back the segment.
|
||||
#pragma const_seg(pop)
|
||||
|
||||
#else // _WIN64
|
||||
|
||||
// "Fix" the segment. On x86, the .CRT segment is merged into the .data segment
|
||||
// so it contains non-const data only.
|
||||
#pragma data_seg(push, ".CRT$XIB")
|
||||
// Declare the pointer so the CRT will find it.
|
||||
DECLSPEC_SELECTANY DebugOnStart::PIFV debug_on_start = &DebugOnStart::Init;
|
||||
// Fix back the segment.
|
||||
#pragma data_seg(pop)
|
||||
|
||||
#endif // _WIN64
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
#endif // BASE_DEBUG_ON_START_H_
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/debug_util.h"
|
||||
|
||||
#include "base/platform_thread.h"
|
||||
|
||||
bool DebugUtil::WaitForDebugger(int wait_seconds, bool silent) {
|
||||
for (int i = 0; i < wait_seconds * 10; ++i) {
|
||||
if (BeingDebugged()) {
|
||||
if (!silent)
|
||||
BreakDebugger();
|
||||
return true;
|
||||
}
|
||||
PlatformThread::Sleep(100);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *const *StackTrace::Addresses(size_t* count) {
|
||||
*count = trace_.size();
|
||||
if (trace_.size())
|
||||
return &trace_[0];
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This is a cross platform interface for helper functions related to debuggers.
|
||||
// You should use this to test if you're running under a debugger, and if you
|
||||
// would like to yield (breakpoint) into the debugger.
|
||||
|
||||
#ifndef BASE_DEBUG_UTIL_H_
|
||||
#define BASE_DEBUG_UTIL_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
// A stacktrace can be helpful in debugging. For example, you can include a
|
||||
// stacktrace member in a object (probably around #ifndef NDEBUG) so that you
|
||||
// can later see where the given object was created from.
|
||||
class StackTrace {
|
||||
public:
|
||||
// Create a stacktrace from the current location
|
||||
StackTrace();
|
||||
// Get an array of instruction pointer values.
|
||||
// count: (output) the number of elements in the returned array
|
||||
const void *const *Addresses(size_t* count);
|
||||
// Print a backtrace to stderr
|
||||
void PrintBacktrace();
|
||||
|
||||
// Resolve backtrace to symbols and write to stream.
|
||||
void OutputToStream(std::ostream* os);
|
||||
|
||||
private:
|
||||
std::vector<void*> trace_;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(StackTrace);
|
||||
};
|
||||
|
||||
class DebugUtil {
|
||||
public:
|
||||
// Starts the registered system-wide JIT debugger to attach it to specified
|
||||
// process.
|
||||
static bool SpawnDebuggerOnProcess(unsigned process_id);
|
||||
|
||||
// Waits wait_seconds seconds for a debugger to attach to the current process.
|
||||
// When silent is false, an exception is thrown when a debugger is detected.
|
||||
static bool WaitForDebugger(int wait_seconds, bool silent);
|
||||
|
||||
// Are we running under a debugger?
|
||||
// On OS X, the underlying mechanism doesn't work when the sandbox is enabled.
|
||||
// To get around this, this function caches its value.
|
||||
// WARNING: Because of this, on OS X, a call MUST be made to this function
|
||||
// BEFORE the sandbox is enabled.
|
||||
static bool BeingDebugged();
|
||||
|
||||
// Break into the debugger, assumes a debugger is present.
|
||||
static void BreakDebugger();
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
// On OS X, it can take a really long time for the OS Crash handler to
|
||||
// process a Chrome crash. This translates into a long wait till the process
|
||||
// actually dies.
|
||||
// This method disables OS Crash reporting entireley.
|
||||
// TODO(playmobil): Remove this when we have Breakpad integration enabled -
|
||||
// see http://crbug.com/7652
|
||||
static void DisableOSCrashDumps();
|
||||
#endif // defined(OS_MACOSX)
|
||||
};
|
||||
|
||||
#endif // BASE_DEBUG_UTIL_H_
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/debug_util.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
static void ExitSignalHandler(int sig) {
|
||||
exit(128 + sig);
|
||||
}
|
||||
|
||||
// static
|
||||
void DebugUtil::DisableOSCrashDumps() {
|
||||
int signals_to_intercept[] ={SIGINT,
|
||||
SIGHUP,
|
||||
SIGTERM,
|
||||
SIGABRT,
|
||||
SIGILL,
|
||||
SIGTRAP,
|
||||
SIGEMT,
|
||||
SIGFPE,
|
||||
SIGBUS,
|
||||
SIGSEGV,
|
||||
SIGSYS,
|
||||
SIGPIPE,
|
||||
SIGXCPU,
|
||||
SIGXFSZ};
|
||||
// For all these signals, just wire thing sup so we exit immediately.
|
||||
for (size_t i = 0; i < arraysize(signals_to_intercept); ++i) {
|
||||
signal(signals_to_intercept[i], ExitSignalHandler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "base/debug_util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <execinfo.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/eintr_wrapper.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/string_piece.h"
|
||||
|
||||
// static
|
||||
bool DebugUtil::SpawnDebuggerOnProcess(unsigned /* process_id */) {
|
||||
NOTIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
|
||||
// Based on Apple's recommended method as described in
|
||||
// http://developer.apple.com/qa/qa2004/qa1361.html
|
||||
// static
|
||||
bool DebugUtil::BeingDebugged() {
|
||||
// If the process is sandboxed then we can't use the sysctl, so cache the
|
||||
// value.
|
||||
static bool is_set = false;
|
||||
static bool being_debugged = false;
|
||||
|
||||
if (is_set) {
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
// Initialize mib, which tells sysctl what info we want. In this case,
|
||||
// we're looking for information about a specific process ID.
|
||||
int mib[] = {
|
||||
CTL_KERN,
|
||||
KERN_PROC,
|
||||
KERN_PROC_PID,
|
||||
getpid()
|
||||
};
|
||||
|
||||
// Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and
|
||||
// binary interfaces may change.
|
||||
struct kinfo_proc info;
|
||||
size_t info_size = sizeof(info);
|
||||
|
||||
int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0);
|
||||
DCHECK(sysctl_result == 0);
|
||||
if (sysctl_result != 0) {
|
||||
is_set = true;
|
||||
being_debugged = false;
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
// This process is being debugged if the P_TRACED flag is set.
|
||||
is_set = true;
|
||||
being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX)
|
||||
|
||||
// We can look in /proc/self/status for TracerPid. We are likely used in crash
|
||||
// handling, so we are careful not to use the heap or have side effects.
|
||||
// Another option that is common is to try to ptrace yourself, but then we
|
||||
// can't detach without forking(), and that's not so great.
|
||||
// static
|
||||
bool DebugUtil::BeingDebugged() {
|
||||
int status_fd = open("/proc/self/status", O_RDONLY);
|
||||
if (status_fd == -1)
|
||||
return false;
|
||||
|
||||
// We assume our line will be in the first 1024 characters and that we can
|
||||
// read this much all at once. In practice this will generally be true.
|
||||
// This simplifies and speeds up things considerably.
|
||||
char buf[1024];
|
||||
|
||||
ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf)));
|
||||
HANDLE_EINTR(close(status_fd));
|
||||
|
||||
if (num_read <= 0)
|
||||
return false;
|
||||
|
||||
StringPiece status(buf, num_read);
|
||||
StringPiece tracer("TracerPid:\t");
|
||||
|
||||
StringPiece::size_type pid_index = status.find(tracer);
|
||||
if (pid_index == StringPiece::npos)
|
||||
return false;
|
||||
|
||||
// Our pid is 0 without a debugger, assume this for any pid starting with 0.
|
||||
pid_index += tracer.size();
|
||||
return pid_index < status.size() && status[pid_index] != '0';
|
||||
}
|
||||
|
||||
#endif // OS_LINUX
|
||||
|
||||
// static
|
||||
void DebugUtil::BreakDebugger() {
|
||||
#if !defined(ARCH_CPU_ARM_FAMILY)
|
||||
asm ("int3");
|
||||
#endif
|
||||
}
|
||||
|
||||
StackTrace::StackTrace() {
|
||||
const int kMaxCallers = 256;
|
||||
|
||||
void* callers[kMaxCallers];
|
||||
int count = backtrace(callers, kMaxCallers);
|
||||
|
||||
// Though the backtrace API man page does not list any possible negative
|
||||
// return values, we still still exclude them because they would break the
|
||||
// memcpy code below.
|
||||
if (count > 0) {
|
||||
trace_.resize(count);
|
||||
memcpy(&trace_[0], callers, sizeof(callers[0]) * count);
|
||||
} else {
|
||||
trace_.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
void StackTrace::PrintBacktrace() {
|
||||
fflush(stderr);
|
||||
backtrace_symbols_fd(&trace_[0], trace_.size(), STDERR_FILENO);
|
||||
}
|
||||
|
||||
void StackTrace::OutputToStream(std::ostream* os) {
|
||||
scoped_ptr_malloc<char*> trace_symbols(
|
||||
backtrace_symbols(&trace_[0], trace_.size()));
|
||||
|
||||
// If we can't retrieve the symbols, print an error and just dump the raw
|
||||
// addresses.
|
||||
if (trace_symbols.get() == NULL) {
|
||||
(*os) << "Unable get symbols for backtrace (" << strerror(errno)
|
||||
<< "). Dumping raw addresses in trace:\n";
|
||||
for (size_t i = 0; i < trace_.size(); ++i) {
|
||||
(*os) << "\t" << trace_[i] << "\n";
|
||||
}
|
||||
} else {
|
||||
(*os) << "Backtrace:\n";
|
||||
for (size_t i = 0; i < trace_.size(); ++i) {
|
||||
(*os) << "\t" << trace_symbols.get()[i] << "\n";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "base/debug_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
TEST(StackTrace, OutputToStream) {
|
||||
StackTrace trace;
|
||||
|
||||
// Dump the trace into a string.
|
||||
std::ostringstream os;
|
||||
trace.OutputToStream(&os);
|
||||
std::string backtrace_message = os.str();
|
||||
|
||||
size_t frames_found = 0;
|
||||
trace.Addresses(&frames_found);
|
||||
if (frames_found == 0) {
|
||||
LOG(ERROR) << "No stack frames found. Skipping rest of test.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the output has symbol initialization warning. If it does, fail.
|
||||
if (backtrace_message.find("Dumping unresolved backtrace") !=
|
||||
std::string::npos) {
|
||||
LOG(ERROR) << "Unable to resolve symbols. Skipping rest of test.";
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//TODO(ajwong): Disabling checking of symbol resolution since it depends
|
||||
// on whether or not symbols are present, and there are too many
|
||||
// configurations to reliably ensure that symbols are findable.
|
||||
#if defined(OS_MACOSX)
|
||||
|
||||
// Symbol resolution via the backtrace_symbol funciton does not work well
|
||||
// in OsX.
|
||||
// See this thread:
|
||||
//
|
||||
// http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html
|
||||
//
|
||||
// Just check instead that we find our way back to the "start" symbol
|
||||
// which should be the first symbol in the trace.
|
||||
//
|
||||
// TODO(port): Find a more reliable way to resolve symbols.
|
||||
|
||||
// Expect to at least find main.
|
||||
EXPECT_TRUE(backtrace_message.find("start") != std::string::npos)
|
||||
<< "Expected to find start in backtrace:\n"
|
||||
<< backtrace_message;
|
||||
|
||||
#else // defined(OS_MACOSX)
|
||||
|
||||
// Expect to at least find main.
|
||||
EXPECT_TRUE(backtrace_message.find("main") != std::string::npos)
|
||||
<< "Expected to find main in backtrace:\n"
|
||||
<< backtrace_message;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with
|
||||
// MSVC's __FUNCTION__ macro.
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
// Expect to find this function as well.
|
||||
// Note: This will fail if not linked with -rdynamic (aka -export_dynamic)
|
||||
EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos)
|
||||
<< "Expected to find " << __func__ << " in backtrace:\n"
|
||||
<< backtrace_message;
|
||||
|
||||
#endif // define(OS_MACOSX)
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/debug_util.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/lock.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/singleton.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Minimalist key reader.
|
||||
// Note: Does not use the CRT.
|
||||
bool RegReadString(HKEY root, const wchar_t* subkey,
|
||||
const wchar_t* value_name, wchar_t* buffer, int* len) {
|
||||
HKEY key = NULL;
|
||||
DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key);
|
||||
if (ERROR_SUCCESS != res || key == NULL)
|
||||
return false;
|
||||
|
||||
DWORD type = 0;
|
||||
DWORD buffer_size = *len * sizeof(wchar_t);
|
||||
// We don't support REG_EXPAND_SZ.
|
||||
res = RegQueryValueEx(key, value_name, NULL, &type,
|
||||
reinterpret_cast<BYTE*>(buffer), &buffer_size);
|
||||
if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) {
|
||||
// Make sure the buffer is NULL terminated.
|
||||
buffer[*len - 1] = 0;
|
||||
*len = lstrlen(buffer);
|
||||
RegCloseKey(key);
|
||||
return true;
|
||||
}
|
||||
RegCloseKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replaces each "%ld" in input per a value. Not efficient but it works.
|
||||
// Note: Does not use the CRT.
|
||||
bool StringReplace(const wchar_t* input, int value, wchar_t* output,
|
||||
int output_len) {
|
||||
memset(output, 0, output_len*sizeof(wchar_t));
|
||||
int input_len = lstrlen(input);
|
||||
|
||||
for (int i = 0; i < input_len; ++i) {
|
||||
int current_output_len = lstrlen(output);
|
||||
|
||||
if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') {
|
||||
// Make sure we have enough place left.
|
||||
if ((current_output_len + 12) >= output_len)
|
||||
return false;
|
||||
|
||||
// Cheap _itow().
|
||||
wsprintf(output+current_output_len, L"%d", value);
|
||||
i += 2;
|
||||
} else {
|
||||
if (current_output_len >= output_len)
|
||||
return false;
|
||||
output[current_output_len] = input[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
|
||||
// of functions. The Sym* family of functions may only be invoked by one
|
||||
// thread at a time. SymbolContext code may access a symbol server over the
|
||||
// network while holding the lock for this singleton. In the case of high
|
||||
// latency, this code will adversly affect performance.
|
||||
//
|
||||
// There is also a known issue where this backtrace code can interact
|
||||
// badly with breakpad if breakpad is invoked in a separate thread while
|
||||
// we are using the Sym* functions. This is because breakpad does now
|
||||
// share a lock with this function. See this related bug:
|
||||
//
|
||||
// http://code.google.com/p/google-breakpad/issues/detail?id=311
|
||||
//
|
||||
// This is a very unlikely edge case, and the current solution is to
|
||||
// just ignore it.
|
||||
class SymbolContext {
|
||||
public:
|
||||
static SymbolContext* Get() {
|
||||
// We use a leaky singleton because code may call this during process
|
||||
// termination.
|
||||
return
|
||||
Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
|
||||
}
|
||||
|
||||
// Initializes the symbols for the process if it hasn't been done yet.
|
||||
// Subsequent calls will not reinitialize the symbol, but instead return
|
||||
// the error code from the first call.
|
||||
bool Init() {
|
||||
AutoLock lock(lock_);
|
||||
if (!initialized_) {
|
||||
process_ = GetCurrentProcess();
|
||||
|
||||
// Defer symbol load until they're needed, use undecorated names, and
|
||||
// get line numbers.
|
||||
SymSetOptions(SYMOPT_DEFERRED_LOADS |
|
||||
SYMOPT_UNDNAME |
|
||||
SYMOPT_LOAD_LINES);
|
||||
if (SymInitialize(process_, NULL, TRUE)) {
|
||||
init_error_ = ERROR_SUCCESS;
|
||||
} else {
|
||||
init_error_ = GetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return init_error_ == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns the error code of a failed initialization. This should only be
|
||||
// called if Init() has been called. We do not LOG(FATAL) here because
|
||||
// this code is called might be triggered by a LOG(FATAL) itself. Instead,
|
||||
// we log an ERROR, and return ERROR_INVALID_DATA.
|
||||
DWORD init_error() {
|
||||
if (!initialized_) {
|
||||
LOG(ERROR) << "Calling GetInitError() before Init() was called. "
|
||||
<< "Returning ERROR_INVALID_DATA.";
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
return init_error_;
|
||||
}
|
||||
|
||||
// Returns the process this was initialized for. This should only be
|
||||
// called if Init() has been called. We LOG(ERROR) in this situation.
|
||||
// LOG(FATAL) is not used because this code is might be triggered
|
||||
// by a LOG(FATAL) itself.
|
||||
HANDLE process() {
|
||||
if (!initialized_) {
|
||||
LOG(ERROR) << "Calling process() before Init() was called. "
|
||||
<< "Returning NULL.";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return process_;
|
||||
}
|
||||
|
||||
// For the given trace, attempts to resolve the symbols, and output a trace
|
||||
// to the ostream os. The format for each line of the backtrace is:
|
||||
//
|
||||
// <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
|
||||
//
|
||||
// This function should only be called if Init() has been called. We do not
|
||||
// LOG(FATAL) here because this code is called might be triggered by a
|
||||
// LOG(FATAL) itself.
|
||||
void OutputTraceToStream(const std::vector<void*>& trace, std::ostream* os) {
|
||||
AutoLock lock(lock_);
|
||||
|
||||
for (size_t i = 0; (i < trace.size()) && os->good(); ++i) {
|
||||
const int kMaxNameLength = 256;
|
||||
DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
|
||||
|
||||
// Code adapted from MSDN example:
|
||||
// http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
|
||||
ULONG64 buffer[
|
||||
(sizeof(SYMBOL_INFO) +
|
||||
kMaxNameLength * sizeof(wchar_t) +
|
||||
sizeof(ULONG64) - 1) /
|
||||
sizeof(ULONG64)];
|
||||
|
||||
// Initialize symbol information retrieval structures.
|
||||
DWORD64 sym_displacement = 0;
|
||||
PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = kMaxNameLength;
|
||||
BOOL has_symbol = SymFromAddr(process(), frame,
|
||||
&sym_displacement, symbol);
|
||||
|
||||
// Attempt to retrieve line number information.
|
||||
DWORD line_displacement = 0;
|
||||
IMAGEHLP_LINE64 line = {};
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
BOOL has_line = SymGetLineFromAddr64(process(), frame,
|
||||
&line_displacement, &line);
|
||||
|
||||
// Output the backtrace line.
|
||||
(*os) << "\t";
|
||||
if (has_symbol) {
|
||||
(*os) << symbol->Name << " [0x" << trace[i] << "+"
|
||||
<< sym_displacement << "]";
|
||||
} else {
|
||||
// If there is no symbol informtion, add a spacer.
|
||||
(*os) << "(No symbol) [0x" << trace[i] << "]";
|
||||
}
|
||||
if (has_line) {
|
||||
(*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
|
||||
}
|
||||
(*os) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
SymbolContext()
|
||||
: initialized_(false),
|
||||
process_(NULL),
|
||||
init_error_(ERROR_SUCCESS) {
|
||||
}
|
||||
|
||||
private:
|
||||
Lock lock_;
|
||||
bool initialized_;
|
||||
HANDLE process_;
|
||||
DWORD init_error_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SymbolContext);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Note: Does not use the CRT.
|
||||
bool DebugUtil::SpawnDebuggerOnProcess(unsigned process_id) {
|
||||
wchar_t reg_value[1026];
|
||||
int len = arraysize(reg_value);
|
||||
if (RegReadString(HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug",
|
||||
L"Debugger", reg_value, &len)) {
|
||||
wchar_t command_line[1026];
|
||||
if (StringReplace(reg_value, process_id, command_line,
|
||||
arraysize(command_line))) {
|
||||
// We don't mind if the debugger is present because it will simply fail
|
||||
// to attach to this process.
|
||||
STARTUPINFO startup_info = {0};
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
PROCESS_INFORMATION process_info = {0};
|
||||
|
||||
if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL,
|
||||
&startup_info, &process_info)) {
|
||||
CloseHandle(process_info.hThread);
|
||||
WaitForInputIdle(process_info.hProcess, 10000);
|
||||
CloseHandle(process_info.hProcess);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool DebugUtil::BeingDebugged() {
|
||||
return ::IsDebuggerPresent() != 0;
|
||||
}
|
||||
|
||||
// static
|
||||
void DebugUtil::BreakDebugger() {
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
StackTrace::StackTrace() {
|
||||
// From http://msdn.microsoft.com/en-us/library/bb204633(VS.85).aspx,
|
||||
// the sum of FramesToSkip and FramesToCapture must be less than 63,
|
||||
// so set it to 62.
|
||||
const int kMaxCallers = 62;
|
||||
|
||||
void* callers[kMaxCallers];
|
||||
// TODO(ajwong): Migrate this to StackWalk64.
|
||||
int count = CaptureStackBackTrace(0, kMaxCallers, callers, NULL);
|
||||
if (count > 0) {
|
||||
trace_.resize(count);
|
||||
memcpy(&trace_[0], callers, sizeof(callers[0]) * count);
|
||||
} else {
|
||||
trace_.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
void StackTrace::PrintBacktrace() {
|
||||
OutputToStream(&std::cerr);
|
||||
}
|
||||
|
||||
void StackTrace::OutputToStream(std::ostream* os) {
|
||||
SymbolContext* context = SymbolContext::Get();
|
||||
|
||||
if (context->Init() != ERROR_SUCCESS) {
|
||||
DWORD error = context->init_error();
|
||||
(*os) << "Error initializing symbols (" << error
|
||||
<< "). Dumping unresolved backtrace:\n";
|
||||
for (size_t i = 0; (i < trace_.size()) && os->good(); ++i) {
|
||||
(*os) << "\t" << trace_[i] << "\n";
|
||||
}
|
||||
} else {
|
||||
(*os) << "Backtrace:\n";
|
||||
context->OutputTraceToStream(trace_, os);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This module provides a way to monitor a directory for changes.
|
||||
|
||||
#ifndef BASE_DIRECTORY_WATCHER_H_
|
||||
#define BASE_DIRECTORY_WATCHER_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/ref_counted.h"
|
||||
|
||||
class FilePath;
|
||||
|
||||
// This class lets you register interest in changes on a directory.
|
||||
// The delegate will get called whenever a file is added or changed in the
|
||||
// directory.
|
||||
class DirectoryWatcher {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void OnDirectoryChanged(const FilePath& path) = 0;
|
||||
};
|
||||
|
||||
DirectoryWatcher();
|
||||
~DirectoryWatcher() {}
|
||||
|
||||
// Register interest in any changes in the directory |path|.
|
||||
// OnDirectoryChanged will be called back for each change within the dir.
|
||||
// If |recursive| is true, the delegate will be notified for each change
|
||||
// within the directory tree starting at |path|. Returns false on error.
|
||||
//
|
||||
// Note: on Windows you may got more notifications for non-recursive watch
|
||||
// than you expect, especially on versions earlier than Vista. The behavior
|
||||
// is consistent on any particular version of Windows, but not across
|
||||
// different versions.
|
||||
bool Watch(const FilePath& path, Delegate* delegate, bool recursive) {
|
||||
return impl_->Watch(path, delegate, recursive);
|
||||
}
|
||||
|
||||
// Used internally to encapsulate different members on different platforms.
|
||||
class PlatformDelegate : public base::RefCounted<PlatformDelegate> {
|
||||
public:
|
||||
virtual ~PlatformDelegate() {}
|
||||
virtual bool Watch(const FilePath& path, Delegate* delegate,
|
||||
bool recursive) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
scoped_refptr<PlatformDelegate> impl_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DirectoryWatcher);
|
||||
};
|
||||
|
||||
#endif // BASE_DIRECTORY_WATCHER_H_
|
|
@ -0,0 +1,321 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/directory_watcher.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/eintr_wrapper.h"
|
||||
#include "base/file_path.h"
|
||||
#include "base/hash_tables.h"
|
||||
#include "base/lock.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/singleton.h"
|
||||
#include "base/task.h"
|
||||
#include "base/thread.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Singleton to manage all inotify watches.
|
||||
class InotifyReader {
|
||||
public:
|
||||
typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
|
||||
static const Watch kInvalidWatch = -1;
|
||||
|
||||
// Watch |path| for changes. |delegate| will be notified on each change. Does
|
||||
// not check for duplicates. If you call it n times with same |path|
|
||||
// and |delegate|, it will receive n notifications for each change
|
||||
// in |path|. It makes implementation of DirectoryWatcher simple.
|
||||
// Returns kInvalidWatch on failure.
|
||||
Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate);
|
||||
|
||||
// Remove |watch| for |delegate|. If you had n watches for same |delegate|
|
||||
// and path, after calling this function you will have n - 1.
|
||||
// Returns true on success.
|
||||
bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
|
||||
|
||||
// Callback for InotifyReaderTask.
|
||||
void OnInotifyEvent(inotify_event* event);
|
||||
|
||||
private:
|
||||
friend struct DefaultSingletonTraits<InotifyReader>;
|
||||
|
||||
typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo;
|
||||
typedef std::multiset<DelegateInfo> DelegateSet;
|
||||
|
||||
InotifyReader();
|
||||
~InotifyReader();
|
||||
|
||||
// We keep track of which delegates want to be notified on which watches.
|
||||
// Multiset is used because there may be many DirectoryWatchers for same path
|
||||
// and delegate.
|
||||
base::hash_map<Watch, DelegateSet> delegates_;
|
||||
|
||||
// For each watch we also want to know the path it's watching.
|
||||
base::hash_map<Watch, FilePath> paths_;
|
||||
|
||||
// Lock to protect delegates_ and paths_.
|
||||
Lock lock_;
|
||||
|
||||
// Separate thread on which we run blocking read for inotify events.
|
||||
base::Thread thread_;
|
||||
|
||||
// File descriptor returned by inotify_init.
|
||||
const int inotify_fd_;
|
||||
|
||||
// Use self-pipe trick to unblock select during shutdown.
|
||||
int shutdown_pipe_[2];
|
||||
|
||||
// Flag set to true when startup was successful.
|
||||
bool valid_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InotifyReader);
|
||||
};
|
||||
|
||||
class InotifyReaderTask : public Task {
|
||||
public:
|
||||
InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
|
||||
: reader_(reader),
|
||||
inotify_fd_(inotify_fd),
|
||||
shutdown_fd_(shutdown_fd) {
|
||||
}
|
||||
|
||||
virtual void Run() {
|
||||
while (true) {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(inotify_fd_, &rfds);
|
||||
FD_SET(shutdown_fd_, &rfds);
|
||||
|
||||
// Wait until some inotify events are available.
|
||||
int select_result =
|
||||
HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1,
|
||||
&rfds, NULL, NULL, NULL));
|
||||
if (select_result < 0) {
|
||||
DLOG(WARNING) << "select failed: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FD_ISSET(shutdown_fd_, &rfds))
|
||||
return;
|
||||
|
||||
// Adjust buffer size to current event queue size.
|
||||
int buffer_size;
|
||||
int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD,
|
||||
&buffer_size));
|
||||
|
||||
if (ioctl_result != 0) {
|
||||
DLOG(WARNING) << "ioctl failed: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<char> buffer(buffer_size);
|
||||
|
||||
ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0],
|
||||
buffer_size));
|
||||
|
||||
if (bytes_read < 0) {
|
||||
DLOG(WARNING) << "read from inotify fd failed: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t i = 0;
|
||||
while (i < bytes_read) {
|
||||
inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
|
||||
size_t event_size = sizeof(inotify_event) + event->len;
|
||||
DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
|
||||
reader_->OnInotifyEvent(event);
|
||||
i += event_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
InotifyReader* reader_;
|
||||
int inotify_fd_;
|
||||
int shutdown_fd_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
|
||||
};
|
||||
|
||||
class InotifyReaderNotifyTask : public Task {
|
||||
public:
|
||||
InotifyReaderNotifyTask(DirectoryWatcher::Delegate* delegate,
|
||||
const FilePath& path)
|
||||
: delegate_(delegate),
|
||||
path_(path) {
|
||||
}
|
||||
|
||||
virtual void Run() {
|
||||
delegate_->OnDirectoryChanged(path_);
|
||||
}
|
||||
|
||||
private:
|
||||
DirectoryWatcher::Delegate* delegate_;
|
||||
FilePath path_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InotifyReaderNotifyTask);
|
||||
};
|
||||
|
||||
InotifyReader::InotifyReader()
|
||||
: thread_("inotify_reader"),
|
||||
inotify_fd_(inotify_init()),
|
||||
valid_(false) {
|
||||
shutdown_pipe_[0] = -1;
|
||||
shutdown_pipe_[1] = -1;
|
||||
if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
|
||||
thread_.message_loop()->PostTask(
|
||||
FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
|
||||
valid_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
InotifyReader::~InotifyReader() {
|
||||
if (valid_) {
|
||||
// Write to the self-pipe so that the select call in InotifyReaderTask
|
||||
// returns.
|
||||
HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
|
||||
thread_.Stop();
|
||||
}
|
||||
if (inotify_fd_ >= 0)
|
||||
close(inotify_fd_);
|
||||
if (shutdown_pipe_[0] >= 0)
|
||||
close(shutdown_pipe_[0]);
|
||||
if (shutdown_pipe_[1] >= 0)
|
||||
close(shutdown_pipe_[1]);
|
||||
}
|
||||
|
||||
InotifyReader::Watch InotifyReader::AddWatch(
|
||||
const FilePath& path, DirectoryWatcher::Delegate* delegate) {
|
||||
if (!valid_)
|
||||
return kInvalidWatch;
|
||||
|
||||
AutoLock auto_lock(lock_);
|
||||
|
||||
Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
|
||||
IN_CREATE | IN_DELETE |
|
||||
IN_CLOSE_WRITE | IN_MOVE);
|
||||
if (watch == kInvalidWatch)
|
||||
return kInvalidWatch;
|
||||
|
||||
if (paths_[watch].empty())
|
||||
paths_[watch] = path; // We don't yet watch this path.
|
||||
|
||||
delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current()));
|
||||
|
||||
return watch;
|
||||
}
|
||||
|
||||
bool InotifyReader::RemoveWatch(Watch watch,
|
||||
DirectoryWatcher::Delegate* delegate) {
|
||||
if (!valid_)
|
||||
return false;
|
||||
|
||||
AutoLock auto_lock(lock_);
|
||||
|
||||
if (paths_[watch].empty())
|
||||
return false; // We don't recognize this watch.
|
||||
|
||||
// Only erase one occurrence of delegate (there may be more).
|
||||
delegates_[watch].erase(
|
||||
delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
|
||||
|
||||
if (delegates_[watch].empty()) {
|
||||
paths_.erase(watch);
|
||||
delegates_.erase(watch);
|
||||
|
||||
return (inotify_rm_watch(inotify_fd_, watch) == 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InotifyReader::OnInotifyEvent(inotify_event* event) {
|
||||
if (event->mask & IN_IGNORED)
|
||||
return;
|
||||
|
||||
DelegateSet delegates_to_notify;
|
||||
FilePath changed_path;
|
||||
|
||||
{
|
||||
AutoLock auto_lock(lock_);
|
||||
changed_path = paths_[event->wd];
|
||||
delegates_to_notify.insert(delegates_[event->wd].begin(),
|
||||
delegates_[event->wd].end());
|
||||
}
|
||||
|
||||
DelegateSet::iterator i;
|
||||
for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) {
|
||||
DirectoryWatcher::Delegate* delegate = i->first;
|
||||
MessageLoop* loop = i->second;
|
||||
loop->PostTask(FROM_HERE,
|
||||
new InotifyReaderNotifyTask(delegate, changed_path));
|
||||
}
|
||||
}
|
||||
|
||||
class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
||||
public:
|
||||
DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {}
|
||||
~DirectoryWatcherImpl();
|
||||
|
||||
virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
||||
bool recursive);
|
||||
|
||||
private:
|
||||
// Delegate to notify upon changes.
|
||||
DirectoryWatcher::Delegate* delegate_;
|
||||
|
||||
// Path we're watching (passed to delegate).
|
||||
FilePath path_;
|
||||
|
||||
// Watch returned by InotifyReader.
|
||||
InotifyReader::Watch watch_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
||||
};
|
||||
|
||||
DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
||||
if (watch_ != InotifyReader::kInvalidWatch)
|
||||
Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
|
||||
}
|
||||
|
||||
bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
||||
DirectoryWatcher::Delegate* delegate, bool recursive) {
|
||||
DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
|
||||
|
||||
if (recursive) {
|
||||
// TODO(phajdan.jr): Support recursive watches.
|
||||
// Unfortunately inotify has no "native" support for them, but it can be
|
||||
// emulated by walking the directory tree and setting up watches for each
|
||||
// directory. Of course this is ineffective for large directory trees.
|
||||
// For the algorithm, see the link below:
|
||||
// http://osdir.com/ml/gnome.dashboard.devel/2004-10/msg00022.html
|
||||
NOTIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
delegate_ = delegate;
|
||||
path_ = path;
|
||||
watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_);
|
||||
|
||||
return watch_ != InotifyReader::kInvalidWatch;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DirectoryWatcher::DirectoryWatcher() {
|
||||
impl_ = new DirectoryWatcherImpl();
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/directory_watcher.h"
|
||||
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/scoped_cftyperef.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const CFAbsoluteTime kEventLatencySeconds = 0.3;
|
||||
|
||||
class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
||||
public:
|
||||
DirectoryWatcherImpl() {}
|
||||
~DirectoryWatcherImpl() {
|
||||
if (!path_.value().empty()) {
|
||||
FSEventStreamStop(fsevent_stream_);
|
||||
FSEventStreamInvalidate(fsevent_stream_);
|
||||
FSEventStreamRelease(fsevent_stream_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
||||
bool recursive);
|
||||
|
||||
void OnFSEventsCallback(const FilePath& event_path) {
|
||||
DCHECK(!path_.value().empty());
|
||||
if (!recursive_) {
|
||||
FilePath absolute_event_path = event_path;
|
||||
if (!file_util::AbsolutePath(&absolute_event_path))
|
||||
return;
|
||||
if (absolute_event_path != path_)
|
||||
return;
|
||||
}
|
||||
delegate_->OnDirectoryChanged(path_);
|
||||
}
|
||||
|
||||
private:
|
||||
// Delegate to notify upon changes.
|
||||
DirectoryWatcher::Delegate* delegate_;
|
||||
|
||||
// Path we're watching (passed to delegate).
|
||||
FilePath path_;
|
||||
|
||||
// Indicates recursive watch.
|
||||
bool recursive_;
|
||||
|
||||
// Backend stream we receive event callbacks from (strong reference).
|
||||
FSEventStreamRef fsevent_stream_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
||||
};
|
||||
|
||||
void FSEventsCallback(ConstFSEventStreamRef stream,
|
||||
void* event_watcher, size_t num_events,
|
||||
void* event_paths, const FSEventStreamEventFlags flags[],
|
||||
const FSEventStreamEventId event_ids[]) {
|
||||
char** paths = reinterpret_cast<char**>(event_paths);
|
||||
DirectoryWatcherImpl* watcher =
|
||||
reinterpret_cast<DirectoryWatcherImpl*> (event_watcher);
|
||||
for (size_t i = 0; i < num_events; i++) {
|
||||
watcher->OnFSEventsCallback(FilePath(paths[i]));
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
||||
DirectoryWatcher::Delegate* delegate,
|
||||
bool recursive) {
|
||||
DCHECK(path_.value().empty()); // Can only watch one path.
|
||||
|
||||
DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
|
||||
|
||||
if (!file_util::PathExists(path))
|
||||
return false;
|
||||
|
||||
path_ = path;
|
||||
if (!file_util::AbsolutePath(&path_)) {
|
||||
path_ = FilePath(); // Make sure we're marked as not-in-use.
|
||||
return false;
|
||||
}
|
||||
delegate_ = delegate;
|
||||
recursive_ = recursive;
|
||||
|
||||
scoped_cftyperef<CFStringRef> cf_path(CFStringCreateWithCString(
|
||||
NULL, path.value().c_str(), kCFStringEncodingMacHFS));
|
||||
CFStringRef path_for_array = cf_path.get();
|
||||
scoped_cftyperef<CFArrayRef> watched_paths(CFArrayCreate(
|
||||
NULL, reinterpret_cast<const void**>(&path_for_array), 1,
|
||||
&kCFTypeArrayCallBacks));
|
||||
|
||||
FSEventStreamContext context;
|
||||
context.version = 0;
|
||||
context.info = this;
|
||||
context.retain = NULL;
|
||||
context.release = NULL;
|
||||
context.copyDescription = NULL;
|
||||
|
||||
fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
|
||||
watched_paths,
|
||||
kFSEventStreamEventIdSinceNow,
|
||||
kEventLatencySeconds,
|
||||
kFSEventStreamCreateFlagNone);
|
||||
FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(),
|
||||
kCFRunLoopDefaultMode);
|
||||
FSEventStreamStart(fsevent_stream_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DirectoryWatcher::DirectoryWatcher() {
|
||||
impl_ = new DirectoryWatcherImpl();
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file exists for Linux systems which don't have the inotify headers, and
|
||||
// thus cannot build directory_watcher_inotify.cc
|
||||
|
||||
#include "base/directory_watcher.h"
|
||||
|
||||
class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
|
||||
public:
|
||||
virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
||||
bool recursive) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
DirectoryWatcher::DirectoryWatcher() {
|
||||
impl_ = new DirectoryWatcherImpl();
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright (c) 2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/directory_watcher.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/file_path.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/platform_thread.h"
|
||||
#include "base/string_util.h"
|
||||
#if defined(OS_WIN)
|
||||
#include "base/win_util.h"
|
||||
#endif // defined(OS_WIN)
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// For tests where we wait a bit to verify nothing happened
|
||||
const int kWaitForEventTime = 1000;
|
||||
|
||||
class DirectoryWatcherTest : public testing::Test {
|
||||
public:
|
||||
// Implementation of DirectoryWatcher on Mac requires UI loop.
|
||||
DirectoryWatcherTest() : loop_(MessageLoop::TYPE_UI) {
|
||||
}
|
||||
|
||||
void OnTestDelegateFirstNotification(const FilePath& path) {
|
||||
notified_delegates_++;
|
||||
if (notified_delegates_ >= expected_notified_delegates_)
|
||||
MessageLoop::current()->Quit();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
// Name a subdirectory of the temp directory.
|
||||
FilePath path;
|
||||
ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &path));
|
||||
test_dir_ = path.Append(FILE_PATH_LITERAL("DirectoryWatcherTest"));
|
||||
|
||||
// Create a fresh, empty copy of this directory.
|
||||
file_util::Delete(test_dir_, true);
|
||||
file_util::CreateDirectory(test_dir_);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
// Make sure there are no tasks in the loop.
|
||||
loop_.RunAllPending();
|
||||
|
||||
// Clean up test directory.
|
||||
ASSERT_TRUE(file_util::Delete(test_dir_, true));
|
||||
ASSERT_FALSE(file_util::PathExists(test_dir_));
|
||||
}
|
||||
|
||||
// Write |content| to the |filename|. Returns true on success.
|
||||
bool WriteTestFile(const FilePath& filename,
|
||||
const std::string& content) {
|
||||
return (file_util::WriteFile(filename, content.c_str(), content.length()) ==
|
||||
static_cast<int>(content.length()));
|
||||
}
|
||||
|
||||
// Create directory |name| under test_dir_. If |sync| is true, runs
|
||||
// SyncIfPOSIX. Returns path to the created directory, including test_dir_.
|
||||
FilePath CreateTestDirDirectoryASCII(const std::string& name, bool sync) {
|
||||
FilePath path(test_dir_.AppendASCII(name));
|
||||
EXPECT_TRUE(file_util::CreateDirectory(path));
|
||||
if (sync)
|
||||
SyncIfPOSIX();
|
||||
return path;
|
||||
}
|
||||
|
||||
void SetExpectedNumberOfNotifiedDelegates(int n) {
|
||||
notified_delegates_ = 0;
|
||||
expected_notified_delegates_ = n;
|
||||
}
|
||||
|
||||
void VerifyExpectedNumberOfNotifiedDelegates() {
|
||||
// Check that we get at least the expected number of notified delegates.
|
||||
if (expected_notified_delegates_ - notified_delegates_ > 0)
|
||||
loop_.Run();
|
||||
|
||||
// Check that we get no more than the expected number of notified delegates.
|
||||
loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask,
|
||||
kWaitForEventTime);
|
||||
loop_.Run();
|
||||
EXPECT_EQ(expected_notified_delegates_, notified_delegates_);
|
||||
}
|
||||
|
||||
// We need this function for reliable tests on Mac OS X. FSEvents API
|
||||
// has a latency interval and can merge multiple events into one,
|
||||
// and we need a clear distinction between events triggered by test setup code
|
||||
// and test code.
|
||||
void SyncIfPOSIX() {
|
||||
#if defined(OS_POSIX)
|
||||
sync();
|
||||
#endif // defined(OS_POSIX)
|
||||
}
|
||||
|
||||
MessageLoop loop_;
|
||||
|
||||
// The path to a temporary directory used for testing.
|
||||
FilePath test_dir_;
|
||||
|
||||
// The number of test delegates which received their notification.
|
||||
int notified_delegates_;
|
||||
|
||||
// The number of notified test delegates after which we quit the message loop.
|
||||
int expected_notified_delegates_;
|
||||
};
|
||||
|
||||
class TestDelegate : public DirectoryWatcher::Delegate {
|
||||
public:
|
||||
TestDelegate(DirectoryWatcherTest* test)
|
||||
: test_(test),
|
||||
got_notification_(false),
|
||||
original_thread_id_(PlatformThread::CurrentId()) {
|
||||
}
|
||||
|
||||
bool got_notification() const {
|
||||
return got_notification_;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
got_notification_ = false;
|
||||
}
|
||||
|
||||
virtual void OnDirectoryChanged(const FilePath& path) {
|
||||
EXPECT_EQ(original_thread_id_, PlatformThread::CurrentId());
|
||||
if (!got_notification_)
|
||||
test_->OnTestDelegateFirstNotification(path);
|
||||
got_notification_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Hold a pointer to current test fixture to inform it on first notification.
|
||||
DirectoryWatcherTest* test_;
|
||||
|
||||
// Set to true after first notification.
|
||||
bool got_notification_;
|
||||
|
||||
// Keep track of original thread id to verify that callbacks are called
|
||||
// on the same thread.
|
||||
PlatformThreadId original_thread_id_;
|
||||
};
|
||||
|
||||
// Basic test: add a file and verify we notice it.
|
||||
TEST_F(DirectoryWatcherTest, NewFile) {
|
||||
DirectoryWatcher watcher;
|
||||
TestDelegate delegate(this);
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, false));
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
// Verify that modifying a file is caught.
|
||||
TEST_F(DirectoryWatcherTest, ModifiedFile) {
|
||||
// Write a file to the test dir.
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
SyncIfPOSIX();
|
||||
|
||||
DirectoryWatcher watcher;
|
||||
TestDelegate delegate(this);
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, false));
|
||||
|
||||
// Now make sure we get notified if the file is modified.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "new content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, DeletedFile) {
|
||||
// Write a file to the test dir.
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
SyncIfPOSIX();
|
||||
|
||||
DirectoryWatcher watcher;
|
||||
TestDelegate delegate(this);
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, false));
|
||||
|
||||
// Now make sure we get notified if the file is deleted.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(file_util::Delete(test_dir_.AppendASCII("test_file"), false));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
// Verify that letting the watcher go out of scope stops notifications.
|
||||
TEST_F(DirectoryWatcherTest, Unregister) {
|
||||
TestDelegate delegate(this);
|
||||
|
||||
{
|
||||
DirectoryWatcher watcher;
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, false));
|
||||
|
||||
// And then let it fall out of scope, clearing its watch.
|
||||
}
|
||||
|
||||
// Write a file to the test dir.
|
||||
SetExpectedNumberOfNotifiedDelegates(0);
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, SubDirRecursive) {
|
||||
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
// TODO(port): Recursive watches are not implemented on Linux.
|
||||
return;
|
||||
#endif // !defined(OS_WIN)
|
||||
|
||||
// Verify that modifications to a subdirectory are noticed by recursive watch.
|
||||
TestDelegate delegate(this);
|
||||
DirectoryWatcher watcher;
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, true));
|
||||
// Write a file to the subdir.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, SubDirNonRecursive) {
|
||||
#if defined(OS_WIN)
|
||||
// Disable this test for earlier version of Windows. It turned out to be
|
||||
// very difficult to create a reliable test for them.
|
||||
if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA)
|
||||
return;
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", false));
|
||||
|
||||
// Create a test file before the test. On Windows we get a notification
|
||||
// when creating a file in a subdir even with a non-recursive watch.
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
||||
|
||||
SyncIfPOSIX();
|
||||
|
||||
// Verify that modifications to a subdirectory are not noticed
|
||||
// by a not-recursive watch.
|
||||
DirectoryWatcher watcher;
|
||||
TestDelegate delegate(this);
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, false));
|
||||
|
||||
// Modify the test file. There should be no notifications.
|
||||
SetExpectedNumberOfNotifiedDelegates(0);
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "other content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Used by the DeleteDuringNotify test below.
|
||||
// Deletes the DirectoryWatcher when it's notified.
|
||||
class Deleter : public DirectoryWatcher::Delegate {
|
||||
public:
|
||||
Deleter(DirectoryWatcher* watcher, MessageLoop* loop)
|
||||
: watcher_(watcher),
|
||||
loop_(loop) {
|
||||
}
|
||||
|
||||
virtual void OnDirectoryChanged(const FilePath& path) {
|
||||
watcher_.reset(NULL);
|
||||
loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask());
|
||||
}
|
||||
|
||||
scoped_ptr<DirectoryWatcher> watcher_;
|
||||
MessageLoop* loop_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
// Verify that deleting a watcher during the callback
|
||||
TEST_F(DirectoryWatcherTest, DeleteDuringNotify) {
|
||||
DirectoryWatcher* watcher = new DirectoryWatcher;
|
||||
Deleter deleter(watcher, &loop_); // Takes ownership of watcher.
|
||||
ASSERT_TRUE(watcher->Watch(test_dir_, &deleter, false));
|
||||
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
loop_.Run();
|
||||
|
||||
// We win if we haven't crashed yet.
|
||||
// Might as well double-check it got deleted, too.
|
||||
ASSERT_TRUE(deleter.watcher_.get() == NULL);
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, MultipleWatchersSingleFile) {
|
||||
DirectoryWatcher watcher1, watcher2;
|
||||
TestDelegate delegate1(this), delegate2(this);
|
||||
ASSERT_TRUE(watcher1.Watch(test_dir_, &delegate1, false));
|
||||
ASSERT_TRUE(watcher2.Watch(test_dir_, &delegate2, false));
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(2);
|
||||
ASSERT_TRUE(WriteTestFile(test_dir_.AppendASCII("test_file"), "content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, MultipleWatchersDifferentFiles) {
|
||||
const int kNumberOfWatchers = 5;
|
||||
DirectoryWatcher watchers[kNumberOfWatchers];
|
||||
TestDelegate delegates[kNumberOfWatchers] = {this, this, this, this, this};
|
||||
FilePath subdirs[kNumberOfWatchers];
|
||||
for (int i = 0; i < kNumberOfWatchers; i++) {
|
||||
subdirs[i] = CreateTestDirDirectoryASCII("Dir" + IntToString(i), false);
|
||||
ASSERT_TRUE(watchers[i].Watch(subdirs[i], &delegates[i], false));
|
||||
}
|
||||
for (int i = 0; i < kNumberOfWatchers; i++) {
|
||||
// Verify that we only get modifications from one watcher (each watcher has
|
||||
// different directory).
|
||||
|
||||
for (int j = 0; j < kNumberOfWatchers; j++)
|
||||
delegates[j].reset();
|
||||
|
||||
// Write a file to the subdir.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdirs[i].AppendASCII("test_file"), "content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
|
||||
loop_.RunAllPending();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(OS_WIN) || defined(OS_MACOSX)
|
||||
// TODO(phajdan.jr): Enable when support for Linux recursive watches is added.
|
||||
|
||||
TEST_F(DirectoryWatcherTest, WatchCreatedDirectory) {
|
||||
TestDelegate delegate(this);
|
||||
DirectoryWatcher watcher;
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, true));
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
||||
// Create a file inside the subdir to force Windows to fire notifications.
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
|
||||
delegate.reset();
|
||||
|
||||
// Verify that changes inside the subdir are noticed.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "other content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, RecursiveWatchDeletedSubdirectory) {
|
||||
FilePath subdir(CreateTestDirDirectoryASCII("SubDir", true));
|
||||
|
||||
TestDelegate delegate(this);
|
||||
DirectoryWatcher watcher;
|
||||
ASSERT_TRUE(watcher.Watch(test_dir_, &delegate, true));
|
||||
|
||||
// Write a file to the subdir.
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdir.AppendASCII("test_file"), "some content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
|
||||
delegate.reset();
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(file_util::Delete(subdir, true));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
|
||||
TEST_F(DirectoryWatcherTest, MoveFileAcrossWatches) {
|
||||
FilePath subdir1(CreateTestDirDirectoryASCII("SubDir1", true));
|
||||
FilePath subdir2(CreateTestDirDirectoryASCII("SubDir2", true));
|
||||
|
||||
TestDelegate delegate1(this), delegate2(this);
|
||||
DirectoryWatcher watcher1, watcher2;
|
||||
ASSERT_TRUE(watcher1.Watch(subdir1, &delegate1, true));
|
||||
ASSERT_TRUE(watcher2.Watch(subdir2, &delegate2, true));
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdir1.AppendASCII("file"), "some content"));
|
||||
SyncIfPOSIX();
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
|
||||
delegate1.reset();
|
||||
delegate2.reset();
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(2);
|
||||
ASSERT_TRUE(file_util::Move(subdir1.AppendASCII("file"),
|
||||
subdir2.AppendASCII("file")));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
|
||||
delegate1.reset();
|
||||
delegate2.reset();
|
||||
|
||||
SetExpectedNumberOfNotifiedDelegates(1);
|
||||
ASSERT_TRUE(WriteTestFile(subdir2.AppendASCII("file"), "other content"));
|
||||
VerifyExpectedNumberOfNotifiedDelegates();
|
||||
}
|
||||
#endif // defined(OS_WIN) || defined(OS_MACOSX)
|
||||
|
||||
// Verify that watching a directory that doesn't exist fails, but doesn't
|
||||
// asssert.
|
||||
// Basic test: add a file and verify we notice it.
|
||||
TEST_F(DirectoryWatcherTest, NonExistentDirectory) {
|
||||
DirectoryWatcher watcher;
|
||||
ASSERT_FALSE(watcher.Watch(test_dir_.AppendASCII("does-not-exist"), NULL,
|
||||
false));
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/directory_watcher.h"
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/object_watcher.h"
|
||||
#include "base/ref_counted.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate,
|
||||
public base::ObjectWatcher::Delegate {
|
||||
public:
|
||||
DirectoryWatcherImpl() : handle_(INVALID_HANDLE_VALUE) {}
|
||||
virtual ~DirectoryWatcherImpl();
|
||||
|
||||
virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
|
||||
bool recursive);
|
||||
|
||||
// Callback from MessageLoopForIO.
|
||||
virtual void OnObjectSignaled(HANDLE object);
|
||||
|
||||
private:
|
||||
// Delegate to notify upon changes.
|
||||
DirectoryWatcher::Delegate* delegate_;
|
||||
// Path we're watching (passed to delegate).
|
||||
FilePath path_;
|
||||
// Handle for FindFirstChangeNotification.
|
||||
HANDLE handle_;
|
||||
// ObjectWatcher to watch handle_ for events.
|
||||
base::ObjectWatcher watcher_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
|
||||
};
|
||||
|
||||
DirectoryWatcherImpl::~DirectoryWatcherImpl() {
|
||||
if (handle_ != INVALID_HANDLE_VALUE) {
|
||||
watcher_.StopWatching();
|
||||
FindCloseChangeNotification(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectoryWatcherImpl::Watch(const FilePath& path,
|
||||
DirectoryWatcher::Delegate* delegate, bool recursive) {
|
||||
DCHECK(path_.value().empty()); // Can only watch one path.
|
||||
|
||||
handle_ = FindFirstChangeNotification(
|
||||
path.value().c_str(),
|
||||
recursive,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE);
|
||||
if (handle_ == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
delegate_ = delegate;
|
||||
path_ = path;
|
||||
watcher_.StartWatching(handle_, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DirectoryWatcherImpl::OnObjectSignaled(HANDLE object) {
|
||||
DCHECK(object == handle_);
|
||||
// Make sure we stay alive through the body of this function.
|
||||
scoped_refptr<DirectoryWatcherImpl> keep_alive(this);
|
||||
|
||||
delegate_->OnDirectoryChanged(path_);
|
||||
|
||||
// Register for more notifications on file change.
|
||||
BOOL ok = FindNextChangeNotification(object);
|
||||
DCHECK(ok);
|
||||
watcher_.StartWatching(object, this);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DirectoryWatcher::DirectoryWatcher() {
|
||||
impl_ = new DirectoryWatcherImpl();
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This provides a wrapper around system calls which may be interrupted by a
|
||||
// signal and return EINTR. See man 7 signal.
|
||||
//
|
||||
// On Windows, this wrapper macro does nothing.
|
||||
|
||||
#ifndef BASE_EINTR_WRAPPER_H_
|
||||
#define BASE_EINTR_WRAPPER_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
typeof(x) __eintr_result__; \
|
||||
do { \
|
||||
__eintr_result__ = x; \
|
||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
||||
__eintr_result__;\
|
||||
})
|
||||
|
||||
#else
|
||||
|
||||
#define HANDLE_EINTR(x) x
|
||||
|
||||
#endif // OS_POSIX
|
||||
|
||||
#endif // !BASE_EINTR_WRAPPER_H_
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#include "base/event_recorder.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
// A note about time.
|
||||
// For perfect playback of events, you'd like a very accurate timer
|
||||
// so that events are played back at exactly the same time that
|
||||
// they were recorded. However, windows has a clock which is only
|
||||
// granular to ~15ms. We see more consistent event playback when
|
||||
// using a higher resolution timer. To do this, we use the
|
||||
// timeGetTime API instead of the default GetTickCount() API.
|
||||
|
||||
namespace base {
|
||||
|
||||
EventRecorder* EventRecorder::current_ = NULL;
|
||||
|
||||
LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
CHECK(EventRecorder::current());
|
||||
return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
CHECK(EventRecorder::current());
|
||||
return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
EventRecorder::~EventRecorder() {
|
||||
// Try to assert early if the caller deletes the recorder
|
||||
// while it is still in use.
|
||||
DCHECK(!journal_hook_);
|
||||
DCHECK(!is_recording_ && !is_playing_);
|
||||
}
|
||||
|
||||
bool EventRecorder::StartRecording(const FilePath& filename) {
|
||||
if (journal_hook_ != NULL)
|
||||
return false;
|
||||
if (is_recording_ || is_playing_)
|
||||
return false;
|
||||
|
||||
// Open the recording file.
|
||||
DCHECK(file_ == NULL);
|
||||
file_ = file_util::OpenFile(filename, "wb+");
|
||||
if (!file_) {
|
||||
DLOG(ERROR) << "EventRecorder could not open log file";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the faster clock, if possible.
|
||||
::timeBeginPeriod(1);
|
||||
|
||||
// Set the recording hook. JOURNALRECORD can only be used as a global hook.
|
||||
journal_hook_ = ::SetWindowsHookEx(WH_JOURNALRECORD, StaticRecordWndProc,
|
||||
GetModuleHandle(NULL), 0);
|
||||
if (!journal_hook_) {
|
||||
DLOG(ERROR) << "EventRecorder Record Hook failed";
|
||||
file_util::CloseFile(file_);
|
||||
return false;
|
||||
}
|
||||
|
||||
is_recording_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventRecorder::StopRecording() {
|
||||
if (is_recording_) {
|
||||
DCHECK(journal_hook_ != NULL);
|
||||
|
||||
if (!::UnhookWindowsHookEx(journal_hook_)) {
|
||||
DLOG(ERROR) << "EventRecorder Unhook failed";
|
||||
// Nothing else we can really do here.
|
||||
return;
|
||||
}
|
||||
|
||||
::timeEndPeriod(1);
|
||||
|
||||
DCHECK(file_ != NULL);
|
||||
file_util::CloseFile(file_);
|
||||
file_ = NULL;
|
||||
|
||||
journal_hook_ = NULL;
|
||||
is_recording_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EventRecorder::StartPlayback(const FilePath& filename) {
|
||||
if (journal_hook_ != NULL)
|
||||
return false;
|
||||
if (is_recording_ || is_playing_)
|
||||
return false;
|
||||
|
||||
// Open the recording file.
|
||||
DCHECK(file_ == NULL);
|
||||
file_ = file_util::OpenFile(filename, "rb");
|
||||
if (!file_) {
|
||||
DLOG(ERROR) << "EventRecorder Playback could not open log file";
|
||||
return false;
|
||||
}
|
||||
// Read the first event from the record.
|
||||
if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) {
|
||||
DLOG(ERROR) << "EventRecorder Playback has no records!";
|
||||
file_util::CloseFile(file_);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the faster clock, if possible.
|
||||
::timeBeginPeriod(1);
|
||||
|
||||
// Playback time is tricky. When playing back, we read a series of events,
|
||||
// each with timeouts. Simply subtracting the delta between two timers will
|
||||
// lead to fast playback (about 2x speed). The API has two events, one
|
||||
// which advances to the next event (HC_SKIP), and another that requests the
|
||||
// event (HC_GETNEXT). The same event will be requested multiple times.
|
||||
// Each time the event is requested, we must calculate the new delay.
|
||||
// To do this, we track the start time of the playback, and constantly
|
||||
// re-compute the delay. I mention this only because I saw two examples
|
||||
// of how to use this code on the net, and both were broken :-)
|
||||
playback_start_time_ = timeGetTime();
|
||||
playback_first_msg_time_ = playback_msg_.time;
|
||||
|
||||
// Set the hook. JOURNALPLAYBACK can only be used as a global hook.
|
||||
journal_hook_ = ::SetWindowsHookEx(WH_JOURNALPLAYBACK, StaticPlaybackWndProc,
|
||||
GetModuleHandle(NULL), 0);
|
||||
if (!journal_hook_) {
|
||||
DLOG(ERROR) << "EventRecorder Playback Hook failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
is_playing_ = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventRecorder::StopPlayback() {
|
||||
if (is_playing_) {
|
||||
DCHECK(journal_hook_ != NULL);
|
||||
|
||||
if (!::UnhookWindowsHookEx(journal_hook_)) {
|
||||
DLOG(ERROR) << "EventRecorder Unhook failed";
|
||||
// Nothing else we can really do here.
|
||||
}
|
||||
|
||||
DCHECK(file_ != NULL);
|
||||
file_util::CloseFile(file_);
|
||||
file_ = NULL;
|
||||
|
||||
::timeEndPeriod(1);
|
||||
|
||||
journal_hook_ = NULL;
|
||||
is_playing_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Windows callback hook for the recorder.
|
||||
LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
|
||||
static bool recording_enabled = true;
|
||||
EVENTMSG* msg_ptr = NULL;
|
||||
|
||||
// The API says we have to do this.
|
||||
// See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx
|
||||
if (nCode < 0)
|
||||
return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
|
||||
|
||||
// Check for the break key being pressed and stop recording.
|
||||
if (::GetKeyState(VK_CANCEL) & 0x8000) {
|
||||
StopRecording();
|
||||
return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
// The Journal Recorder must stop recording events when system modal
|
||||
// dialogs are present. (see msdn link above)
|
||||
switch(nCode) {
|
||||
case HC_SYSMODALON:
|
||||
recording_enabled = false;
|
||||
break;
|
||||
case HC_SYSMODALOFF:
|
||||
recording_enabled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (nCode == HC_ACTION && recording_enabled) {
|
||||
// Aha - we have an event to record.
|
||||
msg_ptr = reinterpret_cast<EVENTMSG*>(lParam);
|
||||
msg_ptr->time = timeGetTime();
|
||||
fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_);
|
||||
fflush(file_);
|
||||
}
|
||||
|
||||
return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
// Windows callback for the playback mode.
|
||||
LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
static bool playback_enabled = true;
|
||||
int delay = 0;
|
||||
|
||||
switch(nCode) {
|
||||
// A system modal dialog box is being displayed. Stop playing back
|
||||
// messages.
|
||||
case HC_SYSMODALON:
|
||||
playback_enabled = false;
|
||||
break;
|
||||
|
||||
// A system modal dialog box is destroyed. We can start playing back
|
||||
// messages again.
|
||||
case HC_SYSMODALOFF:
|
||||
playback_enabled = true;
|
||||
break;
|
||||
|
||||
// Prepare to copy the next mouse or keyboard event to playback.
|
||||
case HC_SKIP:
|
||||
if (!playback_enabled)
|
||||
break;
|
||||
|
||||
// Read the next event from the record.
|
||||
if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1)
|
||||
this->StopPlayback();
|
||||
break;
|
||||
|
||||
// Copy the mouse or keyboard event to the EVENTMSG structure in lParam.
|
||||
case HC_GETNEXT:
|
||||
if (!playback_enabled)
|
||||
break;
|
||||
|
||||
memcpy(reinterpret_cast<void*>(lParam), &playback_msg_,
|
||||
sizeof(playback_msg_));
|
||||
|
||||
// The return value is the amount of time (in milliseconds) to wait
|
||||
// before playing back the next message in the playback queue. Each
|
||||
// time this is called, we recalculate the delay relative to our current
|
||||
// wall clock.
|
||||
delay = (playback_msg_.time - playback_first_msg_time_) -
|
||||
(timeGetTime() - playback_start_time_);
|
||||
if (delay < 0)
|
||||
delay = 0;
|
||||
return delay;
|
||||
|
||||
// An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE
|
||||
// indicating that the message is not removed from the message queue after
|
||||
// PeekMessage processing.
|
||||
case HC_NOREMOVE:
|
||||
break;
|
||||
}
|
||||
|
||||
return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
} // namespace base
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче