diff options
Diffstat (limited to 'ceee/common')
29 files changed, 4400 insertions, 0 deletions
diff --git a/ceee/common/com_utils.cc b/ceee/common/com_utils.cc new file mode 100644 index 0000000..b251ca1 --- /dev/null +++ b/ceee/common/com_utils.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2010 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. +// +// Utilities for COM objects, error codes etc. + +#include "ceee/common/com_utils.h" + +#include <atlbase.h> +#include <shlwapi.h> + +#include "base/string_util.h" + +namespace com { + +std::ostream& operator<<(std::ostream& os, const LogHr& hr) { + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + char error_text[4096] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, 0, hr.hr_, 0, error_text, + arraysize(error_text), NULL); + std::string error(error_text); + TrimString(error, kWhitespaceASCII, &error); + + return os << "[hr=0x" << std::hex << hr.hr_ << ", msg=" << error << "]"; +} + +std::ostream& operator<<(std::ostream& os, const LogWe& we) { + // Looks up the human-readable system message for the Windows error code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + char error_text[4096] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, 0, we.we_, 0, error_text, + arraysize(error_text), NULL); + std::string error(error_text); + TrimString(error, kWhitespaceASCII, &error); + + return os << "[we=" << we.we_ << ", msg=" << error << "]"; +} + +// Seam so unit tests can mock out the side effects of this function. +HRESULT UpdateRegistryFromResourceImpl(int reg_id, BOOL should_register, + _ATL_REGMAP_ENTRY* entries) { + return _pAtlModule->UpdateRegistryFromResource( + reg_id, should_register, entries); +} + +HRESULT ModuleRegistrationWithoutAppid(int reg_id, BOOL should_register) { + // Get our module path. + wchar_t module_path[MAX_PATH] = { 0 }; + if (0 == ::GetModuleFileName(_AtlBaseModule.GetModuleInstance(), + module_path, arraysize(module_path))) { + NOTREACHED(); + return AlwaysErrorFromLastError(); + } + + // Copy the filename. + std::wstring module_basename(::PathFindFileName(module_path)); + // And strip the filename. + if (!::PathRemoveFileSpec(module_path)) { + NOTREACHED(); + return AlwaysErrorFromLastError(); + } + + _ATL_REGMAP_ENTRY entries[] = { + { L"MODULE_PATH", module_path }, + { L"MODULE_BASENAME", module_basename.c_str() }, + { NULL, NULL} + }; + + return UpdateRegistryFromResourceImpl(reg_id, should_register, entries); +} + +} // namespace com diff --git a/ceee/common/com_utils.h b/ceee/common/com_utils.h new file mode 100644 index 0000000..e531ba4 --- /dev/null +++ b/ceee/common/com_utils.h @@ -0,0 +1,93 @@ +// Copyright (c) 2010 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. +// +// Utilities for COM objects, error codes etc. + +#ifndef CEEE_COMMON_COM_UTILS_H_ +#define CEEE_COMMON_COM_UTILS_H_ + +#include <windows.h> +#include <wtypes.h> + +#include <ostream> + +namespace com { + +// Returns the provided hr if it is an error, otherwise default_error. +inline HRESULT AlwaysError(HRESULT hr, HRESULT default_error) { + if (FAILED(hr)) { + return hr; + } else { + return default_error; + } +} + +// Returns the provided hr if it is an error, otherwise E_FAIL. +inline HRESULT AlwaysError(HRESULT hr) { + return AlwaysError(hr, E_FAIL); +} + +// Converts a Win32 result code to a HRESULT. If the 'win32_code' +// does not indicate an error, it returns 'default_error'. +inline HRESULT AlwaysErrorFromWin32(DWORD win32_code, + HRESULT default_error) { + HRESULT hr = HRESULT_FROM_WIN32(win32_code); + return AlwaysError(hr, default_error); +} + +// Converts a Win32 result code to a HRESULT. If the 'win32_code' +// does not indicate an error, it returns E_FAIL +inline HRESULT AlwaysErrorFromWin32(DWORD win32_code) { + return AlwaysErrorFromWin32(win32_code, E_FAIL); +} + +// Returns the HRESULT equivalent of GetLastError(), unless it does not +// represent an error in which case it returns 'default_error'. +inline HRESULT AlwaysErrorFromLastError(HRESULT default_error) { + return AlwaysErrorFromWin32(::GetLastError(), default_error); +} + +// Returns the HRESULT equivalent of GetLastError(), unless it does not +// represent an error in which case it returns E_FAIL. +inline HRESULT AlwaysErrorFromLastError() { + return AlwaysErrorFromLastError(E_FAIL); +} + +// Return the specified string, or the empty string if it is NULL. +inline const wchar_t* ToString(BSTR str) { + return str ? str : L""; +} + +// Logs HRESULTs verbosely, with the error code and human-readable error +// text if available. +class LogHr { + public: + explicit LogHr(HRESULT hr) : hr_(hr) {} + private: + HRESULT hr_; + friend std::ostream& operator<<(std::ostream&, const LogHr&); +}; + +std::ostream& operator<<(std::ostream& os, const LogHr& hr); + +// Logs Windows errors verbosely, with the error code and human-readable error +// text if available. +class LogWe { + public: + LogWe() : we_(::GetLastError()) {} + explicit LogWe(DWORD we) : we_(we) {} + private: + DWORD we_; + friend std::ostream& operator<<(std::ostream&, const LogWe&); +}; + +std::ostream& operator<<(std::ostream& os, const LogWe& we); + +// Use to register a .reg file that needs %MODULE_PATH% and %MODULE_BASENAME% +// replaceables, but no %APPID%. +HRESULT ModuleRegistrationWithoutAppid(int reg_id, BOOL should_register); + +} // namespace com + +#endif // CEEE_COMMON_COM_UTILS_H_ diff --git a/ceee/common/com_utils_unittest.cc b/ceee/common/com_utils_unittest.cc new file mode 100644 index 0000000..ea27f84 --- /dev/null +++ b/ceee/common/com_utils_unittest.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2010 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. +// +// Unit tests for COM utils. + +#include "ceee/common/com_utils.h" + +#include <atlcomcli.h> + +#include "ceee/testing/utils/mock_win32.h" +#include "ceee/testing/utils/mock_static.h" +#include "ceee/testing/utils/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace com { + +HRESULT UpdateRegistryFromResourceImpl(int reg_id, BOOL should_register, + _ATL_REGMAP_ENTRY* entries); + +} // namespace com; + +namespace { + +using testing::_; +using testing::DoAll; +using testing::CopyStringToArgument; +using testing::Field; +using testing::Pointee; +using testing::Return; +using testing::StrEq; + +TEST(ComUtils, AlwaysError) { + EXPECT_EQ(com::AlwaysError(S_OK, E_INVALIDARG), E_INVALIDARG); + EXPECT_EQ(com::AlwaysError(E_FAIL, E_INVALIDARG), E_FAIL); + + EXPECT_EQ(com::AlwaysError(S_OK), E_FAIL); + EXPECT_EQ(com::AlwaysError(E_FAIL), E_FAIL); + + EXPECT_EQ(com::AlwaysErrorFromWin32(NO_ERROR, E_INVALIDARG), E_INVALIDARG); + EXPECT_EQ(com::AlwaysErrorFromWin32(NO_ERROR), E_FAIL); + + EXPECT_EQ(com::AlwaysErrorFromWin32(ERROR_ACCESS_DENIED, E_INVALIDARG), + HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + EXPECT_EQ(com::AlwaysErrorFromWin32(ERROR_ACCESS_DENIED), + HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + + DWORD last_error = ::GetLastError(); + + ::SetLastError(NO_ERROR); + EXPECT_EQ(com::AlwaysErrorFromLastError(E_INVALIDARG), E_INVALIDARG); + EXPECT_EQ(com::AlwaysErrorFromLastError(), E_FAIL); + + ::SetLastError(ERROR_ACCESS_DENIED); + EXPECT_EQ(com::AlwaysErrorFromLastError(E_INVALIDARG), + HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + EXPECT_EQ(com::AlwaysErrorFromLastError(), + HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + + ::SetLastError(last_error); +} + +TEST(ComUtils, ToString) { + CComBSTR filled(L"hello"); + EXPECT_STREQ(com::ToString(filled), L"hello"); + + CComBSTR empty; + EXPECT_STREQ(com::ToString(empty), L""); +} + +TEST(ComUtils, HrLog) { + { + std::ostringstream stream; + stream << com::LogHr(S_OK); + std::string str = stream.str(); + EXPECT_NE(str.find("0x0,"), std::string::npos); + EXPECT_NE(str.find("msg="), std::string::npos); + } + + { + std::ostringstream stream; + stream << com::LogHr(E_FAIL); + std::string str = stream.str(); + EXPECT_NE(str.find("0x80004005,"), std::string::npos); + EXPECT_NE(str.find("msg=Unspecified error"), std::string::npos); + } +} + +TEST(ComUtils, WeLog) { + { + std::ostringstream stream; + stream << com::LogWe(ERROR_SUCCESS); + std::string str = stream.str(); + EXPECT_NE(str.find("[we=0,"), std::string::npos); + EXPECT_NE(str.find("msg=The operation completed successfully"), + std::string::npos); + } + + { + std::ostringstream stream; + stream << com::LogWe(ERROR_INVALID_FUNCTION); + std::string str = stream.str(); + EXPECT_NE(str.find("[we=1,"), std::string::npos); + EXPECT_NE(str.find("msg=Incorrect function"), std::string::npos); + } +} + +HRESULT UpdateRegistryFromResourceImpl(int reg_id, BOOL should_register, + _ATL_REGMAP_ENTRY* entries) { + return _pAtlModule->UpdateRegistryFromResource( + reg_id, should_register, entries); +} + +// Mock out UpdateRegistryFromResourceImpl. +MOCK_STATIC_CLASS_BEGIN(MockModuleRegistrationWithoutAppid) + MOCK_STATIC_INIT_BEGIN(MockModuleRegistrationWithoutAppid) + MOCK_STATIC_INIT2(com::UpdateRegistryFromResourceImpl, + UpdateRegistryFromResourceImpl); + MOCK_STATIC_INIT_END() + + MOCK_STATIC3(HRESULT, , UpdateRegistryFromResourceImpl, + int, BOOL, _ATL_REGMAP_ENTRY*); +MOCK_STATIC_CLASS_END(MockModuleRegistrationWithoutAppid) + +MATCHER(ValidateRegmapComponents, "Checks regmap for correct values") { + return 0 == lstrcmpW(arg[0].szData, L"c:\\foo\\blat") && + 0 == lstrcmpW(arg[1].szData, L"bingo.exe"); +} + +TEST_DEBUG_ONLY(ComUtils, ModuleRegistrationWithoutAppid) { + MockModuleRegistrationWithoutAppid mock; + EXPECT_CALL(mock, UpdateRegistryFromResourceImpl( + 42, TRUE, ValidateRegmapComponents())).WillRepeatedly(Return(S_OK)); + + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, GetModuleFileName(_, _, _)).WillRepeatedly(DoAll( + CopyStringToArgument<1>(L"c:\\foo\\blat\\bingo.exe"), Return(1))); + + EXPECT_EQ(S_OK, com::ModuleRegistrationWithoutAppid(42, TRUE)); +} + +} // namespace diff --git a/ceee/common/common.gyp b/ceee/common/common.gyp new file mode 100644 index 0000000..ce8cc2b --- /dev/null +++ b/ceee/common/common.gyp @@ -0,0 +1,106 @@ +# Copyright (c) 2010 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', + '../../ceee/common.gypi', + ], + 'target_defaults': { + 'include_dirs': [ + '../..', + ], + }, + 'targets': [ + { + 'target_name': 'initializing_coclass', + 'type': 'none', + 'sources': [ + 'initializing_coclass.h', + 'initializing_coclass.py', + ], + 'actions': [ + { + 'action_name': 'make_initializing_coclass', + 'msvs_cygwin_shell': 0, + 'msvs_quote_cmd': 0, + 'inputs': [ + 'initializing_coclass.py', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/initializing_coclass_gen.inl', + ], + 'action': [ + '<@(python)', + 'initializing_coclass.py', + '"<(SHARED_INTERMEDIATE_DIR)/initializing_coclass_gen.inl"', + ], + }, + ], + # All who use this need to be able to find the .inl file we generate. + 'all_dependent_settings': { + 'include_dirs': ['<(SHARED_INTERMEDIATE_DIR)'], + }, + }, + { + 'target_name': 'ceee_common', + 'type': 'static_library', + 'dependencies': [ + 'initializing_coclass', + '../../base/base.gyp:base', + '../../testing/gmock.gyp:gmock', + '../../third_party/npapi/npapi.gyp:npapi', + ], + 'msvs_guid': '4BAB6049-29A7-47C2-A754-14CA3FBF5BD9', + 'sources': [ + 'com_utils.cc', + 'com_utils.h', + 'initializing_coclass.h', + 'install_utils.cc', + 'install_utils.h', + 'np_browser_functions.cc', + 'np_browser_functions.h', + 'npobject_impl.cc', + 'npobject_impl.h', + 'npplugin_impl.cc', + 'npplugin_impl.h', + 'process_utils_win.cc', + 'process_utils_win.h', + 'sidestep_integration.cc', + 'windows_constants.cc', + 'windows_constants.h', + 'window_utils.cc', + 'window_utils.h', + ], + }, + { + 'target_name': 'ceee_common_unittests', + 'type': 'executable', + 'msvs_guid': 'A6DF7E24-63DF-47E6-BA72-423FCA8AC36D', + 'sources': [ + 'com_utils_unittest.cc', + 'common_unittest_main.cc', + 'initializing_coclass_unittest.cc', + 'install_utils_unittest.cc', + 'npobject_impl_unittest.cc', + 'npplugin_impl_unittest.cc', + 'process_utils_win_unittest.cc', + 'sidestep_unittest.cc', + 'window_utils_unittest.cc', + ], + 'dependencies': [ + 'ceee_common', + '<(DEPTH)/ceee/testing/sidestep/sidestep.gyp:sidestep', + '../testing/utils/test_utils.gyp:test_utils', + '../../base/base.gyp:base', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + '../../third_party/npapi/npapi.gyp:npapi', + ], + } + ] +} diff --git a/ceee/common/common_unittest_main.cc b/ceee/common/common_unittest_main.cc new file mode 100644 index 0000000..66c81a0 --- /dev/null +++ b/ceee/common/common_unittest_main.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2010 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. +// +// Main function for common unittests. +#include <atlbase.h> +#include <atlcom.h> +#include "base/at_exit.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// We're testing ATL code that requires a module object. +class ObligatoryModule: public CAtlDllModuleT<ObligatoryModule> { +}; + +ObligatoryModule g_obligatory_atl_module; + +int main(int argc, char **argv) { + base::AtExitManager exit_manager; + testing::InitGoogleMock(&argc, argv); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/ceee/common/initializing_coclass.h b/ceee/common/initializing_coclass.h new file mode 100644 index 0000000..b29313f --- /dev/null +++ b/ceee/common/initializing_coclass.h @@ -0,0 +1,123 @@ +// Copyright (c) 2010 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. +// +// +// Declares a convenience coclass implementation that makes it easy to create +// initialized object instances. +#ifndef CEEE_COMMON_INITIALIZING_COCLASS_H_ +#define CEEE_COMMON_INITIALIZING_COCLASS_H_ + +#include <atlbase.h> +#include <atlcom.h> + +using ATL::CComObject; + +// A convenience mixin coclass, to allow creating and initializing +// CComObject<T>-derived object instances, and optionally query them for +// a given interface, all in a single CreateInstance invocation. +// +// Usage: +// <code> +// class MyObjectImpl: public CComObjectRoot<...>, +// public InitializingCoClass<MyObjectImpl>, ... { +// public: +// // This function will be invoked when MyObjectImpl::CreateInstance with N +// // arguments of the appropriate type is invoked. +// // @note if this function returns error, the object will be destroyed +// // @note reference args must be const +// HRESULT Initialize(SomeType1 arg1, ..., SomeTypeN argN); +// }; +// ... +// CComPtr<IFoo> foo; +// HRESULT hr = MyObjectImp::CreateInstance(arg1, ..., argN, &foo) +// </code> +// +// @param T the CComObjectRootEx derivative class you want to create and +// initalize. +template <class T> +class InitializingCoClass { + public: + typedef CComObject<T> TImpl; + + // Creates an instance of CComObject<T> and initializes it. + // @param new_instance on success returns the new, initialized instance. + // @note this method does not return a reference to the new object. + static HRESULT CreateInstance(T** new_instance) { + TImpl* instance; + HRESULT hr = TImpl::CreateInstance(&instance); + if (FAILED(hr)) + return hr; + + instance->InternalFinalConstructAddRef(); + hr = instance->Initialize(); + instance->InternalFinalConstructRelease(); + if (FAILED(hr)) { + delete instance; + instance = NULL; + } + *new_instance = instance; + + return hr; + } + + // Creates an instance of CComObject<T>, initializes it and queries it + // for interface I. + // @param instance on success returns the new, initialized instance. + template <class I> + static HRESULT CreateInitialized(I** instance) { + T* new_instance; + HRESULT hr = CreateInstance(&new_instance); + if (FAILED(hr)) + return hr; + + hr = new_instance->QueryInterface(__uuidof(I), + reinterpret_cast<void**>(instance)); + if (FAILED(hr)) + delete new_instance; + + return hr; + } + + // Creates an instance of CComObject<T>, initializes it and queries it + // for an interface. + // @param iid the interface to query for. Must be congruent to the type I. + // @param instance on success returns the new, initialized instance. + template <class I> + static HRESULT CreateInitializedIID(REFIID iid, I** instance) { + T* new_instance; + HRESULT hr = CreateInstance(&new_instance); + if (FAILED(hr)) + return hr; + + hr = new_instance->QueryInterface(iid, reinterpret_cast<void**>(instance)); + if (FAILED(hr)) + delete new_instance; + + return hr; + } + + +// This file is chock full of repetitions of the same code, and because we +// don't yet stock infinite monkeys (nor infinite developers to review their +// code) we substitute with the python script initializing_coclass.py +// +// The functions therein are of this general make: + +// Create a new, initialized T* instance. Note that zero references will +// be held to *new_instance at return. +// +// template <class A1, ..., class An> +// HRESULT CreateInstance(A1 a1, ..., An an, T **new_instance); +// +// Create a new, initialized T* instance, and QI it for I +// template <class I, class A1, ..., class An> +// HRESULT CreateInstance(A1 a1, ..., An an, I **new_instance); +#include "initializing_coclass_gen.inl" // NOLINT +}; + +#ifndef CEEE_COMMON_INITIALIZING_COCLASS_GEN_INL_ +#error initializing_coclass_gen.inl wasn't generated correctly. +#endif + +#endif // CEEE_COMMON_INITIALIZING_COCLASS_H_ diff --git a/ceee/common/initializing_coclass.py b/ceee/common/initializing_coclass.py new file mode 100644 index 0000000..f756fd0 --- /dev/null +++ b/ceee/common/initializing_coclass.py @@ -0,0 +1,107 @@ +# Copyright (c) 2010 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. +"""A generator for initializing_coclass.h, which contains a bunch of +repeated code that can't be produced through the preprocessor.""" + +import sys +from string import Template + +HEADER_ = """\ +// This file is autogenerated by initializing_coclass.py, do not edit. +#ifndef CEEE_COMMON_INITIALIZING_COCLASS_GEN_INL_ +#define CEEE_COMMON_INITIALIZING_COCLASS_GEN_INL_ + +""" + +FOOTER_ = """\ +#endif // CEEE_COMMON_INITIALIZING_COCLASS_GEN_INL_ +""" + +# This template is used to generate a pair of template functions for N +# initialization parameters. +TEMPLATE_ = Template('''\ + // Creates a new instance of ImplClass + // @returns S_OK on success, an appropriate error on failure. + // @param new_instance on success returns the newly created and initialized + // ImplClass instance. + // @note *new_instance has zero references, and as result the use of this + // function is highly discouraged as the existence of *new_instance + // on return of this function is quite brittle. + template <${templ_params}> + static HRESULT CreateInstance(${params}, T** new_instance) { + TImpl* instance; + HRESULT hr = TImpl::CreateInstance(&instance); + if (FAILED(hr)) + return hr; + + instance->InternalFinalConstructAddRef(); + hr = instance->Initialize(${args}); + instance->InternalFinalConstructRelease(); + if (FAILED(hr)) { + delete instance; + instance = NULL; + } + *new_instance = instance; + + return hr; + } + + template <class I, ${templ_params}> + static HRESULT CreateInitialized(${params}, I** instance) { + T* new_instance; + HRESULT hr = CreateInstance(${args}, &new_instance); + if (FAILED(hr)) + return hr; + + hr = new_instance->QueryInterface(__uuidof(I), + reinterpret_cast<void**>(instance)); + if (FAILED(hr)) + delete new_instance; + + return hr; + } + + template <class I, ${templ_params}> + static HRESULT CreateInitializedIID(${params}, REFIID iid, I** instance) { + T* new_instance; + HRESULT hr = CreateInstance(${args}, &new_instance); + if (FAILED(hr)) + return hr; + + hr = new_instance->QueryInterface(iid, reinterpret_cast<void**>(instance)); + if (FAILED(hr)) + delete new_instance; + + return hr; + } + +''') + + +def Emit(outfile, templ, num_args): + """Emits a template function for num_args arguments""" + mapping = { + 'templ_params': + ", ".join(["class A%d" % d for d in xrange(1, num_args + 1)]), + 'params': + ", ".join(["const A%d &a%d" % (d, d) for d in xrange(1, num_args + 1)]), + 'args': ", ".join(["a%d" % d for d in xrange(1, num_args + 1)]), + } + outfile.write(templ.substitute(mapping)) + + +NUM_ARGUMENTS = 10 + + +def Main(outfile): + """Emits the contents of initializing_coclass-inl.h.""" + outfile.write(HEADER_) + # do one to NUM_ARGUMENTS arguments + for num_args in xrange(1, 1 + NUM_ARGUMENTS): + Emit(outfile, TEMPLATE_, num_args) + outfile.write(FOOTER_) + +if (__name__ == "__main__"): + outfile = open(sys.argv[1], 'w') + Main(outfile) diff --git a/ceee/common/initializing_coclass_unittest.cc b/ceee/common/initializing_coclass_unittest.cc new file mode 100644 index 0000000..df41160 --- /dev/null +++ b/ceee/common/initializing_coclass_unittest.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2010 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. +// +// Unit tests for InitializingCoClass template mixin. +#include "ceee/common/initializing_coclass.h" + +#include <atlbase.h> +#include <atlcom.h> +#include "gtest/gtest.h" + +namespace { + +// A test class to exercise initializing coclass functionality +// We inherit from two interfaces just to play devil's advocate +class CoClassTesterBase: public CComObjectRootEx<CComSingleThreadModel>, + public IObjectWithSiteImpl<CoClassTesterBase>, + public IDispatchImpl<IDispatch> { + public: + CoClassTesterBase() { + instances_++; + last_initializer_called_ = 0; + } + + ~CoClassTesterBase() { + instances_--; + } + + BEGIN_COM_MAP(CoClassTesterBase) + COM_INTERFACE_ENTRY(IObjectWithSite) + COM_INTERFACE_ENTRY(IDispatch) + END_COM_MAP() + + public: + static int last_initializer_called_; + + // tally of our instances + static int instances_; +}; + +int CoClassTesterBase::last_initializer_called_ = 0; +int CoClassTesterBase::instances_ = 0; + +#define DECLARE_INIT_FUNCTION(args, num, result) \ +HRESULT Initialize args { \ + last_initializer_called_ = num; \ + return result; \ +} + +class CoClassSuccessTester: public CoClassTesterBase, + public InitializingCoClass<CoClassSuccessTester> { + public: + DECLARE_INIT_FUNCTION((), 0, S_OK) + DECLARE_INIT_FUNCTION((int a1), 1, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2), 2, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3), 3, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4), 4, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5), 5, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6), + 6, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7), 7, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8), 8, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8, int a9), 9, S_OK) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8, int a9, int a10), 10, S_OK) +}; + +class CoClassFailureTester: public CoClassTesterBase, + public InitializingCoClass<CoClassFailureTester> { + public: + DECLARE_INIT_FUNCTION((), 0, E_FAIL) + DECLARE_INIT_FUNCTION((int a1), 1, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2), 2, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3), 3, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4), 4, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5), 5, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6), + 6, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7), 7, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8), 8, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8, int a9), 9, E_FAIL) + DECLARE_INIT_FUNCTION((int a1, int a2, int a3, int a4, int a5, int a6, + int a7, int a8, int a9, int a10), 10, E_FAIL) +}; +} // namespace + +// Test for successful init +TEST(InitializingCoClassTest, InitSuccess) { +#define SUCCESS_TEST(num, args) \ + { \ + CComPtr<IDispatch> disp; \ + ASSERT_TRUE(SUCCEEDED(CoClassSuccessTester::CreateInitialized args)); \ + ASSERT_EQ(1, CoClassTesterBase::instances_); \ + ASSERT_EQ(num, CoClassTesterBase::last_initializer_called_); \ + disp.Release(); \ + ASSERT_EQ(0, CoClassTesterBase::instances_); \ + } \ + + SUCCESS_TEST(0, (&disp)) + SUCCESS_TEST(1, (1, &disp)) + SUCCESS_TEST(2, (1, 2, &disp)) + SUCCESS_TEST(3, (1, 2, 3, &disp)) + SUCCESS_TEST(4, (1, 2, 3, 4, &disp)) + SUCCESS_TEST(5, (1, 2, 3, 4, 5, &disp)) + SUCCESS_TEST(6, (1, 2, 3, 4, 5, 6, &disp)) + SUCCESS_TEST(7, (1, 2, 3, 4, 5, 6, 7, &disp)) + SUCCESS_TEST(8, (1, 2, 3, 4, 5, 6, 7, 8, &disp)) + SUCCESS_TEST(9, (1, 2, 3, 4, 5, 6, 7, 8, 9, &disp)) + SUCCESS_TEST(10, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &disp)) +} + +// Test for failure on initialization, proper cleanup +TEST(InitializingCoClassTest, InitFailure) { +#define FAILURE_TEST(num, args) \ + { \ + CComPtr<IDispatch> disp; \ + ASSERT_TRUE(FAILED(CoClassFailureTester::CreateInitialized args)); \ + ASSERT_EQ(0, CoClassTesterBase::instances_); \ + ASSERT_EQ(num, CoClassTesterBase::last_initializer_called_); \ + ASSERT_TRUE(NULL == disp.p); \ + } \ + + FAILURE_TEST(0, (&disp)) + FAILURE_TEST(1, (1, &disp)) + FAILURE_TEST(2, (1, 2, &disp)) + FAILURE_TEST(3, (1, 2, 3, &disp)) + FAILURE_TEST(4, (1, 2, 3, 4, &disp)) + FAILURE_TEST(5, (1, 2, 3, 4, 5, &disp)) + FAILURE_TEST(6, (1, 2, 3, 4, 5, 6, &disp)) + FAILURE_TEST(7, (1, 2, 3, 4, 5, 6, 7, &disp)) + FAILURE_TEST(8, (1, 2, 3, 4, 5, 6, 7, 8, &disp)) + FAILURE_TEST(9, (1, 2, 3, 4, 5, 6, 7, 8, 9, &disp)) + FAILURE_TEST(10, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &disp)) +} + +// Test for failure on QueryInterface, proper cleanup +TEST(InitializingCoClassTest, QueryInterfaceFailure) { +#define QI_FAILURE_TEST(num, args) \ + { \ + CComPtr<IStream> stream; \ + ASSERT_EQ(E_NOINTERFACE, CoClassSuccessTester::CreateInitialized args); \ + ASSERT_EQ(0, CoClassTesterBase::instances_); \ + ASSERT_EQ(num, CoClassTesterBase::last_initializer_called_); \ + ASSERT_TRUE(NULL == stream.p); \ + } \ + + QI_FAILURE_TEST(0, (&stream)) + QI_FAILURE_TEST(1, (1, &stream)) + QI_FAILURE_TEST(2, (1, 2, &stream)) + QI_FAILURE_TEST(3, (1, 2, 3, &stream)) + QI_FAILURE_TEST(4, (1, 2, 3, 4, &stream)) + QI_FAILURE_TEST(5, (1, 2, 3, 4, 5, &stream)) + QI_FAILURE_TEST(6, (1, 2, 3, 4, 5, 6, &stream)) + QI_FAILURE_TEST(7, (1, 2, 3, 4, 5, 6, 7, &stream)) + QI_FAILURE_TEST(8, (1, 2, 3, 4, 5, 6, 7, 8, &stream)) + QI_FAILURE_TEST(9, (1, 2, 3, 4, 5, 6, 7, 8, 9, &stream)) + QI_FAILURE_TEST(10, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &stream)) +} + + +// Test for proper cleanup on Initialize failure for CreateInstance +TEST(InitializingCoClassTest, InitFailureOnCreateInstance) { +#define INIT_FAILURE_TEST(num, args) \ + { \ + CoClassSuccessTester* tester; \ + ASSERT_EQ(E_FAIL, CoClassFailureTester::CreateInstance args); \ + ASSERT_EQ(0, CoClassTesterBase::instances_); \ + ASSERT_EQ(num, CoClassTesterBase::last_initializer_called_); \ + ASSERT_TRUE(NULL == tester); \ + } \ + + QI_FAILURE_TEST(0, (&stream)) + QI_FAILURE_TEST(1, (1, &stream)) + QI_FAILURE_TEST(2, (1, 2, &stream)) + QI_FAILURE_TEST(3, (1, 2, 3, &stream)) + QI_FAILURE_TEST(4, (1, 2, 3, 4, &stream)) + QI_FAILURE_TEST(5, (1, 2, 3, 4, 5, &stream)) + QI_FAILURE_TEST(6, (1, 2, 3, 4, 5, 6, &stream)) + QI_FAILURE_TEST(7, (1, 2, 3, 4, 5, 6, 7, &stream)) + QI_FAILURE_TEST(8, (1, 2, 3, 4, 5, 6, 7, 8, &stream)) + QI_FAILURE_TEST(9, (1, 2, 3, 4, 5, 6, 7, 8, 9, &stream)) + QI_FAILURE_TEST(10, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &stream)) +} + +namespace { + +// Class that does not like to be copied. +class DoNotCopy { + public: + DoNotCopy() { + } + DoNotCopy(const DoNotCopy& copy) { + EXPECT_TRUE(false); + } +}; + +// Simple class we will use to test if reference parameters are being honoured +// or if in fact they are being copied somewhere inbetween CreateInstance() and +// Initialize(). +class CoClassCopyTester + : public CComObjectRootEx<CComSingleThreadModel>, + public IDispatchImpl<IDispatch>, + public InitializingCoClass<CoClassCopyTester> { + public: + BEGIN_COM_MAP(CoClassCopyTester) + COM_INTERFACE_ENTRY(IDispatch) + END_COM_MAP() + + HRESULT Initialize(const DoNotCopy& data) { + // If we got this far it means we didn't run the copy constructor + return S_OK; + } +}; +} // namespace + +// Test to make sure that passing a reference parameter doesn't trigger a copy +// on the argument. +TEST(InitializingCoClassTest, TestReferenceParams) { + DoNotCopy data; + CComPtr<IDispatch> tester; + + HRESULT hr = CoClassCopyTester::CreateInitialized(data, &tester); + ASSERT_EQ(S_OK, hr); +} diff --git a/ceee/common/install_utils.cc b/ceee/common/install_utils.cc new file mode 100644 index 0000000..64b23fb --- /dev/null +++ b/ceee/common/install_utils.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2010 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. +// +// Utilities related to installation of the CEEE. + +#include "ceee/common/install_utils.h" + +#include <windows.h> + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/path_service.h" + +namespace installer_util { + namespace switches { + // TODO(joi@chromium.org) Move to chrome/installer/util_constants.h + const wchar_t kEnableCeee[] = L"enable-ceee"; + + // TODO(joi@chromium.org) Remove from here, reuse from + // chrome/installer/util_constants.h + const wchar_t kChromeFrame[] = L"chrome-frame"; + } +} + +namespace ceee_install_utils { + +bool ShouldRegisterCeee() { + // First check if it's a developer running us explicitly. + FilePath exe_path; + if (PathService::Get(base::FILE_EXE, &exe_path)) { + if (exe_path.BaseName() == FilePath(L"regsvr32.exe")) { + return true; + } + } + + // Failing that, it's some kind of install scenario, so the + // --enable-ceee flag must be provided. It should be ignored + // unless --chrome-frame is also specified, so we check for + // both. + CommandLine current_command_line(CommandLine::NO_PROGRAM); + current_command_line.ParseFromString(::GetCommandLine()); + if (current_command_line.HasSwitch(installer_util::switches::kEnableCeee) && + current_command_line.HasSwitch(installer_util::switches::kChromeFrame)) { + return true; + } else { + return false; + } +} + +} // namespace ceee_install_utils diff --git a/ceee/common/install_utils.h b/ceee/common/install_utils.h new file mode 100644 index 0000000..c5ed966 --- /dev/null +++ b/ceee/common/install_utils.h @@ -0,0 +1,40 @@ +// Copyright (c) 2010 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. +// +// Utilities related to installation of the CEEE. + +#ifndef CEEE_COMMON_INSTALL_UTILS_H_ +#define CEEE_COMMON_INSTALL_UTILS_H_ + +// This is the only way we define DllRegisterServer in CEEE. +// It guarantees that we check whether registration should be +// done before actually doing it. +// +// Instructions: +// +// a) In your module's main compilation unit, define +// a function DllRegisterServerImpl with the same signature +// as DllRegisterServer. +// +// b) Below your DllRegisterServerImpl function, put this +// macro. +#define CEEE_DEFINE_DLL_REGISTER_SERVER() \ + STDAPI DllRegisterServer(void) { \ + if (!ceee_install_utils::ShouldRegisterCeee()) { \ + return S_OK; \ + } else { \ + return DllRegisterServerImpl(); \ + } \ + } + +namespace ceee_install_utils { + +// Returns true if the --enable-ceee flag was passed to the process +// that loaded this DLL, or if the process loading the DLL is +// regsvr32 (i.e. it's a developer that explicitly wants to register). +bool ShouldRegisterCeee(); + +} // namespace ceee_install_utils + +#endif // CEEE_COMMON_INSTALL_UTILS_H_ diff --git a/ceee/common/install_utils_unittest.cc b/ceee/common/install_utils_unittest.cc new file mode 100644 index 0000000..61f3b54 --- /dev/null +++ b/ceee/common/install_utils_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2010 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. +// +// Unit tests for utilities related to installation of the CEEE. + +#include "ceee/common/install_utils.h" + +#include "base/base_paths.h" +#include "ceee/testing/utils/mock_win32.h" +#include "ceee/testing/utils/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using testing::_; +using testing::DoAll; +using testing::CopyStringToArgument; +using testing::Return; + +TEST(ShouldRegisterCeee, FalseUnlessRegsvr32OrCommandLine) { + // We can safely assume that the real command line and + // process name for the unit test process does not satisfy + // either condition. + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, TrueIfRegsvr32) { + testing::PathServiceOverrider override(base::FILE_EXE, + FilePath(L"c:\\windows\\system32\\regsvr32.exe")); + ASSERT_TRUE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, FalseIfRegsvr32NotLastPathComponent) { + testing::PathServiceOverrider override(base::FILE_EXE, + FilePath(L"c:\\windows\\regsvr32.exe\\foobar.exe")); + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, TrueIfBothFlagsOnCommandLine) { + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, GetCommandLine()).WillOnce( + Return(const_cast<wchar_t*>( + L"mini_installer.exe --enable-ceee --chrome-frame"))); + ASSERT_TRUE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, FalseIfOneFlagOnCommandLine) { + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, GetCommandLine()) + .WillOnce(Return(const_cast<wchar_t*>( + L"mini_installer.exe --enable-ceee"))) + .WillOnce(Return(const_cast<wchar_t*>( + L"mini_installer.exe --chrome-frame"))); + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, FalseIfInvertedFlagOnCommandLine) { + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, GetCommandLine()).WillOnce( + Return(const_cast<wchar_t*>( + L"mini_installer.exe --noenable-ceee --chrome-frame"))); + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); +} + +TEST(ShouldRegisterCeee, FalseIfBogusFlagOnCommandLine) { + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, GetCommandLine()).WillOnce( + Return(const_cast<wchar_t*>( + L"mini_installer.exe --enable-ceeez --chrome-frame"))); + ASSERT_FALSE(ceee_install_utils::ShouldRegisterCeee()); +} + +namespace dll_register_server_test { + +// Gets set to true by DllRegisterServer. +bool registration_was_done = false; + +// This is for unit testing only. Because we're in an anonymous namespace +// it's separate from everything else. +STDAPI DllRegisterServerImpl() { + registration_was_done = true; + return S_OK; +} + +CEEE_DEFINE_DLL_REGISTER_SERVER() + +TEST(ConditionalDllRegisterServer, ShouldNotInstall) { + // We can safely assume that the real command line and + // process name for the unit test process does not satisfy + // either condition. + registration_was_done = false; + DllRegisterServer(); + ASSERT_FALSE(registration_was_done); +} + +TEST(ConditionalDllRegisterServer, ShouldInstall) { + // This is just one way to make ShouldRegisterCeee() + // return true; we've unit tested elsewhere that the other cases + // are handled correctly. + testing::PathServiceOverrider override(base::FILE_EXE, + FilePath(L"c:\\windows\\system32\\regsvr32.exe")); + + registration_was_done = false; + DllRegisterServer(); + ASSERT_TRUE(registration_was_done); +} + +} // namespace dll_register_server_test + + +} // namespace diff --git a/ceee/common/np_browser_functions.cc b/ceee/common/np_browser_functions.cc new file mode 100644 index 0000000..b421f7c --- /dev/null +++ b/ceee/common/np_browser_functions.cc @@ -0,0 +1,435 @@ +// Copyright (c) 2010 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 "ceee/common/np_browser_functions.h" + +#include "base/logging.h" + +namespace npapi { + +// global function pointers (within this namespace) for the NPN functions. +NPN_GetURLProcPtr g_geturl = NULL; +NPN_PostURLProcPtr g_posturl = NULL; +NPN_RequestReadProcPtr g_requestread = NULL; +NPN_NewStreamProcPtr g_newstream = NULL; +NPN_WriteProcPtr g_write = NULL; +NPN_DestroyStreamProcPtr g_destroystream = NULL; +NPN_StatusProcPtr g_status = NULL; +NPN_UserAgentProcPtr g_useragent = NULL; +NPN_MemAllocProcPtr g_memalloc = NULL; +NPN_MemFreeProcPtr g_memfree = NULL; +NPN_MemFlushProcPtr g_memflush = NULL; +NPN_ReloadPluginsProcPtr g_reloadplugins = NULL; +NPN_GetJavaEnvProcPtr g_getJavaEnv = NULL; +NPN_GetJavaPeerProcPtr g_getJavaPeer = NULL; +NPN_GetURLNotifyProcPtr g_geturlnotify = NULL; +NPN_PostURLNotifyProcPtr g_posturlnotify = NULL; +NPN_GetValueProcPtr g_getvalue = NULL; +NPN_SetValueProcPtr g_setvalue = NULL; +NPN_InvalidateRectProcPtr g_invalidaterect = NULL; +NPN_InvalidateRegionProcPtr g_invalidateregion = NULL; +NPN_ForceRedrawProcPtr g_forceredraw = NULL; +NPN_GetStringIdentifierProcPtr g_getstringidentifier = NULL; +NPN_GetStringIdentifiersProcPtr g_getstringidentifiers = NULL; +NPN_GetIntIdentifierProcPtr g_getintidentifier = NULL; +NPN_IdentifierIsStringProcPtr g_identifierisstring = NULL; +NPN_UTF8FromIdentifierProcPtr g_utf8fromidentifier = NULL; +NPN_IntFromIdentifierProcPtr g_intfromidentifier = NULL; +NPN_CreateObjectProcPtr g_createobject = NULL; +NPN_RetainObjectProcPtr g_retainobject = NULL; +NPN_ReleaseObjectProcPtr g_releaseobject = NULL; +NPN_InvokeProcPtr g_invoke = NULL; +NPN_InvokeDefaultProcPtr g_invoke_default = NULL; +NPN_EvaluateProcPtr g_evaluate = NULL; +NPN_GetPropertyProcPtr g_getproperty = NULL; +NPN_SetPropertyProcPtr g_setproperty = NULL; +NPN_RemovePropertyProcPtr g_removeproperty = NULL; +NPN_HasPropertyProcPtr g_hasproperty = NULL; +NPN_HasMethodProcPtr g_hasmethod = NULL; +NPN_ReleaseVariantValueProcPtr g_releasevariantvalue = NULL; +NPN_SetExceptionProcPtr g_setexception = NULL; +NPN_PushPopupsEnabledStateProcPtr g_pushpopupsenabledstate = NULL; +NPN_PopPopupsEnabledStateProcPtr g_poppopupsenabledstate = NULL; +NPN_EnumerateProcPtr g_enumerate = NULL; +NPN_PluginThreadAsyncCallProcPtr g_pluginthreadasynccall = NULL; +NPN_ConstructProcPtr g_construct = NULL; + +// Must be called prior to calling any of the browser functions below. +void InitializeBrowserFunctions(NPNetscapeFuncs* functions) { + CHECK(functions); + DCHECK(g_geturl == NULL || g_geturl == functions->geturl); + + g_geturl = functions->geturl; + g_posturl = functions->posturl; + g_requestread = functions->requestread; + g_newstream = functions->newstream; + g_write = functions->write; + g_destroystream = functions->destroystream; + g_status = functions->status; + g_useragent = functions->uagent; + g_memalloc = functions->memalloc; + g_memfree = functions->memfree; + g_memflush = functions->memflush; + g_reloadplugins = functions->reloadplugins; + g_getJavaEnv = functions->getJavaEnv; + g_getJavaPeer = functions->getJavaPeer; + g_geturlnotify = functions->geturlnotify; + g_posturlnotify = functions->posturlnotify; + g_getvalue = functions->getvalue; + g_setvalue = functions->setvalue; + g_invalidaterect = functions->invalidaterect; + g_invalidateregion = functions->invalidateregion; + g_forceredraw = functions->forceredraw; + g_getstringidentifier = functions->getstringidentifier; + g_getstringidentifiers = functions->getstringidentifiers; + g_getintidentifier = functions->getintidentifier; + g_identifierisstring = functions->identifierisstring; + g_utf8fromidentifier = functions->utf8fromidentifier; + g_intfromidentifier = functions->intfromidentifier; + g_createobject = functions->createobject; + g_retainobject = functions->retainobject; + g_releaseobject = functions->releaseobject; + g_invoke = functions->invoke; + g_invoke_default = functions->invokeDefault; + g_evaluate = functions->evaluate; + g_getproperty = functions->getproperty; + g_setproperty = functions->setproperty; + g_removeproperty = functions->removeproperty; + g_hasproperty = functions->hasproperty; + g_hasmethod = functions->hasmethod; + g_releasevariantvalue = functions->releasevariantvalue; + g_setexception = functions->setexception; + g_pushpopupsenabledstate = functions->pushpopupsenabledstate; + g_poppopupsenabledstate = functions->poppopupsenabledstate; + g_enumerate = functions->enumerate; + g_pluginthreadasynccall = functions->pluginthreadasynccall; + g_construct = functions->construct; +} + +void UninitializeBrowserFunctions() { +// We skip doing this in the official build as it doesn't serve much purpose +// during shutdown. The reason for it being here in the other types of builds +// is to spot potential browser bugs whereby the browser leaves living objects +// in our DLL after shutdown has been called. In theory those objects could +// trigger a call to the browser functions after shutdown has been called +// and for non official builds we want that to simply crash. +// For official builds we leave the function pointers around since they +// continue to valid. + g_geturl = NULL; + g_posturl = NULL; + g_requestread = NULL; + g_newstream = NULL; + g_write = NULL; + g_destroystream = NULL; + g_status = NULL; + g_useragent = NULL; + g_memalloc = NULL; + g_memfree = NULL; + g_memflush = NULL; + g_reloadplugins = NULL; + g_getJavaEnv = NULL; + g_getJavaPeer = NULL; + g_geturlnotify = NULL; + g_posturlnotify = NULL; + g_getvalue = NULL; + g_setvalue = NULL; + g_invalidaterect = NULL; + g_invalidateregion = NULL; + g_forceredraw = NULL; + g_getstringidentifier = NULL; + g_getstringidentifiers = NULL; + g_getintidentifier = NULL; + g_identifierisstring = NULL; + g_utf8fromidentifier = NULL; + g_intfromidentifier = NULL; + g_createobject = NULL; + g_retainobject = NULL; + g_releaseobject = NULL; + g_invoke = NULL; + g_invoke_default = NULL; + g_evaluate = NULL; + g_getproperty = NULL; + g_setproperty = NULL; + g_removeproperty = NULL; + g_hasproperty = NULL; + g_hasmethod = NULL; + g_releasevariantvalue = NULL; + g_setexception = NULL; + g_pushpopupsenabledstate = NULL; + g_poppopupsenabledstate = NULL; + g_enumerate = NULL; + g_pluginthreadasynccall = NULL; + g_construct = NULL; +} + +bool IsInitialized() { + // We only check one function for convenience. + return g_getvalue != NULL; +} + +// Function stubs for functions that the host browser implements. + +NPError GetURL(NPP instance, const char* URL, const char* window) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_geturl(instance, URL, window); +} + +NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len, + const char* buf, NPBool file) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_posturl(instance, URL, window, len, buf, file); +} + +NPError RequestRead(NPStream* stream, NPByteRange* rangeList) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_requestread(stream, rangeList); +} + +NPError NewStream(NPP instance, NPMIMEType type, const char* window, + NPStream** stream) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_newstream(instance, type, window, stream); +} + +int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_write(instance, stream, len, buffer); +} + +NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_destroystream(instance, stream, reason); +} + +void Status(NPP instance, const char* message) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_status(instance, message); +} + +const char* UserAgent(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_useragent(instance); +} + +void* MemAlloc(uint32 size) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memalloc(size); +} + +void MemFree(void* ptr) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memfree(ptr); +} + +uint32 MemFlush(uint32 size) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memflush(size); +} + +void ReloadPlugins(NPBool reloadPages) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_reloadplugins(reloadPages); +} + +void* GetJavaEnv() { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getJavaEnv(); +} + +void* GetJavaPeer(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getJavaPeer(instance); +} + +NPError GetURLNotify(NPP instance, const char* URL, const char* window, + void* notifyData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_geturlnotify(instance, URL, window, notifyData); +} + +NPError PostURLNotify(NPP instance, const char* URL, const char* window, + uint32 len, const char* buf, NPBool file, + void* notifyData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_posturlnotify(instance, URL, window, len, buf, file, notifyData); +} + +NPError GetValue(NPP instance, NPNVariable variable, void* ret_value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getvalue(instance, variable, ret_value); +} + +NPError SetValue(NPP instance, NPPVariable variable, void* value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setvalue(instance, variable, value); +} + +void InvalidateRect(NPP instance, NPRect* rect) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invalidaterect(instance, rect); +} + +void InvalidateRegion(NPP instance, NPRegion region) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invalidateregion(instance, region); +} + +void ForceRedraw(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_forceredraw(instance); +} + +void ReleaseVariantValue(NPVariant* variant) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_releasevariantvalue(variant); +} + +NPIdentifier GetStringIdentifier(const NPUTF8* name) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getstringidentifier(name); +} + +void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getstringidentifiers(names, nameCount, identifiers); +} + +NPIdentifier GetIntIdentifier(int32_t intid) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getintidentifier(intid); +} + +int32_t IntFromIdentifier(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_intfromidentifier(identifier); +} + +bool IdentifierIsString(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_identifierisstring(identifier); +} + +NPUTF8* UTF8FromIdentifier(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_utf8fromidentifier(identifier); +} + +NPObject* CreateObject(NPP instance, NPClass* aClass) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_createobject(instance, aClass); +} + +NPObject* RetainObject(NPObject* obj) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_retainobject(obj); +} + +void ReleaseObject(NPObject* obj) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_releaseobject(obj); +} + +bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, unsigned argCount, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invoke(npp, obj, methodName, args, argCount, result); +} + +bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + unsigned argCount, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invoke_default(npp, obj, args, argCount, result); +} + +bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_evaluate(npp, obj, script, result); +} + +bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getproperty(npp, obj, propertyName, result); +} + +bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setproperty(npp, obj, propertyName, value); +} + +bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_hasproperty(npp, npobj, propertyName); +} + +bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_hasmethod(npp, npobj, methodName); +} + +bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_removeproperty(npp, obj, propertyName); +} + +void SetException(NPObject* obj, const NPUTF8* message) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setexception(obj, message); +} + +void PushPopupsEnabledState(NPP npp, NPBool enabled) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_pushpopupsenabledstate(npp, enabled); +} + +void PopPopupsEnabledState(NPP npp) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_poppopupsenabledstate(npp); +} + +bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier, + uint32_t* count) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_enumerate(npp, obj, identifier, count); +} + +void PluginThreadAsyncCall(NPP instance, + void (*func)(void*), // NOLINT + void* userData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_pluginthreadasynccall(instance, func, userData); +} + +bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_construct(npp, obj, args, argCount, result); +} + +std::string StringFromIdentifier(NPIdentifier identifier) { + std::string ret; + NPUTF8* utf8 = UTF8FromIdentifier(identifier); + if (utf8) { + ret = utf8; + MemFree(utf8); + } + return ret; +} + +} // namespace npapi + +void AllocateStringVariant(const std::string& str, NPVariant* var) { + DCHECK(var); + + int len = str.length(); + NPUTF8* buffer = reinterpret_cast<NPUTF8*>(npapi::MemAlloc(len + 1)); + if (buffer) { + buffer[len] = '\0'; + memcpy(buffer, str.c_str(), len); + STRINGN_TO_NPVARIANT(buffer, len, *var); + } else { + NULL_TO_NPVARIANT(*var); + } +} diff --git a/ceee/common/np_browser_functions.h b/ceee/common/np_browser_functions.h new file mode 100644 index 0000000..8ab8b63 --- /dev/null +++ b/ceee/common/np_browser_functions.h @@ -0,0 +1,260 @@ +// Copyright (c) 2010 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 CEEE_COMMON_NP_BROWSER_FUNCTIONS_H_ +#define CEEE_COMMON_NP_BROWSER_FUNCTIONS_H_ + +#include <string> +#include "base/logging.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace npapi { + +// Must be called prior to calling any of the browser functions below. +void InitializeBrowserFunctions(NPNetscapeFuncs* functions); +void UninitializeBrowserFunctions(); + +// Returns true iff InitializeBrowserFunctions has been called successully. +bool IsInitialized(); + +// Function stubs for functions that the host browser implements. + +NPError GetURL(NPP instance, const char* URL, const char* window); + +NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len, + const char* buf, NPBool file); + +NPError RequestRead(NPStream* stream, NPByteRange* rangeList); + +NPError NewStream(NPP instance, NPMIMEType type, const char* window, + NPStream** stream); + +int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer); + +NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason); + +void Status(NPP instance, const char* message); + +const char* UserAgent(NPP instance); + +void* MemAlloc(uint32 size); + +void MemFree(void* ptr); + +uint32 MemFlush(uint32 size); + +void ReloadPlugins(NPBool reloadPages); + +void* GetJavaEnv(); + +void* GetJavaPeer(NPP instance); + +NPError GetURLNotify(NPP instance, const char* URL, const char* window, + void* notifyData); + +NPError PostURLNotify(NPP instance, const char* URL, const char* window, + uint32 len, const char* buf, NPBool file, + void* notifyData); + +NPError GetValue(NPP instance, NPNVariable variable, void* ret_value); + +NPError SetValue(NPP instance, NPPVariable variable, void* value); + +void InvalidateRect(NPP instance, NPRect* rect); + +void InvalidateRegion(NPP instance, NPRegion region); + +void ForceRedraw(NPP instance); + +void ReleaseVariantValue(NPVariant* variant); + +NPIdentifier GetStringIdentifier(const NPUTF8* name); + +void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers); + +NPIdentifier GetIntIdentifier(int32_t intid); + +int32_t IntFromIdentifier(NPIdentifier identifier); + +bool IdentifierIsString(NPIdentifier identifier); + +NPUTF8* UTF8FromIdentifier(NPIdentifier identifier); + +NPObject* CreateObject(NPP, NPClass* aClass); + +NPObject* RetainObject(NPObject* obj); + +void ReleaseObject(NPObject* obj); + +bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, unsigned argCount, NPVariant* result); + +bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + unsigned argCount, NPVariant* result); + +bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result); + +bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + NPVariant* result); + +bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value); + +bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); + +bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName); + +bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName); + +void SetException(NPObject* obj, const NPUTF8* message); + +void PushPopupsEnabledState(NPP npp, NPBool enabled); + +void PopPopupsEnabledState(NPP npp); + +bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier, + uint32_t* count); + +void PluginThreadAsyncCall(NPP instance, + void (*func)(void*), // NOLINT + void* userData); + +bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount, + NPVariant* result); + +// Helper routine that wraps UTF8FromIdentifier to convert a string identifier +// to an STL string. It's not super efficient since it could possibly do two +// heap allocations (STL string has a stack based buffer for smaller strings). +// For debugging purposes it is useful. +std::string StringFromIdentifier(NPIdentifier identifier); + +} // namespace npapi + +// Simple helper class for freeing NPVariants at the end of a scope. +class ScopedNpVariant : public NPVariant { + public: + ScopedNpVariant() { + VOID_TO_NPVARIANT(*this); + } + + ~ScopedNpVariant() { + Free(); + } + + void Free() { + npapi::ReleaseVariantValue(this); + VOID_TO_NPVARIANT(*this); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedNpVariant); +}; + +// Simple helper class for freeing NPObjects at the end of a scope. +template <typename NpoType = NPObject> +class ScopedNpObject { + public: + ScopedNpObject() : npo_(NULL) { + } + + explicit ScopedNpObject(NpoType* npo) : npo_(npo) { + } + + ~ScopedNpObject() { + Free(); + } + + NpoType* get() const { + return npo_; + } + + operator NpoType*() const { + return npo_; + } + + NpoType* operator->() const { + return npo_; + } + + ScopedNpObject<NpoType>& operator=(NpoType* npo) { + if (npo != npo_) { + DCHECK(npo_ == NULL); + npapi::RetainObject(npo); + npo_ = npo; + } + return *this; + } + + void Free() { + if (npo_) { + npapi::ReleaseObject(npo_); + npo_ = NULL; + } + } + + NpoType** Receive() { + DCHECK(npo_ == NULL) << "Object leak. Pointer must be NULL"; + return &npo_; + } + + NpoType* Detach() { + NpoType* p = npo_; + npo_ = NULL; + return p; + } + + void Attach(NpoType* p) { + DCHECK(npo_ == NULL); + npo_ = p; + } + + NpoType* Copy() const { + if (npo_ != NULL) + npapi::RetainObject(npo_); + return npo_; + } + + bool Invoke0(NPP npp, NPIdentifier id, NPVariant* result) { + return npapi::Invoke(npp, npo_, id, NULL, 0, result); + } + bool Invoke0(NPP npp, const NPUTF8* name, NPVariant* result) { + return Invoke0(npp, npapi::GetStringIdentifier(name), result); + } + + bool Invoke1(NPP npp, NPIdentifier id, const NPVariant &arg1, + NPVariant* result) { + return npapi::Invoke(npp, npo_, id, &arg1, 1, result); + } + bool Invoke1(NPP npp, const NPUTF8* name, const NPVariant &arg1, + NPVariant* result) { + return Invoke1(npp, npapi::GetStringIdentifier(name), arg1, result); + } + bool InvokeN(NPP npp, NPIdentifier id, const NPVariant* args, unsigned argc, + NPVariant* result) { + return npapi::Invoke(npp, npo_, id, args, argc, result); + } + bool InvokeN(NPP npp, const NPUTF8* name, const NPVariant* args, + unsigned argc, NPVariant* result) { + return Invoke1(npp, npapi::GetStringIdentifier(name), args, argc, result); + } + + bool GetProperty(NPP npp, NPIdentifier id, NPVariant* result) { + return npapi::GetProperty(npp, npo_, id, result); + } + + bool GetProperty(NPP npp, const NPUTF8* name, NPVariant* result) { + return GetProperty(npp, npapi::GetStringIdentifier(name), result); + } + + private: + NpoType* npo_; + DISALLOW_COPY_AND_ASSIGN(ScopedNpObject); +}; + +// Allocates a new NPUTF8 string and assigns it to the variant. +// If memory allocation fails, the variant type will be set to NULL. +// The memory allocation is done via the npapi browser functions. +void AllocateStringVariant(const std::string& str, NPVariant* var); + +#endif // CEEE_COMMON_NP_BROWSER_FUNCTIONS_H_ diff --git a/ceee/common/npobject_impl.cc b/ceee/common/npobject_impl.cc new file mode 100644 index 0000000..cf1a64c --- /dev/null +++ b/ceee/common/npobject_impl.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2010 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. +// +// Implementation of NPAPI object base class. +#include "ceee/common/npobject_impl.h" + +void NpObjectBase::Invalidate() { +} + +bool NpObjectBase::HasMethod(NPIdentifier name) { + return false; +} + +bool NpObjectBase::Invoke(NPIdentifier name, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + return false; +} + +bool NpObjectBase::InvokeDefault(const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + return false; +} + +bool NpObjectBase::HasProperty(NPIdentifier name) { + return false; +} + +bool NpObjectBase::GetProperty(NPIdentifier name, NPVariant* result) { + return false; +} + +bool NpObjectBase::SetProperty(NPIdentifier name, const NPVariant* value) { + return false; +} + +bool NpObjectBase::RemoveProperty(NPIdentifier name) { + return false; +} + +bool NpObjectBase::Enumeration(NPIdentifier** value, uint32_t* count) { + return false; +} + +bool NpObjectBase::Construct(const NPVariant* args, uint32_t argCount, + NPVariant* result) { + return false; +} + + +void NpObjectBase::NPDeallocate(NPObject* obj) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + delete base; +} + +void NpObjectBase::NPInvalidate(NPObject* obj) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + base->Invalidate(); +} + +bool NpObjectBase::NPHasMethod(NPObject* obj, NPIdentifier name) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->HasMethod(name); +} + +bool NpObjectBase::NPInvoke(NPObject* obj, + NPIdentifier name, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->Invoke(name, args, arg_count, result); +} + +bool NpObjectBase::NPInvokeDefault(NPObject* obj, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->InvokeDefault(args, arg_count, result); +} + +bool NpObjectBase::NPHasProperty(NPObject* obj, NPIdentifier name) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->HasProperty(name); +} + +bool NpObjectBase::NPGetProperty(NPObject* obj, + NPIdentifier name, + NPVariant* result) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->GetProperty(name, result); +} + +bool NpObjectBase::NPSetProperty(NPObject* obj, + NPIdentifier name, + const NPVariant* value) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->SetProperty(name, value); +} + +bool NpObjectBase::NPRemoveProperty(NPObject* obj, + NPIdentifier name) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->RemoveProperty(name); +} + +bool NpObjectBase::NPEnumeration(NPObject* obj, NPIdentifier** value, + uint32_t* count) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->Enumeration(value, count); +} + +bool NpObjectBase::NPConstruct(NPObject* obj, const NPVariant* args, + uint32_t arg_count, NPVariant* result) { + NpObjectBase* base = static_cast<NpObjectBase*>(obj); + return base->Construct(args, arg_count, result); +} diff --git a/ceee/common/npobject_impl.h b/ceee/common/npobject_impl.h new file mode 100644 index 0000000..9f0dee4 --- /dev/null +++ b/ceee/common/npobject_impl.h @@ -0,0 +1,119 @@ +// Copyright (c) 2010 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. +// +// Base classes to unclutter the writing of NPAPI objects. +#ifndef CEEE_COMMON_NPOBJECT_IMPL_H_ +#define CEEE_COMMON_NPOBJECT_IMPL_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/nphostapi.h" + +// Base class for NPAPI objects. +class NpObjectBase: public NPObject { + public: + explicit NpObjectBase(NPP npp) : npp_(npp) { + } + virtual ~NpObjectBase() { + } + + protected: + // @name NP Object interface. + // Override these to implement your object's functionality. + // For documentation on these methods, see the corresponding callback + // function description in http://developer.mozilla.org/en/NPObject + // @{ + virtual void Invalidate(); + virtual bool HasMethod(NPIdentifier name); + virtual bool Invoke(NPIdentifier name, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); + virtual bool InvokeDefault(const NPVariant* args, + uint32_t argCount, + NPVariant* result); + virtual bool HasProperty(NPIdentifier name); + virtual bool GetProperty(NPIdentifier name, NPVariant* result); + virtual bool SetProperty(NPIdentifier name, const NPVariant* value); + virtual bool RemoveProperty(NPIdentifier name); + virtual bool Enumeration(NPIdentifier** value, uint32_t* count); + virtual bool Construct(const NPVariant* args, uint32_t argCount, + NPVariant* result); + // @} + + // @name NP callback functions. + // @{ + static void NPDeallocate(NPObject* obj); + static void NPInvalidate(NPObject* obj); + static bool NPHasMethod(NPObject* obj, NPIdentifier name); + static bool NPInvoke(NPObject* npobj, + NPIdentifier name, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); + static bool NPInvokeDefault(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); + static bool NPHasProperty(NPObject* npobj, NPIdentifier name); + static bool NPGetProperty(NPObject* npobj, + NPIdentifier name, + NPVariant* result); + static bool NPSetProperty(NPObject* npobj, + NPIdentifier name, + const NPVariant* value); + static bool NPRemoveProperty(NPObject* npobj, + NPIdentifier name); + static bool NPEnumeration(NPObject* npobj, NPIdentifier** value, + uint32_t* count); + static bool NPConstruct(NPObject* npobj, const NPVariant* args, + uint32_t arg_count, NPVariant* result); + // @} + + NPP npp() const { return npp_; } + private: + // The NPP instance we belong to. + NPP npp_; + + DISALLOW_COPY_AND_ASSIGN(NpObjectBase); +}; + + +// Derive your NPAPI object class from NpObjectImpl<MyClass>. +template <class ImplClass> class NpObjectImpl: public NpObjectBase { + public: + explicit NpObjectImpl(NPP npp) : NpObjectBase(npp) { + } + + static NPObject* NPAllocate(NPP npp, NPClass* np_class) { + NPObject* object = new ImplClass(npp); + object->_class = np_class; + object->referenceCount = 1; + return object; + } + + static NPClass* object_class() { return &object_class_; } + + private: + static NPClass object_class_; + + DISALLOW_COPY_AND_ASSIGN(NpObjectImpl); +}; + +template <class ImplClass> NPClass NpObjectImpl<ImplClass>::object_class_ = { + NP_CLASS_STRUCT_VERSION, + ImplClass::NPAllocate, // allocate; + ImplClass::NPDeallocate, // deallocate; + ImplClass::NPInvalidate, // invalidate; + ImplClass::NPHasMethod, // hasMethod; + ImplClass::NPInvoke, // invoke; + ImplClass::NPInvokeDefault, // invokeDefault; + ImplClass::NPHasProperty, // hasProperty; + ImplClass::NPGetProperty, // getProperty; + ImplClass::NPSetProperty, // setProperty; + ImplClass::NPRemoveProperty, // removeProperty; + ImplClass::NPEnumeration, // enumerate + ImplClass::NPConstruct, // construct +}; + +#endif // CEEE_COMMON_NPOBJECT_IMPL_H_ diff --git a/ceee/common/npobject_impl_unittest.cc b/ceee/common/npobject_impl_unittest.cc new file mode 100644 index 0000000..5b40170 --- /dev/null +++ b/ceee/common/npobject_impl_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2010 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. +// +// Tests for NPAPI Object Wrapper classes. + +#include "ceee/common/npobject_impl.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::_; +using testing::Return; + +namespace { + +// A bare bones implementation class which really just uses the default +// implementations of NpObjectImpl/NpObjectBase so we can test those +// wrapper classes for correct default NPAPI functionality. +class BareNpObject : public NpObjectImpl<BareNpObject> { + public: + explicit BareNpObject(NPP npp) : NpObjectImpl<BareNpObject>(npp) { + } + ~BareNpObject() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(BareNpObject); +}; + +// A mock class which allows us to verify that the corresponding NPObject +// functions are called when a client calls functions through the NPClass +// struct's function pointers. +class MockNpObject : public NpObjectImpl<MockNpObject> { + public: + explicit MockNpObject(NPP npp) : NpObjectImpl<MockNpObject>(npp) { + } + ~MockNpObject() { + } + + MOCK_METHOD0(Invalidate, void()); + MOCK_METHOD1(HasMethod, bool(NPIdentifier name)); + MOCK_METHOD4(Invoke, bool(NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result)); + MOCK_METHOD3(InvokeDefault, bool(const NPVariant* args, uint32_t argCount, + NPVariant* result)); + MOCK_METHOD1(HasProperty, bool(NPIdentifier name)); + MOCK_METHOD2(GetProperty, bool(NPIdentifier name, NPVariant* result)); + MOCK_METHOD2(SetProperty, bool(NPIdentifier name, const NPVariant* value)); + MOCK_METHOD1(RemoveProperty, bool(NPIdentifier name)); + MOCK_METHOD2(Enumeration, bool(NPIdentifier** value, uint32_t* count)); + MOCK_METHOD3(Construct, bool(const NPVariant* args, uint32_t argCount, + NPVariant* result)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockNpObject); +}; + +// Test the functions in the NPClass struct to ensure that the correct +// NP*FunctionPtr function defaults come out. We use +// https://developer.mozilla.org/en/NPClass as a rough guide for what default +// values should be. +TEST(NpApiWrappers, NpObjectDefaults) { + // Grab the NPClass structure so we can access the functions that have been + // hooked up by this particular implementation. + NPClass* np_class = BareNpObject::object_class(); + ASSERT_TRUE(np_class != NULL); + + // Create an instance of our barebones class so we can use it in the function + // calls from the NPClass struct. Also exercises allocation. + NPObject* np_object = np_class->allocate(NULL, np_class); + ASSERT_TRUE(np_object != NULL); + + EXPECT_GE(np_class->structVersion, (uint32_t)3); + EXPECT_FALSE(np_class->hasMethod(np_object, NULL)); + EXPECT_FALSE(np_class->invoke(np_object, NULL, NULL, 0, NULL)); + EXPECT_FALSE(np_class->invokeDefault(np_object, NULL, 0, NULL)); + EXPECT_FALSE(np_class->hasProperty(np_object, NULL)); + EXPECT_FALSE(np_class->getProperty(np_object, NULL, NULL)); + EXPECT_FALSE(np_class->setProperty(np_object, NULL, NULL)); + EXPECT_FALSE(np_class->removeProperty(np_object, NULL)); + EXPECT_FALSE(np_class->enumerate(np_object, NULL, NULL)); + EXPECT_FALSE(np_class->construct(np_object, NULL, 0, NULL)); + + // Invalidation and deallocation. No return values to check here - just + // exercising the code. Deallocation should clean up the object for us. + np_class->invalidate(np_object); + np_class->deallocate(np_object); + np_object = NULL; +} + +// Uses a Mock class to ensure that the correct functions are being called +// on the NPAPI Object instance when they are called through the NPClass +// structure that our wrapper sets up. +TEST(NpApiWrappers, NpObjectCalls) { + NPClass* np_class = MockNpObject::object_class(); + ASSERT_TRUE(np_class != NULL); + MockNpObject np_object(NULL); + + EXPECT_CALL(np_object, HasMethod(NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->hasMethod(&np_object, NULL)); + + EXPECT_CALL(np_object, Invoke(NULL, NULL, 0, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->invoke(&np_object, NULL, NULL, 0, NULL)); + + EXPECT_CALL(np_object, InvokeDefault(NULL, 0, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->invokeDefault(&np_object, NULL, 0, NULL)); + + EXPECT_CALL(np_object, HasProperty(NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->hasProperty(&np_object, NULL)); + + EXPECT_CALL(np_object, GetProperty(NULL, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->getProperty(&np_object, NULL, NULL)); + + EXPECT_CALL(np_object, SetProperty(NULL, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->setProperty(&np_object, NULL, NULL)); + + EXPECT_CALL(np_object, RemoveProperty(NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->removeProperty(&np_object, NULL)); + + EXPECT_CALL(np_object, Enumeration(NULL, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->enumerate(&np_object, NULL, NULL)); + + EXPECT_CALL(np_object, Construct(NULL, 0, NULL)) + .Times(1) + .WillOnce(Return(false)); + EXPECT_FALSE(np_class->construct(&np_object, NULL, 0, NULL)); +} + +} // namespace diff --git a/ceee/common/npplugin_impl.cc b/ceee/common/npplugin_impl.cc new file mode 100644 index 0000000..b0cfee7 --- /dev/null +++ b/ceee/common/npplugin_impl.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2010 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. +// +// Implementation of NPAPI plugin base class. +#include "base/logging.h" +#include "ceee/common/npplugin_impl.h" + +NpPluginBase::~NpPluginBase() { +} + +NPError NpPluginBase::SetWindow(NPWindow* window) { + return NPERR_NO_ERROR; +} + +NPError NpPluginBase::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + return NPERR_GENERIC_ERROR; +} + +NPError NpPluginBase::DestroyStream(NPStream* stream, + NPReason reason) { + DCHECK(false); // You need to override this if you override NewStream + return NPERR_GENERIC_ERROR; +} + +int32 NpPluginBase::WriteReady(NPStream* stream) { + return 0; +} + +int32 NpPluginBase::Write(NPStream* stream, int32 offset, + int32 len, void* buffer) { + return 0; +} + +void NpPluginBase::StreamAsFile(NPStream* stream, + const char* fname) { +} + +void NpPluginBase::Print(NPPrint* platform_print) { +} + +int16 NpPluginBase::HandleEvent(void* event) { + return 0; +} + +void NpPluginBase::URLNotify(const char* url, + NPReason reason, void* notify_data) { +} + +NPError NpPluginBase::GetValue(NPPVariable variable, void* value) { + return NPERR_GENERIC_ERROR; +} + +NPError NpPluginBase::SetValue(NPNVariable variable, + void* value) { + return NPERR_GENERIC_ERROR; +} + +NPError NpPluginBase::NPP_Destroy(NPP instance, NPSavedData** save) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + delete plugin; + instance->pdata = NULL; + return NPERR_NO_ERROR; +} + +NPError NpPluginBase::NPP_SetWindow(NPP instance, NPWindow* window) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->SetWindow(window); +} + +NPError NpPluginBase::NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16* stype) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + return plugin->NewStream(type, stream, seekable, stype); +} + +NPError NpPluginBase::NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->DestroyStream(stream, reason); +} + +int32 NpPluginBase::NPP_WriteReady(NPP instance, NPStream* stream) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->WriteReady(stream); +} + +int32 NpPluginBase::NPP_Write(NPP instance, NPStream* stream, int32 offset, + int32 len, void* buffer) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->Write(stream, offset, len, buffer); +} + +void NpPluginBase::NPP_StreamAsFile(NPP instance, NPStream* stream, + const char* fname) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->StreamAsFile(stream, fname); +} + +void NpPluginBase::NPP_Print(NPP instance, NPPrint* platform_print) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->Print(platform_print); +} + +int16 NpPluginBase::NPP_HandleEvent(NPP instance, void* event) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->HandleEvent(event); +} + +void NpPluginBase::NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notify_data) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->URLNotify(url, reason, notify_data); +} + +NPError NpPluginBase::NPP_GetValue(NPP instance, NPPVariable variable, + void* value) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->GetValue(variable, value); +} + +NPError NpPluginBase::NPP_SetValue(NPP instance, NPNVariable variable, + void* value) { + NpPluginBase* plugin = reinterpret_cast<NpPluginBase*>(instance->pdata); + + return plugin->SetValue(variable, value); +} diff --git a/ceee/common/npplugin_impl.h b/ceee/common/npplugin_impl.h new file mode 100644 index 0000000..4068b18b --- /dev/null +++ b/ceee/common/npplugin_impl.h @@ -0,0 +1,147 @@ +// Copyright (c) 2010 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. +// +// Base classes to unclutter the writing of NPAPI plugins. +#ifndef CEEE_COMMON_NPPLUGIN_IMPL_H_ +#define CEEE_COMMON_NPPLUGIN_IMPL_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/nphostapi.h" + +// Base class for NPAPI plugins. +class NpPluginBase { + public: + explicit NpPluginBase(NPP npp) : npp_(npp) { + } + virtual ~NpPluginBase(); + + protected: + NPError Initialize(); + + // @name NP Plugin interface. + // Override these to implement your plugin's functionality + // For documentation on these methods, see the corresponding callback + // function description in https://developer.mozilla.org/en + // /Gecko_Plugin_API_Reference/Plug-in_Side_Plug-in_API + // @{ + virtual NPError SetWindow(NPWindow* window); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual NPError DestroyStream(NPStream* stream, + NPReason reason); + virtual int32 WriteReady(NPStream* stream); + virtual int32 Write(NPStream* stream, int32 offset, + int32 len, void* buffer); + virtual void StreamAsFile(NPStream* stream, + const char* fname); + virtual void Print(NPPrint* platform_print); + virtual int16 HandleEvent(void* event); + virtual void URLNotify(const char* url, NPReason reason, void* notify_data); + virtual NPError GetValue(NPPVariable variable, void* value); + virtual NPError SetValue(NPNVariable variable, void* value); + // @} + + // @name NP callback functions. + // @{ + static NPError NPP_Destroy(NPP instance, NPSavedData** save); + static NPError NPP_SetWindow(NPP instance, NPWindow* window); + static NPError NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16* stype); + static NPError NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason); + static int32 NPP_WriteReady(NPP instance, NPStream* stream); + static int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, + int32 len, void* buffer); + static void NPP_StreamAsFile(NPP instance, NPStream* stream, + const char* fname); + static void NPP_Print(NPP instance, NPPrint* platform_print); + static int16 NPP_HandleEvent(NPP instance, void* event); + static void NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData); + static NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value); + static NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value); + // @} + + // Our instance. + NPP npp_; + + private: + DISALLOW_COPY_AND_ASSIGN(NpPluginBase); +}; + +// Derive your NPAPI implementation class from NpPluginImpl<MyClass>. +template <class ImplClass> class NpPluginImpl: public NpPluginBase { + public: + explicit NpPluginImpl(NPP npp) : NpPluginBase(npp) { + } + + // Retrieve a set of plugin entrypoints suitable for ImplClass. + static NPError GetEntryPoints(NPPluginFuncs* plugin_funcs); + + protected: + static NPError NPP_New(NPMIMEType plugin_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[], NPSavedData* saved); + // Override this in ImplClass if you need custom creation logic. + static NPError Create(NPMIMEType plugin_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[], NPSavedData* saved); + + private: + DISALLOW_COPY_AND_ASSIGN(NpPluginImpl); +}; + +template <class ImplClass> +NPError NpPluginImpl<ImplClass>::GetEntryPoints(NPPluginFuncs* plugin_funcs) { + plugin_funcs->version = 11; + plugin_funcs->size = sizeof(plugin_funcs); + plugin_funcs->newp = ImplClass::NPP_New; + plugin_funcs->destroy = ImplClass::NPP_Destroy; + plugin_funcs->setwindow = ImplClass::NPP_SetWindow; + plugin_funcs->newstream = ImplClass::NPP_NewStream; + plugin_funcs->destroystream = ImplClass::NPP_DestroyStream; + plugin_funcs->asfile = ImplClass::NPP_StreamAsFile; + plugin_funcs->writeready = ImplClass::NPP_WriteReady; + plugin_funcs->write = ImplClass::NPP_Write; + plugin_funcs->print = ImplClass::NPP_Print; + plugin_funcs->event = ImplClass::NPP_HandleEvent; + plugin_funcs->urlnotify = ImplClass::NPP_URLNotify; + plugin_funcs->getvalue = ImplClass::NPP_GetValue; + plugin_funcs->setvalue = ImplClass::NPP_SetValue; + + return NPERR_NO_ERROR; +} + +template <class ImplClass> +NPError NpPluginImpl<ImplClass>::NPP_New(NPMIMEType plugin_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[], NPSavedData* saved) { + // This gives the implclass an opportunity to + // override default creation behavior. + return ImplClass::Create(plugin_type, + instance, + mode, + argc, + argn, + argv, + saved); +} + +template <class ImplClass> +NPError NpPluginImpl<ImplClass>::Create(NPMIMEType plugin_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[], NPSavedData* saved) { + ImplClass* impl = new ImplClass(instance); + NPError error = impl->Initialize(); + if (NPERR_NO_ERROR == error) { + instance->pdata = impl; + } else { + delete impl; + } + + return error; +} + +#endif // CEEE_COMMON_NPPLUGIN_IMPL_H_ diff --git a/ceee/common/npplugin_impl_unittest.cc b/ceee/common/npplugin_impl_unittest.cc new file mode 100644 index 0000000..3a92475 --- /dev/null +++ b/ceee/common/npplugin_impl_unittest.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2010 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. +// +// Tests for NPAPI Plugin Wrapper classes. + +#include "ceee/common/npplugin_impl.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::_; +using testing::Return; + +namespace { + +// A bare bones implementation class which really just uses the default +// implementations of NpPluginImpl/NpPluginBase so we can test those +// wrapper classes for correct default NPAPI functionality. +class BareNpPlugin : public NpPluginImpl<BareNpPlugin> { + public: + explicit BareNpPlugin(NPP instance) : NpPluginImpl<BareNpPlugin>(instance) { + } + ~BareNpPlugin() { + } + + // TODO(stevet@google.com): Note that we re implement this to avoid + // the DCHECK in the original base class. If we can find a non + // crashing method of reminding developers to implement + // DestroyStream, we can remove this implementation. + virtual NPError DestroyStream(NPStream* stream, + NPReason reason) { + return NPERR_GENERIC_ERROR; + } + + private: + DISALLOW_COPY_AND_ASSIGN(BareNpPlugin); +}; + +// A mock class which allows us to verify that corresponding NPPlugin functions +// are called after they are setup by a call to GetEntryPoints. +class MockNpPlugin : public NpPluginImpl<MockNpPlugin> { + public: + explicit MockNpPlugin(NPP instance) : NpPluginImpl<MockNpPlugin>(instance) { + } + ~MockNpPlugin() { + } + + NPError Initialize() { + return NPERR_NO_ERROR; + } + + MOCK_METHOD1(SetWindow, NPError(NPWindow* window)); + MOCK_METHOD4(NewStream, NPError(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype)); + MOCK_METHOD2(DestroyStream, NPError(NPStream* stream, NPReason reason)); + MOCK_METHOD1(WriteReady, int32(NPStream* stream)); + MOCK_METHOD4(Write, int32(NPStream* stream, int32 offset, + int32 len, void* buffer)); + MOCK_METHOD2(StreamAsFile, void(NPStream* stream, const char* fname)); + MOCK_METHOD1(Print, void(NPPrint* platform_print)); + MOCK_METHOD1(HandleEvent, int16(void* event)); + MOCK_METHOD3(URLNotify, void(const char* url, NPReason reason, + void* notify_data)); + MOCK_METHOD2(GetValue, NPError(NPPVariable variable, void* value)); + MOCK_METHOD2(SetValue, NPError(NPNVariable variable, void* value)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockNpPlugin); +}; + +// Test to ensure that the NPPlugin helpers' defaults are correct. Note that +// we use http://goo.gl/u0VT as a rough guide to what values are defaults. +TEST(NpApiWrappers, NpPluginDefaults) { + NPP instance = new NPP_t; + BareNpPlugin np_plugin(instance); + instance->pdata = &np_plugin; + + NPPluginFuncs plugin_funcs; + EXPECT_EQ(NPERR_NO_ERROR, MockNpPlugin::GetEntryPoints(&plugin_funcs)); + + EXPECT_EQ(NPERR_NO_ERROR, plugin_funcs.setwindow(instance, NULL)); + EXPECT_EQ(NPERR_GENERIC_ERROR, + plugin_funcs.newstream(instance, NULL, NULL, '0', NULL)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.destroystream(instance, NULL, 0)); + EXPECT_EQ(0, plugin_funcs.writeready(instance, NULL)); + EXPECT_EQ(0, plugin_funcs.write(instance, NULL, 0, 0, NULL)); + EXPECT_EQ(0, plugin_funcs.event(instance, NULL)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.getvalue(instance, + (NPPVariable)1, NULL)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.setvalue(instance, + (NPNVariable)1, NULL)); +} + +// Initialize a NPPluginFuncs struct with a call to NpPluginImpl<ImplClass> +// ::GetEntryPoints and ensure that the appropriate functions are attached +// to the structure. +TEST(NpApiWrappers, NpPluginCalls) { + // Create a faux NPP instance since function calls need it's pdata field to + // determine what plugin object should be making the call. + NPP instance = new NPP_t; + MockNpPlugin np_plugin(instance); + instance->pdata = &np_plugin; + + NPPluginFuncs plugin_funcs; + EXPECT_EQ(NPERR_NO_ERROR, MockNpPlugin::GetEntryPoints(&plugin_funcs)); + + EXPECT_CALL(np_plugin, SetWindow(NULL)) + .Times(1) + .WillOnce(Return(NPERR_NO_ERROR)); + EXPECT_EQ(NPERR_NO_ERROR, plugin_funcs.setwindow(instance, NULL)); + + EXPECT_CALL(np_plugin, NewStream(NULL, NULL, '0', NULL)) + .Times(1) + .WillOnce(Return(NPERR_GENERIC_ERROR)); + EXPECT_EQ(NPERR_GENERIC_ERROR, + plugin_funcs.newstream(instance, NULL, NULL, '0', NULL)); + + EXPECT_CALL(np_plugin, DestroyStream(NULL, 0)) + .Times(1) + .WillOnce(Return(NPERR_GENERIC_ERROR)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.destroystream(instance, NULL, 0)); + + EXPECT_CALL(np_plugin, WriteReady(NULL)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_EQ(0, plugin_funcs.writeready(instance, NULL)); + + EXPECT_CALL(np_plugin, Write(NULL, 0, 0, NULL)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_EQ(0, plugin_funcs.write(instance, NULL, 0, 0, NULL)); + + EXPECT_CALL(np_plugin, StreamAsFile(NULL, NULL)) + .Times(1); + plugin_funcs.asfile(instance, NULL, NULL); + + EXPECT_CALL(np_plugin, Print(NULL)) + .Times(1); + plugin_funcs.print(instance, NULL); + + EXPECT_CALL(np_plugin, HandleEvent(NULL)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_EQ(0, plugin_funcs.event(instance, NULL)); + + EXPECT_CALL(np_plugin, URLNotify(NULL, 0, NULL)) + .Times(1); + plugin_funcs.urlnotify(instance, NULL, 0, NULL); + + EXPECT_CALL(np_plugin, GetValue((NPPVariable)1, NULL)) + .Times(1) + .WillOnce(Return(NPERR_GENERIC_ERROR)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.getvalue(instance, + (NPPVariable)1, NULL)); + + EXPECT_CALL(np_plugin, SetValue((NPNVariable)1, NULL)) + .Times(1) + .WillOnce(Return(NPERR_GENERIC_ERROR)); + EXPECT_EQ(NPERR_GENERIC_ERROR, plugin_funcs.setvalue(instance, + (NPNVariable)1, NULL)); + + delete instance; + instance = NULL; +} + +} // namespace diff --git a/ceee/common/process_utils_win.cc b/ceee/common/process_utils_win.cc new file mode 100644 index 0000000..094af1e --- /dev/null +++ b/ceee/common/process_utils_win.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2010 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. +// +// Utilities for windows process and threads stuff. + +#include "ceee/common/process_utils_win.h" + +#include <sddl.h> + +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "base/win/windows_version.h" +#include "ceee/common/com_utils.h" + + +namespace process_utils_win { + +HRESULT SetThreadIntegrityLevel(HANDLE* thread, const std::wstring& level) { + HANDLE temp_handle = NULL; + BOOL success = ::OpenProcessToken( + ::GetCurrentProcess(), MAXIMUM_ALLOWED, &temp_handle); + ScopedHandle process_token(temp_handle); + temp_handle = NULL; + if (success) { + success = ::DuplicateTokenEx( + process_token, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, + TokenImpersonation, &temp_handle); + ScopedHandle mic_token(temp_handle); + temp_handle = NULL; + if (success) { + PSID mic_sid = NULL; + success = ::ConvertStringSidToSid(level.c_str(), &mic_sid); + if (success) { + // Set Process IL to Low + TOKEN_MANDATORY_LABEL tml = {0}; + tml.Label.Attributes = SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED; + tml.Label.Sid = mic_sid; + success = ::SetTokenInformation( + mic_token, TokenIntegrityLevel, &tml, + sizeof(tml) + ::GetLengthSid(mic_sid)); + if (success) { + success = ::SetThreadToken(thread, mic_token); + LOG_IF(WARNING, !success) << "Failed to SetThreadToken." << + com::LogWe(); + } else { + LOG(WARNING) << "Failed to SetTokenInformation." << com::LogWe(); + } + ::LocalFree(mic_sid); + } else { + LOG(WARNING) << "Failed to SIDConvert: " << level << com::LogWe(); + } + } else { + LOG(WARNING) << "Failed to Duplicate the process token." << com::LogWe(); + } + } else { + LOG(WARNING) << "Failed to Open the process token." << com::LogWe(); + } + + if (success) + return S_OK; + else + return HRESULT_FROM_WIN32(GetLastError()); +} + +HRESULT ResetThreadIntegrityLevel(HANDLE* thread) { + if (::SetThreadToken(thread, NULL)) + return S_OK; + else + return HRESULT_FROM_WIN32(GetLastError()); +} + +HRESULT IsCurrentProcessUacElevated(bool* running_as_admin) { + DCHECK(NULL != running_as_admin); + + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + *running_as_admin = false; + return S_OK; + } + + HANDLE temp_handle = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &temp_handle)) { + DWORD error_code = ::GetLastError(); + LOG(WARNING) << "Failed to Open the process token." << + com::LogWe(error_code); + return com::AlwaysErrorFromWin32(error_code); + } + + // We check if the current process is running in a higher integrity mode than + // it would be running if it was started 'normally' by checking if its token + // has an associated full elevation token. This seems to do the trick and I + // carefully checked it against the obvious alternative of checking the + // integrity level of the current process. This is what I found out: + // UAC off, normal start: token default, high integrity + // UAC off, admin start: token default, high integrity + // UAC on, normal start: token limited, medium integrity + // UAC on, admin start: token full, medium integrity + // All that for an admin-group member, who can run in elevated mode. + // This logic applies to Vista/Win7. The case of earlier systems is handled + // at the start. + ScopedHandle process_token(temp_handle); + TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault; + DWORD variable_len_dummy = 0; + if (!::GetTokenInformation(process_token, TokenElevationType, &elevation_type, + sizeof(elevation_type), &variable_len_dummy)) { + DWORD error_code = ::GetLastError(); + LOG(WARNING) << "Failed to retrieve token information." << + com::LogWe(error_code); + return com::AlwaysErrorFromWin32(error_code); + } + + *running_as_admin = elevation_type == TokenElevationTypeFull; + + return S_OK; +} + +ProcessCompatibilityCheck::OpenProcessFuncType + ProcessCompatibilityCheck::open_process_func_ = OpenProcess; +ProcessCompatibilityCheck::CloseHandleFuncType + ProcessCompatibilityCheck::close_handle_func_ = CloseHandle; +ProcessCompatibilityCheck::IsWOW64ProcessFuncType + ProcessCompatibilityCheck::is_wow64_process_func_ = IsWow64Process; + +ProcessCompatibilityCheck::ProcessCompatibilityCheck() + : initialization_result_(E_FAIL), + running_on_amd64_platform_(false), + running_as_wow_64_(false), + integrity_checks_on_(false), + running_integrity_(base::INTEGRITY_UNKNOWN) { + StandardInitialize(); +} + +void ProcessCompatibilityCheck::StandardInitialize() { + HRESULT hr = S_OK; + + SYSTEM_INFO system_info; + ::GetNativeSystemInfo(&system_info); + + bool system_with_integrity_checks = + base::win::GetVersion() >= base::win::VERSION_VISTA; + + base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN; + if (system_with_integrity_checks && + !base::GetProcessIntegrityLevel(::GetCurrentProcess(), + &integrity_level)) { + hr = com::AlwaysErrorFromLastError(); + } + + BOOL is_wow_64 = FALSE; + // If not running_on_amd64_platform_, all processes are OK and + // we wouldn't need all these tools... + if (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 && + !is_wow64_process_func_(::GetCurrentProcess(), &is_wow_64)) { + hr = com::AlwaysErrorFromLastError(); + } + + KnownStateInitialize(system_info.wProcessorArchitecture, is_wow_64, + system_with_integrity_checks, integrity_level); + initialization_result_ = hr; +} + +// Initialize with exactly this data with some checks, just in case. +void ProcessCompatibilityCheck::KnownStateInitialize( + WORD system_type, bool current_process_wow64, + bool check_integrity, base::IntegrityLevel current_process_integrity) { + DCHECK(system_type == PROCESSOR_ARCHITECTURE_AMD64 || + system_type == PROCESSOR_ARCHITECTURE_INTEL); + + running_on_amd64_platform_ = system_type == PROCESSOR_ARCHITECTURE_AMD64; + running_as_wow_64_ = current_process_wow64; + + // Can't be running_as_wow_64_ while not running_on_amd64_platform_. + DCHECK((running_on_amd64_platform_ && running_as_wow_64_) || + !running_as_wow_64_); + + integrity_checks_on_ = check_integrity; + running_integrity_ = current_process_integrity; + + // Assert that running_integrity_ is given whenever integrity_checks_on_: + // integrity_checks_on_ => running_integrity_ != base::INTEGRITY_UNKNOWN AND + // !integrity_checks_on_ => running_integrity_ == base::INTEGRITY_UNKNOWN. + DCHECK(((integrity_checks_on_ && + running_integrity_ != base::INTEGRITY_UNKNOWN) || + !integrity_checks_on_) && + ((!integrity_checks_on_ && + running_integrity_ == base::INTEGRITY_UNKNOWN) || + integrity_checks_on_)); +} + +HRESULT ProcessCompatibilityCheck::IsCompatible(HWND process_window, + bool* is_compatible) { + DWORD process_id = 0; + if (::GetWindowThreadProcessId(process_window, &process_id) != 0) { + return IsCompatible(process_id, is_compatible); + } + + return E_FAIL; +} + +HRESULT ProcessCompatibilityCheck::IsCompatible(DWORD process_id, + bool* is_compatible) { + ProcessCompatibilityCheck* instance + = Singleton<ProcessCompatibilityCheck>::get(); + + DCHECK(instance != NULL); + DCHECK(is_compatible != NULL); + + if (instance != NULL && SUCCEEDED(instance->initialization_result_)) { + return instance->IsProcessCompatible(process_id, is_compatible); + } + + return instance != NULL ? instance->initialization_result_ : E_FAIL; +} + +HRESULT ProcessCompatibilityCheck::IsProcessCompatible(DWORD process_id, + bool* is_compatible) { + DCHECK(is_compatible != NULL); + + if (!running_on_amd64_platform_ && !integrity_checks_on_) { + *is_compatible = true; + return S_OK; + } + + HRESULT hr = E_FAIL; + HANDLE other_process = open_process_func_(PROCESS_QUERY_INFORMATION, + FALSE, process_id); + + if (other_process != NULL) { + bool platform_compatible = false; + bool integrity_compatible = false; + hr = IsProcessPlatformCompatible(other_process, &platform_compatible); + if (SUCCEEDED(hr)) + hr = IsProcessIntegrityCompatible(other_process, &integrity_compatible); + + if (SUCCEEDED(hr)) + *is_compatible = platform_compatible && integrity_compatible; + + if (!close_handle_func_(other_process)) + LOG(WARNING) << "CloseHandle failed: " << com::LogWe(); + } + + return hr; +} + +// Is the process we are inquiring about of the same type +// (native 32, native 64 or WOW64) as the current process. +HRESULT ProcessCompatibilityCheck::IsProcessPlatformCompatible( + HANDLE process_handle, bool* is_compatible) { + if (!running_on_amd64_platform_) { + *is_compatible = true; + return S_OK; + } + + BOOL is_other_wow_64 = FALSE; + + if (is_wow64_process_func_(process_handle, &is_other_wow_64)) { + *is_compatible = (running_as_wow_64_ && is_other_wow_64) || + !(running_as_wow_64_ || is_other_wow_64); + return S_OK; + } + + return com::AlwaysErrorFromLastError(); +} + +// Is the process we are inquiring about at the integrity level which should +// permit inserting an executor into that process? +// For the purpose of security-compatibility high integrity processes are +// considered a separate class from medium and low integrity processes because +// in interactive scenarios they will belong to different logon sessions. +// It appears we cannot programatically cross this boundary in COM and so we +// want to filter these processes out. On the other hand, medium and low +// integrity processes will exist within the same logon session and can +// attempt communication (in the sense discussed here). +// In practice this corresponds to running instances of IE as administrator and +// normally. The former group of processes will function at high integrity level +// while the latter at medium or low (IE's protected mode?). Note that each of +// these groups of processes will have a separate ceee_broker. Inquiring about +// process integrity appears to be a very practical way of properly routing +// requests to IE's windows from a broker instance. +HRESULT ProcessCompatibilityCheck::IsProcessIntegrityCompatible( + HANDLE process_handle, bool* is_compatible) { + if (!integrity_checks_on_) { + *is_compatible = true; + return S_OK; + } + + base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN; + if (base::GetProcessIntegrityLevel(process_handle, &integrity_level)) { + *is_compatible = (integrity_level == base::HIGH_INTEGRITY && + running_integrity_ == base::HIGH_INTEGRITY) || + (integrity_level != base::HIGH_INTEGRITY && + running_integrity_ != base::HIGH_INTEGRITY); + return S_OK; + } + + return com::AlwaysErrorFromLastError(); +} + +// Patching affects both static pointers and the state of the sigleton. +// To update the later, required variables are forwarded to its +// KnownStateInitialize. +void ProcessCompatibilityCheck::PatchState( + WORD system_type, bool current_process_wow64, + bool check_integrity, base::IntegrityLevel current_process_integrity, + OpenProcessFuncType open_process_func, + CloseHandleFuncType close_handle_func, + IsWOW64ProcessFuncType is_wow64_process_func) { + PatchState(open_process_func, close_handle_func, is_wow64_process_func); + Singleton<ProcessCompatibilityCheck>::get()->KnownStateInitialize( + system_type, current_process_wow64, + check_integrity, current_process_integrity); +} + +void ProcessCompatibilityCheck::PatchState( + OpenProcessFuncType open_process_func, + CloseHandleFuncType close_handle_func, + IsWOW64ProcessFuncType is_wow64_process_func) { + open_process_func_ = open_process_func; + close_handle_func_ = close_handle_func; + is_wow64_process_func_ = is_wow64_process_func; + DCHECK(open_process_func_ != NULL && close_handle_func_ != NULL && + is_wow64_process_func_ != NULL); +} + +void ProcessCompatibilityCheck::ResetState() { + PatchState(OpenProcess, CloseHandle, IsWow64Process); + Singleton<ProcessCompatibilityCheck>::get()->StandardInitialize(); +} + +} // namespace com diff --git a/ceee/common/process_utils_win.h b/ceee/common/process_utils_win.h new file mode 100644 index 0000000..e928791 --- /dev/null +++ b/ceee/common/process_utils_win.h @@ -0,0 +1,122 @@ +// Copyright (c) 2010 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. +// +// Utilities for process/threads in Windows. + +#ifndef CEEE_COMMON_PROCESS_UTILS_WIN_H_ +#define CEEE_COMMON_PROCESS_UTILS_WIN_H_ + +#include <windows.h> +#include <wtypes.h> + +#include <string> + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "base/process_util.h" + +namespace process_utils_win { + +// Sets the integrity level of the specified thread, or the current thread +// if thread is NULL. The level is a string as #defined in sddl.h, e.g., +// SDDL_ML_LOW or any string of the proper SID Component format as described +// here: http://msdn.microsoft.com/en-us/library/aa379597(v=VS.85).aspx +// Will return HRESULT_FROM_WIN32(ERROR_PRIVILEGE_NOT_HELD) if the provided +// thread (or its process) have lower priviliges then the ones requested. +HRESULT SetThreadIntegrityLevel(HANDLE* thread, const std::wstring& level); + +// Reset the thread integrity level to the process level. Again, if thread +// is NULL, set the integrity level of the current thread. +HRESULT ResetThreadIntegrityLevel(HANDLE* thread); + +// The bool pointer will be set to true if the current process has been started +// in administrator's mode ('Run As Adminstrator') while UAC was active. +// Programs started that way run at different integrity level than these +// invoked by the user in a regular way. +// More detailed analysis is with the implementation. +HRESULT IsCurrentProcessUacElevated(bool* running_as_admin); + +// Checks if a given process is compatible with the current process. +// Two processes are compatible if they run on the same platform-compatible and +// are security-compatible. +// The former makes sense on a 64-bit platform, where 32-bit processes inhabit +// a separate subsystem (WOW64). Thus, two processes are platform-compatible +// when both are 32-bit or both are 64-bit. It is noop on 32-bit systems. +// Two processes are security-compatible when their integrity levels permit COM +// communication between them. High integrity processes are compatible only with +// other high integrity processes. A low or a medium integrity process is +// compatible with any process that is either low or medium integrity. +// Some considerations of security-compatibility are expanded upon in comments +// with implementation. +class ProcessCompatibilityCheck { + public: + // Is the process associated with the given window compatible with the + // current process. If the call returns an error code, the value of + // *is_compatible is undefined. + static HRESULT IsCompatible(HWND process_window, bool* is_compatible); + + // Is the process of given ID compatible with the current process. If the + // call returns an error code, the value of *is_compatible is undefined. + static HRESULT IsCompatible(DWORD process_id, bool* is_compatible); + + protected: + typedef HANDLE (WINAPI *OpenProcessFuncType)(DWORD, BOOL, DWORD); + typedef BOOL (WINAPI *CloseHandleFuncType)(HANDLE); + typedef BOOL (WINAPI *IsWOW64ProcessFuncType)(HANDLE, PBOOL); + + // Non-trivial constructor will initialize default state. Possible error code + // will be stored for reference. + ProcessCompatibilityCheck(); + + // Exposed for unittesting only. + static void PatchState(WORD system_type, bool current_process_wow64, + bool check_integrity, + base::IntegrityLevel current_process_integrity, + OpenProcessFuncType open_process_func, + CloseHandleFuncType close_handle_func, + IsWOW64ProcessFuncType is_wow64_process_func); + static void PatchState(OpenProcessFuncType open_process_func, + CloseHandleFuncType close_handle_func, + IsWOW64ProcessFuncType is_wow64_process_func); + // Reset to normal state. + static void ResetState(); + + // Is the process of given ID compatible with the current process? + HRESULT IsProcessCompatible(DWORD process_id, bool* is_compatible); + + private: + // Initialization function taking true system state. + void StandardInitialize(); + + // Initialize call assigning given values to objects. + void KnownStateInitialize(WORD system_type, bool current_process_wow64, + bool check_integrity, + base::IntegrityLevel current_process_integrity); + + // Functions checking aspects of 'compatibility' of processes with the + // current process. + HRESULT IsProcessPlatformCompatible(HANDLE process_handle, + bool* is_compatible); + HRESULT IsProcessIntegrityCompatible(HANDLE process_handle, + bool* is_compatible); + + HRESULT initialization_result_; + bool running_on_amd64_platform_; + bool running_as_wow_64_; + bool integrity_checks_on_; + base::IntegrityLevel running_integrity_; + + // Function pointers held to allow substituting with a test harness. + static OpenProcessFuncType open_process_func_; + static CloseHandleFuncType close_handle_func_; + static IsWOW64ProcessFuncType is_wow64_process_func_; + + friend struct DefaultSingletonTraits<ProcessCompatibilityCheck>; + + DISALLOW_COPY_AND_ASSIGN(ProcessCompatibilityCheck); +}; + +} // namespace process_utils_win + +#endif // CEEE_COMMON_PROCESS_UTILS_WIN_H_ diff --git a/ceee/common/process_utils_win_unittest.cc b/ceee/common/process_utils_win_unittest.cc new file mode 100644 index 0000000..db6ceaf --- /dev/null +++ b/ceee/common/process_utils_win_unittest.cc @@ -0,0 +1,571 @@ +// Copyright (c) 2010 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. +// +// Unit tests for process utils for Windows. + +#include "base/scoped_ptr.h" +#include "base/win/windows_version.h" +#include "ceee/common/process_utils_win.h" + +#include "ceee/testing/utils/mock_win32.h" +#include "ceee/testing/utils/mock_static.h" +#include "ceee/testing/utils/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +using process_utils_win::ProcessCompatibilityCheck; +using testing::_; +using testing::Invoke; +using testing::NativeSystemInfoMockTool; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; +using testing::WithArgs; + +MOCK_STATIC_CLASS_BEGIN(MockProcessTokenAccess) + MOCK_STATIC_INIT_BEGIN(MockProcessTokenAccess) + MOCK_STATIC_INIT2(base::GetProcessIntegrityLevel, GetProcessIntegrityLevel); + MOCK_STATIC_INIT(GetTokenInformation); + MOCK_STATIC_INIT2(base::win::GetVersion, GetVersion); + MOCK_STATIC_INIT_END() + + MOCK_STATIC2(bool, , GetProcessIntegrityLevel, HANDLE, base::IntegrityLevel*); + MOCK_STATIC5(BOOL, WINAPI, GetTokenInformation, HANDLE, + TOKEN_INFORMATION_CLASS, void*, DWORD, DWORD*); + MOCK_STATIC0(base::win::Version, , GetVersion); +MOCK_STATIC_CLASS_END(MockProcessTokenAccess) + +class TokenInfoExpectations { + public: + TokenInfoExpectations(BOOL return_value, TOKEN_ELEVATION_TYPE set_value) + : return_value_(return_value), set_value_(set_value) { + } + + BOOL QueryInformation(void* value_carrier) { + TOKEN_ELEVATION_TYPE* value_carrier_coerce = + reinterpret_cast<TOKEN_ELEVATION_TYPE*>(value_carrier); + *value_carrier_coerce = set_value_; + + return return_value_; + } + + BOOL return_value_; + TOKEN_ELEVATION_TYPE set_value_; + + private: + TokenInfoExpectations(); +}; + + +TEST(ProcessUtilsWin, SetThreadIntegrityLevel) { + // TODO(mad@chromium.org): Test this! +} + +TEST(ProcessUtilsWin, ResetThreadIntegrityLevel) { + // TODO(mad@chromium.org): Test this too! +} + + +// This harness tests the implementation rather than the function and how +// well this method meets the requirement. Unfortunate, but necessary. +TEST(ProcessUtilsWin, IsRunningWithElevatedToken) { + StrictMock<MockProcessTokenAccess> token_mock; + bool running_admin = true; + TokenInfoExpectations token_set(TRUE, TokenElevationTypeDefault); + + // In this function we check operation on systems with UAC + EXPECT_CALL(token_mock, GetVersion()) + .WillRepeatedly(Return(base::win::VERSION_VISTA)); + + EXPECT_CALL(token_mock, GetTokenInformation(_, TokenElevationType, _, _, _)) + .WillOnce(WithArgs<2>( + Invoke(&token_set, &TokenInfoExpectations::QueryInformation))); + + ASSERT_HRESULT_SUCCEEDED( + process_utils_win::IsCurrentProcessUacElevated(&running_admin)); + ASSERT_FALSE(running_admin); + + token_set.set_value_ = TokenElevationTypeLimited; + EXPECT_CALL(token_mock, GetTokenInformation(_, TokenElevationType, _, _, _)) + .WillOnce(WithArgs<2>( + Invoke(&token_set, &TokenInfoExpectations::QueryInformation))); + ASSERT_HRESULT_SUCCEEDED( + process_utils_win::IsCurrentProcessUacElevated(&running_admin)); + ASSERT_FALSE(running_admin); + + token_set.set_value_ = TokenElevationTypeFull; + EXPECT_CALL(token_mock, GetTokenInformation(_, TokenElevationType, _, _, _)) + .WillOnce(WithArgs<2>( + Invoke(&token_set, &TokenInfoExpectations::QueryInformation))); + ASSERT_HRESULT_SUCCEEDED( + process_utils_win::IsCurrentProcessUacElevated(&running_admin)); + ASSERT_TRUE(running_admin); +} + +TEST(ProcessUtilsWin, IsRunningWithElevatedTokenOnXP) { + StrictMock<MockProcessTokenAccess> token_mock; + bool running_admin = true; + + EXPECT_CALL(token_mock, GetVersion()) + .WillOnce(Return(base::win::VERSION_XP)); + ASSERT_HRESULT_SUCCEEDED( + process_utils_win::IsCurrentProcessUacElevated(&running_admin)); + ASSERT_FALSE(running_admin); // No UAC on WinXP and thus no elevation. +} + +TEST(ProcessUtilsWin, IsRunningWithElevatedTokenError) { + StrictMock<MockProcessTokenAccess> token_mock; + bool running_admin = true; + TokenInfoExpectations token_set(FALSE, TokenElevationTypeDefault); + + EXPECT_CALL(token_mock, GetVersion()) + .WillOnce(Return(base::win::VERSION_WIN7)); + EXPECT_CALL(token_mock, GetTokenInformation(_, TokenElevationType, _, _, _)) + .WillOnce(WithArgs<2>( + Invoke(&token_set, &TokenInfoExpectations::QueryInformation))); + ASSERT_HRESULT_FAILED( + process_utils_win::IsCurrentProcessUacElevated(&running_admin)); +} + +// Test harness' base for telling native 64 processes from WOW64 processes. +class ProcessCompatibilityCheckTest : public testing::Test { + public: + ProcessCompatibilityCheckTest() { + } + + protected: + // The class inherits from the utility under test to permit injecting test + // code without risky patching of WinAPI. + // This is not 'black box', since I need to presume a bit about the sequence + // of calls. + class TestingProcessCompatibility : public ProcessCompatibilityCheck { + public: + static void SetUpSingletonProcess(WORD system_type, + bool process_is64, + base::win::Version system_version, + base::IntegrityLevel process_level) { + AssertProcessDescriptionCorrectess(system_type, system_version, + process_is64, process_level); + + testing_as_wow_ = !process_is64 && + system_type == PROCESSOR_ARCHITECTURE_AMD64; + mocked_system_type_ = system_type; + windows_version_ = system_version; + PatchState(system_type, testing_as_wow_, + system_version > base::win::VERSION_XP, process_level, + ProcessCompatibilityCheckTest::SpoofedOpenProcess, + ProcessCompatibilityCheckTest::SpoofedCloseHandle, + ProcessCompatibilityCheckTest::SpoofIsWow64Process); + } + + // Sets tested singleton as simple 32bit process on Win32. + static void CreateAs_x86() { + SetUpSingletonProcess(PROCESSOR_ARCHITECTURE_INTEL, false, + base::win::VERSION_XP, base::INTEGRITY_UNKNOWN); + } + + // Sets tested singleton either as a simple 64bit process on x64 or as + // 32bit process on WOW64. + static void CreateAsFull_Wow64(bool current_is_32) { + SetUpSingletonProcess(PROCESSOR_ARCHITECTURE_AMD64, !current_is_32, + base::win::VERSION_XP, base::INTEGRITY_UNKNOWN); + } + + static void Reset() { + ResetState(); + } + }; + + class TestingProcessCompatibilityInstantiation + : public ProcessCompatibilityCheck { + public: + TestingProcessCompatibilityInstantiation() + : ProcessCompatibilityCheck() { + } + // New instance of what would have been a singleton (to test constructor + // code). Created as if on a 32bit platform. + static TestingProcessCompatibilityInstantiation* CreateAs_x86() { + return CreateInstance(PROCESSOR_ARCHITECTURE_INTEL, false, + base::win::VERSION_XP, base::INTEGRITY_UNKNOWN); + } + + // Created as if on a 64bit platform. + static TestingProcessCompatibilityInstantiation* CreateAsFull_Wow64( + bool current_is_32) { + return CreateInstance(PROCESSOR_ARCHITECTURE_AMD64, !current_is_32, + base::win::VERSION_XP, base::INTEGRITY_UNKNOWN); + } + + // Creates test instance of ProcessCompatibilityCheck to represent a + // 'current process' as defined by parameters. + static TestingProcessCompatibilityInstantiation* CreateInstance( + WORD system_type, bool process_is64, + base::win::Version system_version, + base::IntegrityLevel process_level) { + PatchState(ProcessCompatibilityCheckTest::SpoofedOpenProcess, + ProcessCompatibilityCheckTest::SpoofedCloseHandle, + ProcessCompatibilityCheckTest::SpoofIsWow64Process); + StrictMock<testing::MockKernel32> kernel_calls; + StrictMock<MockProcessTokenAccess> token_mock; + + AssertProcessDescriptionCorrectess(system_type, system_version, + process_is64, process_level); + testing_as_wow_ = !process_is64 && + system_type == PROCESSOR_ARCHITECTURE_AMD64; + mocked_system_type_ = system_type; + windows_version_ = system_version; + + EXPECT_CALL(token_mock, GetVersion()).WillOnce(Return(system_version)); + if (system_type == PROCESSOR_ARCHITECTURE_INTEL) { + EXPECT_CALL(kernel_calls, GetNativeSystemInfo(_)) + .WillOnce(Invoke(NativeSystemInfoMockTool::SetEnvironment_x86)); + } else { + EXPECT_CALL(kernel_calls, GetNativeSystemInfo(_)) + .WillOnce(Invoke(NativeSystemInfoMockTool::SetEnvironment_x64)); + } + + // If mocked system supports integrity levels, expect a query. + if (system_version > base::win::VERSION_XP) { + EXPECT_CALL(token_mock, GetProcessIntegrityLevel(_, _)). + WillOnce(DoAll(SetArgumentPointee<1>(process_level), Return(true))); + } + + return new TestingProcessCompatibilityInstantiation(); + } + + ~TestingProcessCompatibilityInstantiation() { + ResetState(); + } + + HRESULT IsProcessCompatible(DWORD process_id, bool* is_compatible) { + return ProcessCompatibilityCheck::IsProcessCompatible(process_id, + is_compatible); + } + }; + + struct MockProcessRepresentation { + MockProcessRepresentation() + : process_is_64bit(false), + process_integrity_level(base::INTEGRITY_UNKNOWN), + compatibility_check_expectation(false) { + } + + MockProcessRepresentation(bool is_64, base::IntegrityLevel integrity, + bool expect_compatible) + : process_is_64bit(is_64), process_integrity_level(integrity), + compatibility_check_expectation(expect_compatible) { + } + + bool process_is_64bit; + base::IntegrityLevel process_integrity_level; + bool compatibility_check_expectation; + }; + + virtual void SetUp() { + mock_processes_.clear(); + back_to_pid_map_.clear(); + testing_as_wow_ = false; + mocked_system_type_ = PROCESSOR_ARCHITECTURE_INTEL; + windows_version_ = base::win::VERSION_XP; + next_proc_handle_ = 1; + } + + virtual void TearDown() { + ASSERT_TRUE(back_to_pid_map_.empty()); + TestingProcessCompatibility::Reset(); + } + + // Own version of IsWow64Process. Parameter names and types to match + // kernel32 function's signature. + static BOOL WINAPI SpoofIsWow64Process(HANDLE hProcess, PBOOL Wow64Process) { + if (::GetCurrentProcess() == hProcess) { + *Wow64Process = testing_as_wow_; + return TRUE; + } + + HandleToIdMap::const_iterator it_pid = + back_to_pid_map_.find(hProcess); + + if (it_pid != back_to_pid_map_.end()) { + ProcessIdMap::const_iterator found_it = + mock_processes_.find(it_pid->second); + *Wow64Process = found_it != mock_processes_.end() && + !found_it->second.process_is_64bit; + return TRUE; + } else { + LOG(ERROR) << "Failed to recognize test handle." << hProcess + << " A broken test!"; + return FALSE; + } + } + + // Own version of OpenProcess. Creates my own fake handle and counts issued + // handles. Parameter names and types to match. + static HANDLE WINAPI SpoofedOpenProcess(DWORD dwDesiredAccess, + BOOL bInheritHandle, + DWORD dwProcessId) { + DCHECK((dwDesiredAccess & PROCESS_QUERY_INFORMATION) == + PROCESS_QUERY_INFORMATION); + HANDLE fake_handle = reinterpret_cast<HANDLE>(next_proc_handle_); + back_to_pid_map_[fake_handle] = dwProcessId; + next_proc_handle_++; + + return fake_handle; + } + + // Own version of CloseHandle. Must match process, counts my handles. + static BOOL WINAPI SpoofedCloseHandle(HANDLE hObject) { + return back_to_pid_map_.erase(hObject) > 0; + } + + // Own version of GetProcessIntegrityLevel from base:, which in turn + // invokes queries on process tokens. + static bool MockProcessIntegrityLevel(HANDLE process, + base::IntegrityLevel* level) { + HandleToIdMap::const_iterator it_pid = back_to_pid_map_.find(process); + if (it_pid != back_to_pid_map_.end()) { + ProcessIdMap::const_iterator it_process = + mock_processes_.find(it_pid->second); + if (it_process != mock_processes_.end()) { + *level = it_process->second.process_integrity_level; + return true; + } + } + return false; + } + + static void MakeMockProcess(DWORD pid, bool process_64bit, + base::IntegrityLevel integrity, + bool check_will_succeeed) { + DCHECK(mock_processes_.find(pid) == mock_processes_.end()); + DCHECK(pid < kWindowCounterBase); + AssertProcessDescriptionCorrectess(mocked_system_type_, windows_version_, + process_64bit, integrity); + mock_processes_[pid] = MockProcessRepresentation(process_64bit, integrity, + check_will_succeeed); + } + + static void PerformCompatibilityCheck( + TestingProcessCompatibilityInstantiation* checker) { + StrictMock<MockProcessTokenAccess> mock_token; + StrictMock<testing::MockUser32> mock_user32; + + if (windows_version_ > base::win::VERSION_XP) { + EXPECT_CALL(mock_token, GetProcessIntegrityLevel(_, _)). + WillRepeatedly(Invoke( + ProcessCompatibilityCheckTest::MockProcessIntegrityLevel)); + } + + if (checker != NULL) { + for (ProcessIdMap::const_iterator it = mock_processes_.begin(); + it != mock_processes_.end(); ++it) { + bool process_compatible = false; + ASSERT_HRESULT_SUCCEEDED(checker->IsProcessCompatible(it->first, + &process_compatible)); + EXPECT_EQ(process_compatible, + it->second.compatibility_check_expectation); + } + } else { + EXPECT_CALL(mock_user32, GetWindowThreadProcessId(_, _)) + .WillRepeatedly(Invoke( + ProcessCompatibilityCheckTest::MockThreadProcessFromWindow)); + for (ProcessIdMap::const_iterator it = mock_processes_.begin(); + it != mock_processes_.end(); ++it) { + bool window_compatible = false; + bool process_compatible = false; + + ASSERT_HRESULT_SUCCEEDED( + ProcessCompatibilityCheck::IsCompatible(it->first, + &process_compatible)); + ASSERT_HRESULT_SUCCEEDED( + ProcessCompatibilityCheck::IsCompatible( + reinterpret_cast<HWND>(it->first + kWindowCounterBase), + &window_compatible)); + EXPECT_EQ(window_compatible, process_compatible); + EXPECT_EQ(process_compatible, + it->second.compatibility_check_expectation); + } + } + } + + static DWORD MockThreadProcessFromWindow(HWND window, DWORD* process_id) { + int process_from_window = reinterpret_cast<int>(window) - + kWindowCounterBase; + DCHECK(process_from_window >= 0 && + process_from_window < kWindowCounterBase); + *process_id = static_cast<DWORD>(process_from_window); + return 42; + } + + typedef std::map<DWORD, MockProcessRepresentation> ProcessIdMap; + typedef std::map<HANDLE, DWORD> HandleToIdMap; + + static ProcessIdMap mock_processes_; + static HandleToIdMap back_to_pid_map_; + static unsigned int next_proc_handle_; + + static bool testing_as_wow_; + static WORD mocked_system_type_; + static base::win::Version windows_version_; + + static const int kWindowCounterBase = 1000; + private: + // Asserts that variables defining a process are consistent with each other. + // In other words: can process defined by two latter parameters exist on the + // platform defined by two former. + static void AssertProcessDescriptionCorrectess( + WORD system_type, base::win::Version windows_version, + bool current_process_is64, base::IntegrityLevel process_integrity) { + ASSERT_TRUE(system_type == PROCESSOR_ARCHITECTURE_INTEL || + system_type == PROCESSOR_ARCHITECTURE_AMD64); + ASSERT_TRUE(windows_version >= base::win::VERSION_XP); + + ASSERT_TRUE(system_type == PROCESSOR_ARCHITECTURE_AMD64 || + !current_process_is64); // No 64 bit processes on win32. + + ASSERT_TRUE(windows_version > base::win::VERSION_XP || + process_integrity == base::INTEGRITY_UNKNOWN); + ASSERT_TRUE(windows_version == base::win::VERSION_XP || + process_integrity >= base::LOW_INTEGRITY); + } +}; + +ProcessCompatibilityCheckTest::ProcessIdMap + ProcessCompatibilityCheckTest::mock_processes_; +ProcessCompatibilityCheckTest::HandleToIdMap + ProcessCompatibilityCheckTest::back_to_pid_map_; + +bool ProcessCompatibilityCheckTest::testing_as_wow_ = false; +unsigned int ProcessCompatibilityCheckTest::next_proc_handle_ = 0; +WORD ProcessCompatibilityCheckTest::mocked_system_type_ = + PROCESSOR_ARCHITECTURE_INTEL; +base::win::Version ProcessCompatibilityCheckTest::windows_version_ = + base::win::VERSION_XP; + +TEST_F(ProcessCompatibilityCheckTest, AllIsGoodOnx86) { + TestingProcessCompatibility::CreateAs_x86(); + + bool all_is_good = false; + ASSERT_HRESULT_SUCCEEDED( + ProcessCompatibilityCheck::IsCompatible(42, &all_is_good)); + ASSERT_TRUE(all_is_good); +} + +TEST_F(ProcessCompatibilityCheckTest, Only32PassSieveWithWow) { + TestingProcessCompatibility::CreateAsFull_Wow64(true); + + const int wow_pool = 10; + const int total_count = 15; + + for (int i = 1; i < total_count; ++i) + MakeMockProcess(i, i >= wow_pool, base::INTEGRITY_UNKNOWN, i < wow_pool); + PerformCompatibilityCheck(NULL); +} + +TEST_F(ProcessCompatibilityCheckTest, Only64PassSieveWithNoWow) { + TestingProcessCompatibility::CreateAsFull_Wow64(false); + + const int wow_pool = 10; + const int total_count = 15; + + for (int i = 1; i < total_count; ++i) + MakeMockProcess(i, i >= wow_pool, base::INTEGRITY_UNKNOWN, i >= wow_pool); + PerformCompatibilityCheck(NULL); +} + +TEST_F(ProcessCompatibilityCheckTest, AllIsGoodOnx86_WithInstantiation) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance(TestingProcessCompatibilityInstantiation::CreateAs_x86()); + + bool all_is_good = false; + ASSERT_HRESULT_SUCCEEDED( + test_instance->IsProcessCompatible(42, &all_is_good)); + ASSERT_TRUE(all_is_good); +} + +TEST_F(ProcessCompatibilityCheckTest, Only32PassSieve_WithInstantiation) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance( + TestingProcessCompatibilityInstantiation::CreateAsFull_Wow64(true)); + const int wow_pool = 10; + const int total_count = 15; + + for (int i = 1; i < total_count; ++i) + MakeMockProcess(i, i >= wow_pool, base::INTEGRITY_UNKNOWN, i < wow_pool); + PerformCompatibilityCheck(test_instance.get()); +} + +TEST_F(ProcessCompatibilityCheckTest, Only64PassSieve_WithInstantiation) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance( + TestingProcessCompatibilityInstantiation::CreateAsFull_Wow64(false)); + + const int wow_pool = 10; + const int total_count = 15; + + for (int i = 1; i < total_count; ++i) + MakeMockProcess(i, i >= wow_pool, base::INTEGRITY_UNKNOWN, i >= wow_pool); + PerformCompatibilityCheck(test_instance.get()); +} + +TEST_F(ProcessCompatibilityCheckTest, MediumIntegrityFilterOnWin32_Instance) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance( + TestingProcessCompatibilityInstantiation::CreateInstance( + PROCESSOR_ARCHITECTURE_INTEL, false, + base::win::VERSION_WIN7, base::MEDIUM_INTEGRITY)); + + MakeMockProcess(1, false, base::MEDIUM_INTEGRITY, true); + MakeMockProcess(2, false, base::LOW_INTEGRITY, true); + MakeMockProcess(3, false, base::HIGH_INTEGRITY, false); // Can't access. + MakeMockProcess(4, false, base::MEDIUM_INTEGRITY, true); + PerformCompatibilityCheck(test_instance.get()); +} + +TEST_F(ProcessCompatibilityCheckTest, HighIntegrityFilterOnWin32_Instance) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance( + TestingProcessCompatibilityInstantiation::CreateInstance( + PROCESSOR_ARCHITECTURE_INTEL, false, + base::win::VERSION_WIN7, base::HIGH_INTEGRITY)); + + MakeMockProcess(1, false, base::MEDIUM_INTEGRITY, false); + MakeMockProcess(2, false, base::LOW_INTEGRITY, false); + MakeMockProcess(3, false, base::HIGH_INTEGRITY, true); + MakeMockProcess(4, false, base::MEDIUM_INTEGRITY, false); + PerformCompatibilityCheck(test_instance.get()); +} + +TEST_F(ProcessCompatibilityCheckTest, MediumIntegrityFilterOnWow64_Instance) { + scoped_ptr<TestingProcessCompatibilityInstantiation> + test_instance( + TestingProcessCompatibilityInstantiation::CreateInstance( + PROCESSOR_ARCHITECTURE_AMD64, false, + base::win::VERSION_WIN7, base::MEDIUM_INTEGRITY)); + + MakeMockProcess(1, false, base::MEDIUM_INTEGRITY, true); + MakeMockProcess(2, false, base::LOW_INTEGRITY, true); + MakeMockProcess(3, false, base::HIGH_INTEGRITY, false); + MakeMockProcess(4, false, base::MEDIUM_INTEGRITY, true); + MakeMockProcess(5, true, base::MEDIUM_INTEGRITY, false); + MakeMockProcess(6, true, base::HIGH_INTEGRITY, false); + PerformCompatibilityCheck(test_instance.get()); +} + +TEST_F(ProcessCompatibilityCheckTest, MediumIntegrityFilterOnWow64) { + TestingProcessCompatibility::SetUpSingletonProcess( + PROCESSOR_ARCHITECTURE_AMD64, false, + base::win::VERSION_WIN7, base::MEDIUM_INTEGRITY); + + MakeMockProcess(1, false, base::MEDIUM_INTEGRITY, true); + MakeMockProcess(2, false, base::LOW_INTEGRITY, true); + MakeMockProcess(3, false, base::HIGH_INTEGRITY, false); + MakeMockProcess(4, false, base::MEDIUM_INTEGRITY, true); + MakeMockProcess(5, true, base::MEDIUM_INTEGRITY, false); + MakeMockProcess(6, true, base::HIGH_INTEGRITY, false); + PerformCompatibilityCheck(NULL); +} + +} // namespace diff --git a/ceee/common/sidestep_integration.cc b/ceee/common/sidestep_integration.cc new file mode 100644 index 0000000..71e3f10 --- /dev/null +++ b/ceee/common/sidestep_integration.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2010 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. +// +// Integrate SideStep into our project. + +#include "base/logging.h" +#include "ceee/testing/sidestep/integration.h" + +namespace sidestep { + +void AssertImpl(bool assertion_is_true, const char* message) { + DCHECK(assertion_is_true) << message; +} + +void LogImpl(const char* message) { + DLOG(INFO) << message; +} + +} // namespace sidestep diff --git a/ceee/common/sidestep_unittest.cc b/ceee/common/sidestep_unittest.cc new file mode 100644 index 0000000..86832b1 --- /dev/null +++ b/ceee/common/sidestep_unittest.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2010 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. +// +// Unit test for SideStep. + +#include "ceee/testing/sidestep/integration.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SideStep, AllTests) { + EXPECT_TRUE(sidestep::UnitTests()); +} diff --git a/ceee/common/window_utils.cc b/ceee/common/window_utils.cc new file mode 100644 index 0000000..059c6ec --- /dev/null +++ b/ceee/common/window_utils.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2010 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. +// +// Utilities for windows (as in HWND). + +#include "ceee/common/window_utils.h" + +#include "base/logging.h" +#include "ceee/common/com_utils.h" +#include "ceee/common/process_utils_win.h" + +namespace { + +static bool CanAccessWindowProcess(HWND window) { + bool can_access = false; + + return SUCCEEDED(process_utils_win::ProcessCompatibilityCheck::IsCompatible( + window, &can_access)) && can_access; +} + +} // namespace + +namespace window_utils { + +bool IsWindowClass(HWND window, const std::wstring& window_class) { + if (!::IsWindow(window)) { + // No DCHECK, we may get her while a window is being destroyed. + return false; + } + + wchar_t class_name[MAX_PATH] = { 0 }; + int chars_copied = ::GetClassName(window, class_name, arraysize(class_name)); + LOG_IF(ERROR, chars_copied == 0) << "GetClassName, err=" << ::GetLastError(); + + return (window_class == class_name); +} + +HWND GetTopLevelParent(HWND window) { + HWND top_level = window; + while (HWND parent = ::GetParent(top_level)) { + top_level = parent; + } + return top_level; +} + +bool IsWindowThread(HWND window) { + DWORD process_id = 0; + return ::GetCurrentThreadId() == ::GetWindowThreadProcessId(window, + &process_id) && ::GetCurrentProcessId() == process_id; +} + +bool WindowHasNoThread(HWND window) { + return ::GetWindowThreadProcessId(window, NULL) == 0; +} + +// Changes made to this struct must also be made to the unittest code. +struct FindDescendentData { + FindDescendentData(const std::wstring& window_class, bool only_visible, + bool check_compatibility) + : window_class(window_class), only_visible(only_visible), + check_process_access(check_compatibility), descendent(NULL) { + } + const std::wstring& window_class; + bool only_visible; + HWND descendent; + bool check_process_access; +}; + +BOOL CALLBACK HandleDescendentWindowProc(HWND window, LPARAM param) { + DCHECK(window && param); + FindDescendentData* data = reinterpret_cast<FindDescendentData*>(param); + if (data && IsWindowClass(window, data->window_class) && + (!data->only_visible || IsWindowVisible(window)) && + (!data->check_process_access || CanAccessWindowProcess(window))) { + data->descendent = window; + return FALSE; + } + return TRUE; +} + +bool FindDescendentWindowEx( + HWND ancestor, const std::wstring& window_class, + bool only_visible, bool only_accessible, HWND* descendent) { + FindDescendentData data(window_class, only_visible, only_accessible); + ::EnumChildWindows(ancestor, HandleDescendentWindowProc, + reinterpret_cast<LPARAM>(&data)); + if (data.descendent != NULL) { + if (descendent != NULL) + *descendent = data.descendent; + return true; + } else { + return false; + } +} + +bool FindDescendentWindow( + HWND ancestor, const std::wstring& window_class, bool only_visible, + HWND* descendent) { + return FindDescendentWindowEx(ancestor, window_class, + only_visible, true, descendent); +} + +struct FindTopLevelWindowsData { + FindTopLevelWindowsData(const std::wstring& window_class, + bool check_compatibility, + std::set<HWND>* windows) + : window_class(window_class), + windows(windows), + check_process_access(check_compatibility) { + } + const std::wstring& window_class; + std::set<HWND>* windows; + bool check_process_access; +}; + +BOOL CALLBACK HandleChildWindowProc(HWND window, LPARAM param) { + DCHECK(window && param); + FindTopLevelWindowsData* data = + reinterpret_cast<FindTopLevelWindowsData*>(param); + if (data != NULL && (data->window_class.empty() || + IsWindowClass(window, data->window_class)) && + (!data->check_process_access || CanAccessWindowProcess(window))) { + DCHECK(data->windows != NULL); + data->windows->insert(window); + } + return TRUE; +} + +void FindTopLevelWindowsEx(const std::wstring& window_class, bool only_visible, + std::set<HWND>* top_level_windows) { + FindTopLevelWindowsData find_data(window_class, only_visible, + top_level_windows); + ::EnumWindows(HandleChildWindowProc, reinterpret_cast<LPARAM>(&find_data)); +} + +void FindTopLevelWindows(const std::wstring& window_class, + std::set<HWND>* top_level_windows) { + FindTopLevelWindowsEx(window_class, true, top_level_windows); +} + +} // namespace window_utils diff --git a/ceee/common/window_utils.h b/ceee/common/window_utils.h new file mode 100644 index 0000000..f77bbed --- /dev/null +++ b/ceee/common/window_utils.h @@ -0,0 +1,76 @@ +// Copyright (c) 2010 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. +// +// Utilities for windows (as in HWND). + +#ifndef CEEE_COMMON_WINDOW_UTILS_H_ +#define CEEE_COMMON_WINDOW_UTILS_H_ + +#include <windows.h> + +#include <set> +#include <string> + +namespace window_utils { + +// Returns true if the given window is of the specified window class. +bool IsWindowClass(HWND window, const std::wstring& window_class); + +// Returns the top-level window that is an ancestor of the provided window. +// Note that this will return the window passed in if it has no parent, or if +// it is not a window. +HWND GetTopLevelParent(HWND window); + +// Returns true if we are currently running in the given HWND's thread. +bool IsWindowThread(HWND window); + +// Returns true if <p> window is not associated to any thread anymore. +bool WindowHasNoThread(HWND window); + +// Returns true if we could find a descendent of the ancestor window with the +// specified window class, and visible if @p only_visible is true. +// Also returns the HWND of the descendent in @p descendent. +// +// @param ancestor The ancestor window to parse the descendent hierarchy of. +// As documented in EnumChildWindows, if ancestor is NULL, we only check +// top level windows. +// @param window_class The window class we are looking for. +// @param only_visible Specify whether we only want a visible descendent +// (if true) or don't care about the visibility (if false). +// @param only_accessible Specify if windows associated with processes not +// accessible from the current process are to be filtered out. Particularly +// relevant if ancestor == NULL. +// @param descendent Where to return the handle of the descendent window, +// or NULL if you don't need the handle. +bool FindDescendentWindowEx(HWND ancestor, + const std::wstring& window_class, + bool only_visible, + bool only_accessible, + HWND* descendent); + +// Abbreviated version of the above, provided for backward compatibility. +// Assumes only_accessible == true. +bool FindDescendentWindow(HWND ancestor, + const std::wstring& window_class, + bool only_visible, + HWND* descendent); + + +// Finds all the top level windows. The @p top_level_windows are filtered +// by the provided @p window_class name if it's not empty. An empty +// @p window_class name means that we are looking for all children. +// @p only_visible means that windows associated with processes not accessible +// from the current process are to be filtered out. +void FindTopLevelWindowsEx(const std::wstring& window_class, + bool only_visible, + std::set<HWND>* top_level_windows); + +// Abbreviated version of the above, provided for backward compatibility. +// Assumes only_visible == true. +void FindTopLevelWindows(const std::wstring& window_class, + std::set<HWND>* top_level_windows); + +} // namespace window_utils + +#endif // CEEE_COMMON_WINDOW_UTILS_H_ diff --git a/ceee/common/window_utils_unittest.cc b/ceee/common/window_utils_unittest.cc new file mode 100644 index 0000000..a1902ee --- /dev/null +++ b/ceee/common/window_utils_unittest.cc @@ -0,0 +1,417 @@ +// Copyright (c) 2010 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. +// +// Tests for utilities for windows (as in HWND). + +#include "ceee/common/window_utils.h" + +#include "base/process_util.h" +#include "ceee/common/process_utils_win.h" +#include "ceee/testing/utils/mock_win32.h" +#include "ceee/testing/utils/test_utils.h" +#include "ceee/common/windows_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::NotNull; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; +using testing::WithArgs; + +TEST(WindowUtils, IsWindowClas) { + HWND window = ::GetDesktopWindow(); + ASSERT_TRUE(window); + EXPECT_TRUE(window_utils::IsWindowClass(window, + windows::kDesktopWindowClass)); + EXPECT_FALSE(window_utils::IsWindowClass( + window, std::wstring(windows::kDesktopWindowClass) + L"stop")); +} + +TEST(WindowUtils, IsWindowClassNotWindow) { + testing::LogDisabler no_dcheck; + ASSERT_FALSE(window_utils::IsWindowClass(NULL, L"Foofoo")); +} + +TEST(WindowUtils, GetTopLevelParent) { + // Identity + HWND desktop_window = ::GetDesktopWindow(); + ASSERT_EQ(desktop_window, window_utils::GetTopLevelParent(desktop_window)); + + // Child of a child + HWND tray_window = ::FindWindowEx(NULL, NULL, + windows::kShellTrayWindowClass, NULL); + HWND notify_window = ::FindWindowEx(tray_window, NULL, + windows::kTrayNotifyWindowClass, NULL); + HWND child_of_notify_window = ::FindWindowEx(notify_window, NULL, + NULL, NULL); + ASSERT_TRUE(tray_window); + ASSERT_TRUE(child_of_notify_window); + ASSERT_EQ(tray_window, + window_utils::GetTopLevelParent(child_of_notify_window)); + + // Not a window + StrictMock<testing::MockUser32> user32; + EXPECT_CALL(user32, GetParent(_)).WillOnce(Return((HWND)NULL)); + ASSERT_EQ((HWND)0x42, window_utils::GetTopLevelParent((HWND)0x42)); +} + +TEST(WindowUtils, IsWindowThread) { + DWORD process_id = ::GetCurrentProcessId(); + DWORD thread_id = ::GetCurrentThreadId(); + + // Success. + StrictMock<testing::MockUser32> user32; + EXPECT_CALL(user32, GetWindowThreadProcessId((HWND)42, NotNull())). + WillOnce(DoAll(SetArgumentPointee<1>(process_id), Return(thread_id))); + EXPECT_TRUE(window_utils::IsWindowThread((HWND)42)); + + // Wrong thread. + EXPECT_CALL(user32, GetWindowThreadProcessId((HWND)42, NotNull())). + WillOnce(DoAll(SetArgumentPointee<1>(process_id), Return(0))); + EXPECT_FALSE(window_utils::IsWindowThread((HWND)42)); + + // Wrong process. + EXPECT_CALL(user32, GetWindowThreadProcessId((HWND)42, NotNull())). + WillOnce(DoAll(SetArgumentPointee<1>(0), Return(thread_id))); + EXPECT_FALSE(window_utils::IsWindowThread((HWND)42)); +} + +TEST(WindowUtils, WindowHasNoThread) { + // Success. + StrictMock<testing::MockUser32> user32; + EXPECT_CALL(user32, GetWindowThreadProcessId((HWND)42, NULL)). + WillOnce(Return(1)); // Any non-zero value will do... + EXPECT_FALSE(window_utils::WindowHasNoThread((HWND)42)); + + // No thread for window. + EXPECT_CALL(user32, GetWindowThreadProcessId((HWND)42, NULL)). + WillOnce(Return(0)); + EXPECT_TRUE(window_utils::WindowHasNoThread((HWND)42)); +} + +MOCK_STATIC_CLASS_BEGIN(MockProcessIntegrityQuery) + MOCK_STATIC_INIT_BEGIN(MockProcessIntegrityQuery) + MOCK_STATIC_INIT2(base::GetProcessIntegrityLevel, + GetProcessIntegrityLevel); + MOCK_STATIC_INIT_END() + + MOCK_STATIC2(bool, , GetProcessIntegrityLevel, HANDLE, + base::IntegrityLevel*); +MOCK_STATIC_CLASS_END(MockProcessIntegrityQuery) + +MOCK_STATIC_CLASS_BEGIN(PartialMockWindowUtils) + MOCK_STATIC_INIT_BEGIN(PartialMockWindowUtils) + MOCK_STATIC_INIT2(window_utils::IsWindowClass, IsWindowClass); + MOCK_STATIC_INIT_END() + + MOCK_STATIC2(bool, , IsWindowClass, HWND, const std::wstring&); +MOCK_STATIC_CLASS_END(PartialMockWindowUtils) + + +class MockCompatibilityCheck { + public: + MockCompatibilityCheck(bool on_64_bit, + base::IntegrityLevel current_process_integrity) { + MockAccess::SetUp(on_64_bit, + current_process_integrity != base::INTEGRITY_UNKNOWN, + current_process_integrity); + mock_integrity_ = new MockProcessIntegrityQuery(); + EXPECT_CALL(*mock_integrity_, GetProcessIntegrityLevel(_, _)). + Times(testing::AnyNumber()).WillRepeatedly(Invoke( + MockCompatibilityCheck::MockProcessIntegrityLevel)); + EXPECT_CALL(mock_user32_, GetWindowThreadProcessId(_, _)). + Times(testing::AnyNumber()).WillRepeatedly(Invoke( + MockCompatibilityCheck::MockThreadProcessFromWindow)); + EXPECT_CALL(mock_user32_, IsWindow(_)).Times(0); + EXPECT_CALL(mock_user32_, IsWindowVisible(_)). + Times(testing::AnyNumber()).WillRepeatedly(Invoke( + MockCompatibilityCheck::MockIsWindowVisible)); + EXPECT_CALL(mock_window_tests_, IsWindowClass(_, _)). + Times(testing::AnyNumber()).WillRepeatedly(Return(true)); + } + + ~MockCompatibilityCheck() { + delete mock_integrity_; + mock_integrity_ = NULL; + for (WindowsMap::iterator it = mock_windows_.begin(); + it != mock_windows_.end(); ++it) { + delete it->second; + } + mock_windows_.clear(); + process_id_reference_.clear(); + process_handle_reference_.clear(); + MockAccess::TearDown(); + } + + void AddWindow(HWND fake_window, bool visible, + base::IntegrityLevel owner_integrity, bool process_is_64) { + ASSERT_TRUE(mock_windows_.find(fake_window) == mock_windows_.end()); + MockWindowRepresentation * new_window = + new MockWindowRepresentation(fake_window, visible, process_is_64, + owner_integrity); + mock_windows_[fake_window] = new_window; + process_id_reference_[new_window->owning_process_id] = new_window; + } + + static BOOL MockEnumWindows(WNDENUMPROC enum_proc, LPARAM param) { + for (WindowsMap::const_iterator it = mock_windows_.begin(); + it != mock_windows_.end(); ++it) { + if (!(enum_proc(it->first, param))) + break; + } + return TRUE; + } + + StrictMock<testing::MockUser32> mock_user32_; + + protected: + class MockAccess : public process_utils_win::ProcessCompatibilityCheck { + public: + static void SetUp(bool on_64_bit, bool integrity_checks, + base::IntegrityLevel current_process_integrity) { + PatchState(on_64_bit ? PROCESSOR_ARCHITECTURE_AMD64 + : PROCESSOR_ARCHITECTURE_INTEL, + on_64_bit, integrity_checks, current_process_integrity, + MockCompatibilityCheck::SpoofOpenProcess, + MockCompatibilityCheck::SpoofCloseHandle, + MockCompatibilityCheck::SpoofIsWow64Process); + } + + static void TearDown() { + ResetState(); + } + }; + + struct MockWindowRepresentation { + MockWindowRepresentation(HWND window_handle, bool window_visible, + bool is_64_bit, base::IntegrityLevel integrity) { + window = window_handle; + visible = window_visible; + owning_process_is_64 = is_64_bit; + owning_process_integrity = integrity; + owning_process_id = reinterpret_cast<int>(window_handle) + 1000; + } + + HWND window; + bool visible; + DWORD owning_process_id; + bool owning_process_is_64; + base::IntegrityLevel owning_process_integrity; + }; + + // Own version of IsWow64Process. Parameter names and types to match + // kernel32 function's signature. + static BOOL WINAPI SpoofIsWow64Process(HANDLE hProcess, PBOOL Wow64Process) { + ProcessHandleMap::const_iterator window_it = + process_handle_reference_.find(hProcess); + if (window_it != process_handle_reference_.end()) { + *Wow64Process = !window_it->second->owning_process_is_64; + } else { + *Wow64Process = TRUE; + } + return TRUE; + } + + // Own version of OpenProcess. Issues handles as required. + static HANDLE WINAPI SpoofOpenProcess(DWORD dwDesiredAccess, + BOOL bInheritHandle, + DWORD dwProcessId) { + ProcessIdMap::const_iterator found_window_it = + process_id_reference_.find(dwProcessId); + if (found_window_it != process_id_reference_.end()) { + HANDLE expected_handle_value = + reinterpret_cast<HANDLE>(found_window_it->second->window); + process_handle_reference_[expected_handle_value] = + found_window_it->second; + return expected_handle_value; + } + + return NULL; + } + + static BOOL WINAPI SpoofCloseHandle(HANDLE hObject) { + return process_handle_reference_.erase(hObject) == 1; + } + + static bool MockProcessIntegrityLevel(HANDLE process, + base::IntegrityLevel* level) { + ProcessHandleMap::const_iterator window_it = + process_handle_reference_.find(process); + if (window_it != process_handle_reference_.end()) { + *level = window_it->second->owning_process_integrity; + return true; + } + return false; + } + + static DWORD MockThreadProcessFromWindow(HWND window, DWORD* process_id) { + WindowsMap::const_iterator window_it = mock_windows_.find(window); + if (window_it != mock_windows_.end()) { + *process_id = window_it->second->owning_process_id; + return 42; + } else { + *process_id = 0; + return 0; + } + } + + static BOOL MockIsWindowVisible(HWND window) { + WindowsMap::const_iterator window_it = mock_windows_.find(window); + if (window_it != mock_windows_.end()) + return window_it->second->visible; + + return FALSE; + } + + typedef std::map<HWND, MockWindowRepresentation*> WindowsMap; + typedef std::map<DWORD, MockWindowRepresentation*> ProcessIdMap; + typedef std::map<HANDLE, MockWindowRepresentation*> ProcessHandleMap; + + MockProcessIntegrityQuery* mock_integrity_; + PartialMockWindowUtils mock_window_tests_; + + static WindowsMap mock_windows_; + static ProcessIdMap process_id_reference_; + static ProcessHandleMap process_handle_reference_; +}; + +MockCompatibilityCheck::WindowsMap MockCompatibilityCheck::mock_windows_; +MockCompatibilityCheck::ProcessIdMap + MockCompatibilityCheck::process_id_reference_; +MockCompatibilityCheck::ProcessHandleMap + MockCompatibilityCheck::process_handle_reference_; + +const HWND kDescendent = reinterpret_cast<HWND>(24); +struct FindDescendentData { + const std::wstring& window_class; + bool only_visible; + HWND descendent; +}; + +BOOL MockEnumChildWindows(HWND ancestor, WNDENUMPROC enum_proc, LPARAM param) { + FindDescendentData* data = reinterpret_cast<FindDescendentData*>(param); + if (!data->only_visible) + data->descendent = kDescendent; + return TRUE; +} + +TEST(WindowUtils, FindDescendentWindow) { + testing::LogDisabler no_dcheck; + + // Test failure cases. + StrictMock<testing::MockUser32> user32; + const HWND kAncestor = reinterpret_cast<HWND>(42); + HWND descendent = NULL; + EXPECT_CALL(user32, EnumChildWindows(kAncestor, _, _)). + WillOnce(Return(FALSE)); + EXPECT_FALSE(window_utils::FindDescendentWindow(kAncestor, L"", + false, &descendent)); + EXPECT_EQ(NULL, descendent); + + // Success. + EXPECT_CALL(user32, EnumChildWindows(kAncestor, _, _)). + WillRepeatedly(Invoke(MockEnumChildWindows)); + EXPECT_TRUE(window_utils::FindDescendentWindow(kAncestor, L"", + false, &descendent)); + EXPECT_EQ(kDescendent, descendent); + + // Don't find invisible window if we don't want them. + descendent = NULL; + EXPECT_FALSE(window_utils::FindDescendentWindow(kAncestor, L"", + true, &descendent)); + EXPECT_EQ(NULL, descendent); + + // Still work even if we are not interested in the window handle. + EXPECT_TRUE(window_utils::FindDescendentWindow(kAncestor, L"", false, NULL)); +} + +TEST(WindowUtils, FindDescendentWindowWithFilter) { + const HWND kAncestor = reinterpret_cast<HWND>(444); + HWND descendent = NULL; + MockCompatibilityCheck compatibility_check(true, base::MEDIUM_INTEGRITY); + compatibility_check.AddWindow(reinterpret_cast<HWND>(38), true, + base::HIGH_INTEGRITY, false); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(39), true, + base::LOW_INTEGRITY, true); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(42), true, + base::MEDIUM_INTEGRITY, false); // OK + + EXPECT_CALL(compatibility_check.mock_user32_, + EnumChildWindows(kAncestor, _, _)).WillRepeatedly( + WithArgs<1, 2>(Invoke(MockCompatibilityCheck::MockEnumWindows))); + + EXPECT_TRUE(window_utils::FindDescendentWindow(kAncestor, L"", + false, &descendent)); + EXPECT_EQ(reinterpret_cast<HWND>(42), descendent); +} + +TEST(WindowUtils, FindVisibleDescendentWindowWithFilter) { + const HWND kAncestor = reinterpret_cast<HWND>(444); + HWND descendent = NULL; + MockCompatibilityCheck compatibility_check(true, base::MEDIUM_INTEGRITY); + compatibility_check.AddWindow(reinterpret_cast<HWND>(38), false, + base::HIGH_INTEGRITY, false); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(39), true, + base::LOW_INTEGRITY, true); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(40), false, + base::MEDIUM_INTEGRITY, false); // Invisible. + compatibility_check.AddWindow(reinterpret_cast<HWND>(42), true, + base::MEDIUM_INTEGRITY, false); // OK + + EXPECT_CALL(compatibility_check.mock_user32_, + EnumChildWindows(kAncestor, _, _)).WillRepeatedly( + WithArgs<1, 2>(Invoke(MockCompatibilityCheck::MockEnumWindows))); + + EXPECT_TRUE(window_utils::FindDescendentWindow(kAncestor, L"", + true, &descendent)); + EXPECT_EQ(reinterpret_cast<HWND>(42), descendent); +} + +TEST(WindowUtils, FindWindowsWithoutFilter) { + std::set<HWND> out_set; + MockCompatibilityCheck compatibility_check(true, base::MEDIUM_INTEGRITY); + compatibility_check.AddWindow(reinterpret_cast<HWND>(38), true, + base::HIGH_INTEGRITY, false); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(39), true, + base::LOW_INTEGRITY, true); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(42), true, + base::MEDIUM_INTEGRITY, false); // OK + + EXPECT_CALL(compatibility_check.mock_user32_, + EnumWindows(_, _)).WillRepeatedly(Invoke( + MockCompatibilityCheck::MockEnumWindows)); + + window_utils::FindTopLevelWindowsEx(L"", false, &out_set); + EXPECT_EQ(out_set.size(), 3); + ASSERT_TRUE(out_set.find(reinterpret_cast<HWND>(38)) != out_set.end()); + ASSERT_TRUE(out_set.find(reinterpret_cast<HWND>(39)) != out_set.end()); + ASSERT_TRUE(out_set.find(reinterpret_cast<HWND>(42)) != out_set.end()); +} + +TEST(WindowUtils, FindWindowsWithFilter) { + std::set<HWND> out_set; + MockCompatibilityCheck compatibility_check(true, base::MEDIUM_INTEGRITY); + compatibility_check.AddWindow(reinterpret_cast<HWND>(38), true, + base::HIGH_INTEGRITY, false); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(39), true, + base::LOW_INTEGRITY, true); // Unreachable. + compatibility_check.AddWindow(reinterpret_cast<HWND>(42), true, + base::MEDIUM_INTEGRITY, false); // OK + + EXPECT_CALL(compatibility_check.mock_user32_, + EnumWindows(_, _)).WillRepeatedly(Invoke( + MockCompatibilityCheck::MockEnumWindows)); + + window_utils::FindTopLevelWindows(L"", &out_set); + EXPECT_EQ(out_set.size(), 1); + ASSERT_TRUE(out_set.find(reinterpret_cast<HWND>(42)) != out_set.end()); +} + + +} // namespace diff --git a/ceee/common/windows_constants.cc b/ceee/common/windows_constants.cc new file mode 100644 index 0000000..1562272 --- /dev/null +++ b/ceee/common/windows_constants.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2010 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. +// +// Constants for things in the Windows environment (window class names, +// process names, etc.) + +#include "ceee/common/windows_constants.h" + +namespace windows { + +const wchar_t kExplorerModuleName[] = L"explorer.exe"; +const wchar_t kDesktopWindowClass[] = L"#32769"; +const wchar_t kIeTabWindowClass[] = L"TabWindowClass"; +const wchar_t kIeFrameWindowClass[] = L"IEFrame"; +const wchar_t kHiddenIeFrameWindowClass[] = L"Internet Explorer_Hidden"; +const wchar_t kIeTabContentParentWindowClass[] = L"Shell DocObject View"; +const wchar_t kIeTabContentWindowClass[] = L"Internet Explorer_Server"; +const wchar_t kShellTrayWindowClass[] = L"Shell_TrayWnd"; +const wchar_t kTrayNotifyWindowClass[] = L"TrayNotifyWnd"; + +} // namespace windows diff --git a/ceee/common/windows_constants.h b/ceee/common/windows_constants.h new file mode 100644 index 0000000..9ddb65b --- /dev/null +++ b/ceee/common/windows_constants.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 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. +// +// Constants for things in the Windows environment (window class names, +// process names, etc.) + +#ifndef CEEE_COMMON_WINDOWS_CONSTANTS_H_ +#define CEEE_COMMON_WINDOWS_CONSTANTS_H_ + +namespace windows { + +// Module names + +// Windows Explorer. +extern const wchar_t kExplorerModuleName[]; + +// Window class names + +// The desktop window. +extern const wchar_t kDesktopWindowClass[]; + +// The IE window containing the whole tab. +extern const wchar_t kIeTabWindowClass[]; + +// The top-level IE window. +extern const wchar_t kIeFrameWindowClass[]; + +// A hidden top-level IE window. +extern const wchar_t kHiddenIeFrameWindowClass[]; + +// The parent window of the tab content window. +extern const wchar_t kIeTabContentParentWindowClass[]; + +// The IE window containing the tab content. +extern const wchar_t kIeTabContentWindowClass[]; + +// The shell's tray window. +extern const wchar_t kShellTrayWindowClass[]; + +// The shell's notification window. +extern const wchar_t kTrayNotifyWindowClass[]; + +} // namespace windows + +#endif // CEEE_COMMON_WINDOWS_CONSTANTS_H_ |