diff options
author | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 19:38:52 +0000 |
---|---|---|
committer | ananta@chromium.org <ananta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-16 19:38:52 +0000 |
commit | 6a8515e44d88fcd704dc871d7c07314ceb3323bb (patch) | |
tree | f3c82ed61a3e191313975b6f1afe515709e0cba0 /chrome_frame | |
parent | 457592496d7b0ad20c7f05ac3e1141073e134e6f (diff) | |
download | chromium_src-6a8515e44d88fcd704dc871d7c07314ceb3323bb.zip chromium_src-6a8515e44d88fcd704dc871d7c07314ceb3323bb.tar.gz chromium_src-6a8515e44d88fcd704dc871d7c07314ceb3323bb.tar.bz2 |
Add the ChromeFrame UA in the post platform section in the IE user agent string. To achieve
this we need to add the desired value in the registry under the
Internet Settings\User Agent\Post Platform key. We add this under the per user or per machine
section in the registry depending on whether ChromeFrame is being installed in per user or
per machine mode. This fixes the bug where certain top level requests in IE would not have
the chrome frame UA appended to the IE UA.
Removed the BeginningTransaction vtable patch code, which only existed to add chrome frame to
the UA in outgoing requests.
The limitation with registering the chrome frame UA in the registry is that it will be sent
out even in the ChromeFrame BHO is disabled.
Fixes bug http://code.google.com/p/chromium/issues/detail?id=45087
BUG=45087
TESt=Covered by new chrome frame test.
Review URL: http://codereview.chromium.org/5831002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69442 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r-- | chrome_frame/bho.cc | 5 | ||||
-rw-r--r-- | chrome_frame/chrome_frame_automation.cc | 9 | ||||
-rw-r--r-- | chrome_frame/chrome_tab.cc | 36 | ||||
-rw-r--r-- | chrome_frame/http_negotiate.cc | 129 | ||||
-rw-r--r-- | chrome_frame/http_negotiate.h | 47 | ||||
-rw-r--r-- | chrome_frame/test/http_negotiate_unittest.cc | 106 | ||||
-rw-r--r-- | chrome_frame/test/navigation_test.cc | 82 | ||||
-rw-r--r-- | chrome_frame/utils.cc | 7 | ||||
-rw-r--r-- | chrome_frame/utils.h | 3 |
9 files changed, 116 insertions, 308 deletions
diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc index 35623b3..7fbc473 100644 --- a/chrome_frame/bho.cc +++ b/chrome_frame/bho.cc @@ -319,7 +319,6 @@ bool PatchHelper::InitializeAndPatchProtocolsIfNeeded() { if (state_ == UNKNOWN) { g_trans_hooks.InstallHooks(); - HttpNegotiatePatch::Initialize(); state_ = PATCH_PROTOCOL; ret = true; } @@ -343,10 +342,6 @@ void PatchHelper::UnpatchIfNeeded() { } else if (state_ == PATCH_IBROWSER) { vtable_patch::UnpatchInterfaceMethods(IBrowserService_PatchInfo); MonikerPatch::Uninitialize(); - HttpNegotiatePatch::Uninitialize(); - } else { - HttpNegotiatePatch::Uninitialize(); } - state_ = UNKNOWN; } diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc index e0cab31..b25b89d 100644 --- a/chrome_frame/chrome_frame_automation.cc +++ b/chrome_frame/chrome_frame_automation.cc @@ -46,15 +46,6 @@ static const wchar_t kUmaSendIntervalValue[] = L"UmaSendInterval"; // threads. Lock g_ChromeFrameHistogramLock; -namespace { -std::wstring GetCurrentModuleVersion() { - scoped_ptr<FileVersionInfo> module_version_info( - FileVersionInfo::CreateFileVersionInfoForCurrentModule()); - DCHECK(module_version_info.get() != NULL); - return module_version_info->file_version(); -} -} - class ChromeFrameAutomationProxyImpl::TabProxyNotificationMessageFilter : public IPC::ChannelProxy::MessageFilter { public: diff --git a/chrome_frame/chrome_tab.cc b/chrome_frame/chrome_tab.cc index f3f4bd1..79c846d 100644 --- a/chrome_frame/chrome_tab.cc +++ b/chrome_frame/chrome_tab.cc @@ -417,6 +417,35 @@ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _AtlModule.DllGetClassObject(rclsid, riid, ppv); } +const wchar_t kPostPlatformUAKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\" + L"User Agent\\Post Platform"; +const wchar_t kChromeFramePrefix[] = L"chromeframe/"; + +// To delete the user agent, set value to NULL. +// The is_system parameter indicates whether this is a per machine or a per +// user installation. +HRESULT SetChromeFrameUA(bool is_system, const wchar_t* value) { + HRESULT hr = E_FAIL; + HKEY parent_hive = is_system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + RegKey ua_key; + if (ua_key.Create(parent_hive, kPostPlatformUAKey, KEY_WRITE)) { + std::wstring chrome_frame_ua_value_name = kChromeFramePrefix; + chrome_frame_ua_value_name += GetCurrentModuleVersion(); + if (value) { + ua_key.WriteValue(chrome_frame_ua_value_name.c_str(), value); + } else { + ua_key.DeleteValue(chrome_frame_ua_value_name.c_str()); + } + hr = S_OK; + } else { + DLOG(ERROR) << __FUNCTION__ << ": " << kPostPlatformUAKey; + hr = E_UNEXPECTED; + } + return hr; +} + enum RegistrationFlags { ACTIVEX = 0x0001, ACTIVEDOC = 0x0002, @@ -500,6 +529,13 @@ STDAPI CustomRegistration(UINT reg_flags, BOOL reg, bool is_system) { hr = _AtlModule.UpdateRegistryAppId(reg); } + if (hr == S_OK) { + if (reg) { + hr = SetChromeFrameUA(is_system, L"1"); + } else { + hr = SetChromeFrameUA(is_system, NULL); + } + } return hr; } diff --git a/chrome_frame/http_negotiate.cc b/chrome_frame/http_negotiate.cc index b593f28..85701c3 100644 --- a/chrome_frame/http_negotiate.cc +++ b/chrome_frame/http_negotiate.cc @@ -32,56 +32,6 @@ const char kLowerCaseUserAgent[] = "user-agent"; // TODO(robertshield): Remove this once we update our SDK version. const int LOCAL_BINDSTATUS_SERVER_MIMETYPEAVAILABLE = 54; -static const int kHttpNegotiateBeginningTransactionIndex = 3; - -BEGIN_VTABLE_PATCHES(IHttpNegotiate) - VTABLE_PATCH_ENTRY(kHttpNegotiateBeginningTransactionIndex, - HttpNegotiatePatch::BeginningTransaction) -END_VTABLE_PATCHES() - -namespace { - -class SimpleBindStatusCallback : public CComObjectRootEx<CComSingleThreadModel>, - public IBindStatusCallback { - public: - BEGIN_COM_MAP(SimpleBindStatusCallback) - COM_INTERFACE_ENTRY(IBindStatusCallback) - END_COM_MAP() - - // IBindStatusCallback implementation - STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding) { - return E_NOTIMPL; - } - - STDMETHOD(GetPriority)(LONG* priority) { - return E_NOTIMPL; - } - STDMETHOD(OnLowResource)(DWORD reserved) { - return E_NOTIMPL; - } - - STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress, - ULONG status_code, LPCWSTR status_text) { - return E_NOTIMPL; - } - STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error) { - return E_NOTIMPL; - } - - STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) { - return E_NOTIMPL; - } - - STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc, - STGMEDIUM* storage) { - return E_NOTIMPL; - } - STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object) { - return E_NOTIMPL; - } -}; -} // end namespace - std::string AppendCFUserAgentString(LPCWSTR headers, LPCWSTR additional_headers) { using net::HttpUtil; @@ -157,82 +107,3 @@ std::string ReplaceOrAddUserAgent(LPCWSTR headers, return new_headers; } -HttpNegotiatePatch::HttpNegotiatePatch() { -} - -HttpNegotiatePatch::~HttpNegotiatePatch() { -} - -// static -bool HttpNegotiatePatch::Initialize() { - if (IS_PATCHED(IHttpNegotiate)) { - DLOG(WARNING) << __FUNCTION__ << " called more than once."; - return true; - } - // Use our SimpleBindStatusCallback class as we need a temporary object that - // implements IBindStatusCallback. - CComObjectStackEx<SimpleBindStatusCallback> request; - ScopedComPtr<IBindCtx> bind_ctx; - HRESULT hr = CreateAsyncBindCtx(0, &request, NULL, bind_ctx.Receive()); - DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx"; - if (bind_ctx) { - ScopedComPtr<IUnknown> bscb_holder; - bind_ctx->GetObjectParam(L"_BSCB_Holder_", bscb_holder.Receive()); - if (bscb_holder) { - hr = PatchHttpNegotiate(bscb_holder); - } else { - NOTREACHED() << "Failed to get _BSCB_Holder_"; - hr = E_UNEXPECTED; - } - bind_ctx.Release(); - } - - return SUCCEEDED(hr); -} - -// static -void HttpNegotiatePatch::Uninitialize() { - vtable_patch::UnpatchInterfaceMethods(IHttpNegotiate_PatchInfo); -} - -// static -HRESULT HttpNegotiatePatch::PatchHttpNegotiate(IUnknown* to_patch) { - DCHECK(to_patch); - DCHECK_IS_NOT_PATCHED(IHttpNegotiate); - - ScopedComPtr<IHttpNegotiate> http; - HRESULT hr = http.QueryFrom(to_patch); - if (FAILED(hr)) { - hr = DoQueryService(IID_IHttpNegotiate, to_patch, http.Receive()); - } - - if (http) { - hr = vtable_patch::PatchInterfaceMethods(http, IHttpNegotiate_PatchInfo); - DLOG_IF(ERROR, FAILED(hr)) - << base::StringPrintf("HttpNegotiate patch failed 0x%08X", hr); - } else { - DLOG(WARNING) - << base::StringPrintf("IHttpNegotiate not supported 0x%08X", hr); - } - return hr; -} - -// static -HRESULT HttpNegotiatePatch::BeginningTransaction( - IHttpNegotiate_BeginningTransaction_Fn original, IHttpNegotiate* me, - LPCWSTR url, LPCWSTR headers, DWORD reserved, LPWSTR* additional_headers) { - DVLOG(1) << __FUNCTION__ << " " << url << " headers:\n" << headers; - - HRESULT hr = original(me, url, headers, reserved, additional_headers); - - if (FAILED(hr)) { - DLOG(WARNING) << __FUNCTION__ << " Delegate returned an error"; - return hr; - } - std::string updated(AppendCFUserAgentString(headers, *additional_headers)); - *additional_headers = reinterpret_cast<wchar_t*>(::CoTaskMemRealloc( - *additional_headers, (updated.length() + 1) * sizeof(wchar_t))); - lstrcpyW(*additional_headers, ASCIIToWide(updated).c_str()); - return S_OK; -} - diff --git a/chrome_frame/http_negotiate.h b/chrome_frame/http_negotiate.h index eb17c7f..151ad23 100644 --- a/chrome_frame/http_negotiate.h +++ b/chrome_frame/http_negotiate.h @@ -12,53 +12,6 @@ #include "base/basictypes.h" #include "base/scoped_comptr_win.h" -// Typedefs for IHttpNegotiate methods. -typedef HRESULT (STDMETHODCALLTYPE* IHttpNegotiate_BeginningTransaction_Fn)( - IHttpNegotiate* me, LPCWSTR url, LPCWSTR headers, DWORD reserved, - LPWSTR* additional_headers); -typedef HRESULT (STDMETHODCALLTYPE* IHttpNegotiate_OnResponse_Fn)( - IHttpNegotiate* me, DWORD response_code, LPCWSTR response_header, - LPCWSTR request_header, LPWSTR* additional_request_headers); - -// Typedefs for IBindStatusCallback methods. -typedef HRESULT (STDMETHODCALLTYPE* IBindStatusCallback_StartBinding_Fn)( - IBindStatusCallback* me, DWORD reserved, IBinding *binding); - -// Typedefs for IInternetProtocolSink methods. -typedef HRESULT (STDMETHODCALLTYPE* IInternetProtocolSink_ReportProgress_Fn)( - IInternetProtocolSink* me, ULONG status_code, LPCWSTR status_text); - -// Patches methods of urlmon's IHttpNegotiate implementation for the purposes -// of adding to the http user agent header. - -// Also patches one of the IBindStatusCallback implementations in urlmon to pick -// up an IBinding during the StartBinding call. The IBinding implementor then -// gets a patch applied to its IInternetProtocolSink's ReportProgress method. -// The patched is there so that the reporting of the MIME type to the IBinding -// implementor can be changed if an X-Chrome-Frame HTTP header is present -// in the response headers. If anyone can suggest a more straightforward way of -// doing this, I would be eternally grateful. -class HttpNegotiatePatch { - // class is not to be instantiated atm. - HttpNegotiatePatch(); - ~HttpNegotiatePatch(); - - public: - static bool Initialize(); - static void Uninitialize(); - - // IHttpNegotiate patch methods - static STDMETHODIMP BeginningTransaction( - IHttpNegotiate_BeginningTransaction_Fn original, IHttpNegotiate* me, - LPCWSTR url, LPCWSTR headers, DWORD reserved, LPWSTR* additional_headers); - - protected: - static HRESULT PatchHttpNegotiate(IUnknown* to_patch); - - private: - DISALLOW_COPY_AND_ASSIGN(HttpNegotiatePatch); -}; - // From the latest urlmon.h. Symbol name prepended with LOCAL_ to // avoid conflict (and therefore build errors) for those building with // a newer Windows SDK. diff --git a/chrome_frame/test/http_negotiate_unittest.cc b/chrome_frame/test/http_negotiate_unittest.cc index c56034b..857197e 100644 --- a/chrome_frame/test/http_negotiate_unittest.cc +++ b/chrome_frame/test/http_negotiate_unittest.cc @@ -16,112 +16,6 @@ #include "gtest/gtest.h" #include "gmock/gmock.h" -class HttpNegotiateTest : public testing::Test { - protected: - HttpNegotiateTest() { - } -}; - -class TestHttpNegotiate - : public CComObjectRootEx<CComMultiThreadModel>, - public IHttpNegotiate { - public: - TestHttpNegotiate() - : beginning_transaction_ret_(S_OK), additional_headers_(NULL) { - } - -BEGIN_COM_MAP(TestHttpNegotiate) - COM_INTERFACE_ENTRY(IHttpNegotiate) -END_COM_MAP() - STDMETHOD(BeginningTransaction)(LPCWSTR url, LPCWSTR headers, // NOLINT - DWORD reserved, // NOLINT - LPWSTR* additional_headers) { // NOLINT - if (additional_headers_) { - int len = lstrlenW(additional_headers_); - len++; - *additional_headers = reinterpret_cast<wchar_t*>( - ::CoTaskMemAlloc(len * sizeof(wchar_t))); - lstrcpyW(*additional_headers, additional_headers_); - } - return beginning_transaction_ret_; - } - - STDMETHOD(OnResponse)(DWORD response_code, LPCWSTR response_header, - LPCWSTR request_header, - LPWSTR* additional_request_headers) { - return S_OK; - } - - HRESULT beginning_transaction_ret_; - const wchar_t* additional_headers_; -}; - -TEST_F(HttpNegotiateTest, BeginningTransaction) { - static const int kBeginningTransactionIndex = 3; - CComObjectStackEx<TestHttpNegotiate> test_http; - IHttpNegotiate_BeginningTransaction_Fn original = - reinterpret_cast<IHttpNegotiate_BeginningTransaction_Fn>( - (*reinterpret_cast<void***>( - static_cast<IHttpNegotiate*>( - &test_http)))[kBeginningTransactionIndex]); - - std::wstring cf_ua( - ASCIIToWide(http_utils::GetDefaultUserAgentHeaderWithCFTag())); - std::wstring cf_tag( - ASCIIToWide(http_utils::GetChromeFrameUserAgent())); - - EXPECT_NE(std::wstring::npos, cf_ua.find(cf_tag)); - - struct TestCase { - const std::wstring original_headers_; - const std::wstring delegate_additional_; - const std::wstring expected_additional_; - HRESULT delegate_return_value_; - } test_cases[] = { - { L"Accept: */*\r\n", - L"", - cf_ua + L"\r\n", - S_OK }, - { L"Accept: */*\r\n", - L"", - L"", - E_OUTOFMEMORY }, - { L"", - L"Accept: */*\r\n", - L"Accept: */*\r\n" + cf_ua + L"\r\n", - S_OK }, - { L"User-Agent: Bingo/1.0\r\n", - L"", - L"User-Agent: Bingo/1.0 " + cf_tag + L"\r\n", - S_OK }, - { L"User-Agent: NotMe/1.0\r\n", - L"User-Agent: MeMeMe/1.0\r\n", - L"User-Agent: MeMeMe/1.0 " + cf_tag + L"\r\n", - S_OK }, - { L"", - L"User-Agent: MeMeMe/1.0\r\n", - L"User-Agent: MeMeMe/1.0 " + cf_tag + L"\r\n", - S_OK }, - }; - - for (int i = 0; i < arraysize(test_cases); ++i) { - TestCase& test = test_cases[i]; - wchar_t* additional = NULL; - test_http.beginning_transaction_ret_ = test.delegate_return_value_; - test_http.additional_headers_ = test.delegate_additional_.c_str(); - HttpNegotiatePatch::BeginningTransaction(original, &test_http, - L"http://www.google.com", test.original_headers_.c_str(), 0, - &additional); - EXPECT_TRUE(additional != NULL); - - if (additional) { - // Check against the expected additional headers. - EXPECT_EQ(test.expected_additional_, std::wstring(additional)); - ::CoTaskMemFree(additional); - } - } -} - class TestInternetProtocolSink : public CComObjectRootEx<CComMultiThreadModel>, public IInternetProtocolSink { diff --git a/chrome_frame/test/navigation_test.cc b/chrome_frame/test/navigation_test.cc index 457793d..7e157ee 100644 --- a/chrome_frame/test/navigation_test.cc +++ b/chrome_frame/test/navigation_test.cc @@ -11,6 +11,7 @@ #include "chrome_frame/test/chrome_frame_ui_test_utils.h" #include "chrome_frame/test/mock_ie_event_sink_actions.h" #include "chrome_frame/test/mock_ie_event_sink_test.h" +#include "net/http/http_util.h" // Needed for CreateFunctor. #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING @@ -922,19 +923,20 @@ TEST_P(FullTabNavigationTest, RefreshContents) { return; } + const char kHeaders[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n" + "X-UA-Compatible: chrome=1\r\n"; + + const char kBody[] = "<html><body>Hi there. Got new content?" + "</body></html>"; + std::wstring src_url = server_mock_.Resolve(L"/refresh_src.html"); EXPECT_CALL(server_mock_, Get(_, StrEq(L"/refresh_src.html"), _)) .Times(2) - .WillRepeatedly( - SendFast( - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html\r\n", - "<html>" - "<head><meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\"" - "/></head>" - "<body>Hi there. Got new content?" - "</body></html>")); + .WillOnce(SendFast(kHeaders, kBody)) + .WillOnce(testing::DoAll( + SendFast(kHeaders, kBody), + DelayCloseBrowserMock(&loop_, 4000, &ie_mock_))); EXPECT_CALL(ie_mock_, OnFileDownload(_, _)).Times(testing::AnyNumber()); @@ -945,9 +947,7 @@ TEST_P(FullTabNavigationTest, RefreshContents) { EXPECT_CALL(ie_mock_, OnNavigateComplete2(_, testing::Field(&VARIANT::bstrVal, StrEq(src_url)))) - .WillOnce(testing::DoAll( - DelayRefresh(&ie_mock_, &loop_, 2000), - DelayCloseBrowserMock(&loop_, 4000, &ie_mock_))); + .WillOnce(DelayRefresh(&ie_mock_, &loop_, 2000)); EXPECT_CALL(ie_mock_, OnLoad(in_cf, StrEq(src_url))) .Times(2); @@ -1104,4 +1104,62 @@ TEST_F(FullTabDownloadTest, TopLevelPostReissueFromChromeFramePage) { LaunchIENavigateAndLoop(src_url, kChromeFrameLongNavigationTimeoutInSeconds); } +MATCHER_P(UserAgentHeaderMatcher, ua_string, "") { + std::string headers = arg.headers(); + StringToUpperASCII(&headers); + + std::string ua_string_to_search = ua_string; + StringToUpperASCII(&ua_string_to_search); + + net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), + "\r\n"); + while (it.GetNext()) { + if (lstrcmpiA(it.name().c_str(), "User-Agent") == 0) { + if (it.values().find(ua_string_to_search) != std::string::npos) + return true; + } + } + return false; +} + +// Tests refreshing causes a page load and that the chrome frame user agent +// string is appended to the UA in the incoming top level HTTP requests. +TEST_P(FullTabNavigationTest, RefreshContentsUATest) { + const char kBody[] = "<html><head></head>" + "<body>Hi there. Got new content?" + "</body></html>"; + + std::string headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n"; + bool in_cf = GetParam().invokes_cf(); + if (in_cf) { + headers.append("X-UA-Compatible: chrome=1\r\n"); + } + + std::wstring src_url = server_mock_.Resolve(L"/refresh_src.html"); + + EXPECT_CALL(server_mock_, Get(_, StrEq(L"/refresh_src.html"), + UserAgentHeaderMatcher("chromeframe"))) + .Times(2) + .WillOnce(SendFast(headers, kBody)) + .WillOnce(testing::DoAll( + SendFast(headers, kBody), + DelayCloseBrowserMock(&loop_, 4000, &ie_mock_))); + + EXPECT_CALL(ie_mock_, OnFileDownload(_, _)).Times(testing::AnyNumber()); + + EXPECT_CALL(ie_mock_, + OnBeforeNavigate2(_, testing::Field(&VARIANT::bstrVal, + StrEq(src_url)), + _, _, _, _, _)); + EXPECT_CALL(ie_mock_, + OnNavigateComplete2(_, testing::Field(&VARIANT::bstrVal, + StrEq(src_url)))) + .WillOnce(DelayRefresh(&ie_mock_, &loop_, 2000)); + + EXPECT_CALL(ie_mock_, OnLoad(in_cf, StrEq(src_url))) + .Times(testing::AtMost(2)); + + LaunchIEAndNavigate(src_url); +} + } // namespace chrome_frame_test diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc index c74a840..565990b1 100644 --- a/chrome_frame/utils.cc +++ b/chrome_frame/utils.cc @@ -1565,3 +1565,10 @@ void EnumerateKeyValues(HKEY parent_key, const wchar_t* sub_key_name, ++url_list; } } + +std::wstring GetCurrentModuleVersion() { + scoped_ptr<FileVersionInfo> module_version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + DCHECK(module_version_info.get() != NULL); + return module_version_info->file_version(); +} diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index 2e167d2..f29d536 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -611,4 +611,7 @@ void EnumerateKeyValues(HKEY parent_key, const wchar_t* sub_key_name, bool CheckXUaCompatibleDirective(const std::string& directive, int ie_major_version); +// Returns the version of the current module as a string. +std::wstring GetCurrentModuleVersion(); + #endif // CHROME_FRAME_UTILS_H_ |