| Index: chrome_frame/chrome_frame_activex.cc
|
| diff --git a/chrome_frame/chrome_frame_activex.cc b/chrome_frame/chrome_frame_activex.cc
|
| deleted file mode 100644
|
| index 037ab7119eb84d9319b1d01393d79be6cc584ac0..0000000000000000000000000000000000000000
|
| --- a/chrome_frame/chrome_frame_activex.cc
|
| +++ /dev/null
|
| @@ -1,542 +0,0 @@
|
| -// Copyright (c) 2012 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 "chrome_frame/chrome_frame_activex.h"
|
| -
|
| -#include <wininet.h>
|
| -
|
| -#include <algorithm>
|
| -#include <map>
|
| -
|
| -#include "base/basictypes.h"
|
| -#include "base/command_line.h"
|
| -#include "base/debug/trace_event.h"
|
| -#include "base/logging.h"
|
| -#include "base/memory/singleton.h"
|
| -#include "base/path_service.h"
|
| -#include "base/strings/string_split.h"
|
| -#include "base/strings/string_util.h"
|
| -#include "base/strings/stringprintf.h"
|
| -#include "base/strings/utf_string_conversions.h"
|
| -#include "base/win/scoped_bstr.h"
|
| -#include "base/win/scoped_variant.h"
|
| -#include "chrome/common/automation_messages.h"
|
| -#include "chrome/common/chrome_constants.h"
|
| -#include "chrome/common/chrome_switches.h"
|
| -#include "chrome/test/automation/tab_proxy.h"
|
| -#include "chrome_frame/utils.h"
|
| -#include "url/gurl.h"
|
| -
|
| -namespace {
|
| -
|
| -// Class used to maintain a mapping from top-level windows to ChromeFrameActivex
|
| -// instances.
|
| -class TopLevelWindowMapping {
|
| - public:
|
| - typedef std::vector<HWND> WindowList;
|
| -
|
| - static TopLevelWindowMapping* GetInstance() {
|
| - return Singleton<TopLevelWindowMapping>::get();
|
| - }
|
| -
|
| - // Add |cf_window| to the set of windows registered under |top_window|.
|
| - void AddMapping(HWND top_window, HWND cf_window) {
|
| - top_window_map_lock_.Lock();
|
| - top_window_map_[top_window].push_back(cf_window);
|
| - top_window_map_lock_.Unlock();
|
| - }
|
| -
|
| - // Return the set of Chrome-Frame instances under |window|.
|
| - WindowList GetInstances(HWND window) {
|
| - top_window_map_lock_.Lock();
|
| - WindowList list = top_window_map_[window];
|
| - top_window_map_lock_.Unlock();
|
| - return list;
|
| - }
|
| -
|
| - private:
|
| - // Constructor is private as this class it to be used as a singleton.
|
| - // See static method instance().
|
| - TopLevelWindowMapping() {}
|
| -
|
| - friend struct DefaultSingletonTraits<TopLevelWindowMapping>;
|
| -
|
| - typedef std::map<HWND, WindowList> TopWindowMap;
|
| - TopWindowMap top_window_map_;
|
| -
|
| - CComAutoCriticalSection top_window_map_lock_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TopLevelWindowMapping);
|
| -};
|
| -
|
| -} // unnamed namespace
|
| -
|
| -ChromeFrameActivex::ChromeFrameActivex()
|
| - : chrome_wndproc_hook_(NULL),
|
| - attaching_to_existing_cf_tab_(false) {
|
| - TRACE_EVENT_BEGIN_ETW("chromeframe.createactivex", this, "");
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::FinalConstruct() {
|
| - HRESULT hr = Base::FinalConstruct();
|
| - if (FAILED(hr))
|
| - return hr;
|
| -
|
| - // No need to call FireOnChanged at this point since nobody will be listening.
|
| - ready_state_ = READYSTATE_LOADING;
|
| - return S_OK;
|
| -}
|
| -
|
| -ChromeFrameActivex::~ChromeFrameActivex() {
|
| - // We expect these to be released during a call to SetClientSite(NULL).
|
| - DCHECK_EQ(0u, onmessage_.size());
|
| - DCHECK_EQ(0u, onloaderror_.size());
|
| - DCHECK_EQ(0u, onload_.size());
|
| - DCHECK_EQ(0u, onreadystatechanged_.size());
|
| - DCHECK_EQ(0u, onextensionready_.size());
|
| -
|
| - if (chrome_wndproc_hook_) {
|
| - BOOL unhook_success = ::UnhookWindowsHookEx(chrome_wndproc_hook_);
|
| - DCHECK(unhook_success);
|
| - }
|
| -
|
| - // ChromeFramePlugin::Uninitialize()
|
| - Base::Uninitialize();
|
| -
|
| - TRACE_EVENT_END_ETW("chromeframe.createactivex", this, "");
|
| -}
|
| -
|
| -LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam,
|
| - BOOL& handled) {
|
| - Base::OnCreate(message, wparam, lparam, handled);
|
| - // Install the notification hook on the top-level window, so that we can
|
| - // be notified on move events. Note that the return value is not checked.
|
| - // This hook is installed here, as opposed to during IOleObject_SetClientSite
|
| - // because m_hWnd has not yet been assigned during the SetSite call.
|
| - InstallTopLevelHook(m_spClientSite);
|
| - return 0;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) {
|
| - base::win::ScopedComPtr<IOleContainer> container;
|
| - HRESULT hr = m_spClientSite->GetContainer(container.Receive());
|
| - if (container)
|
| - hr = container.QueryInterface(doc);
|
| - return hr;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) {
|
| - base::win::ScopedComPtr<IHTMLDocument2> document;
|
| - HRESULT hr = GetContainingDocument(document.Receive());
|
| - if (document)
|
| - hr = document->get_parentWindow(window);
|
| - return hr;
|
| -}
|
| -
|
| -void ChromeFrameActivex::OnLoadFailed(int error_code, const std::string& url) {
|
| - base::win::ScopedComPtr<IDispatch> event;
|
| - if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive())))
|
| - Fire_onloaderror(event);
|
| -
|
| - FireEvent(onloaderror_, url);
|
| - Base::OnLoadFailed(error_code, url);
|
| -}
|
| -
|
| -bool ChromeFrameActivex::ShouldShowVersionMismatchDialog(
|
| - bool is_privileged,
|
| - IOleClientSite* client_site) {
|
| - if (!is_privileged) {
|
| - return true;
|
| - }
|
| -
|
| - if (client_site) {
|
| - base::win::ScopedComPtr<IChromeFramePrivileged> service;
|
| - HRESULT hr = DoQueryService(SID_ChromeFramePrivileged,
|
| - client_site,
|
| - service.Receive());
|
| - if (SUCCEEDED(hr) && service) {
|
| - return (S_FALSE != service->ShouldShowVersionMismatchDialog());
|
| - }
|
| - }
|
| -
|
| - NOTREACHED();
|
| - return true;
|
| -}
|
| -
|
| -void ChromeFrameActivex::OnAutomationServerLaunchFailed(
|
| - AutomationLaunchResult reason, const std::string& server_version) {
|
| - Base::OnAutomationServerLaunchFailed(reason, server_version);
|
| -
|
| - if (reason == AUTOMATION_VERSION_MISMATCH &&
|
| - ShouldShowVersionMismatchDialog(is_privileged(), m_spClientSite)) {
|
| - UMA_HISTOGRAM_COUNTS("ChromeFrame.VersionMismatchDisplayed", 1);
|
| - DisplayVersionMismatchWarning(m_hWnd, server_version);
|
| - }
|
| -}
|
| -
|
| -void ChromeFrameActivex::OnChannelError() {
|
| - Fire_onchannelerror();
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::OnDraw(ATL_DRAWINFO& draw_info) { // NOLINT
|
| - HRESULT hr = S_OK;
|
| - int dc_type = ::GetObjectType(draw_info.hicTargetDev);
|
| - if (dc_type == OBJ_ENHMETADC) {
|
| - RECT print_bounds = {0};
|
| - print_bounds.left = draw_info.prcBounds->left;
|
| - print_bounds.right = draw_info.prcBounds->right;
|
| - print_bounds.top = draw_info.prcBounds->top;
|
| - print_bounds.bottom = draw_info.prcBounds->bottom;
|
| -
|
| - automation_client_->Print(draw_info.hdcDraw, print_bounds);
|
| - } else {
|
| - hr = Base::OnDraw(draw_info);
|
| - }
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -STDMETHODIMP ChromeFrameActivex::Load(IPropertyBag* bag, IErrorLog* error_log) {
|
| - DCHECK(bag);
|
| -
|
| - const wchar_t* event_props[] = {
|
| - (L"onload"),
|
| - (L"onloaderror"),
|
| - (L"onmessage"),
|
| - (L"onreadystatechanged"),
|
| - };
|
| -
|
| - base::win::ScopedComPtr<IHTMLObjectElement> obj_element;
|
| - GetObjectElement(obj_element.Receive());
|
| -
|
| - base::win::ScopedBstr object_id;
|
| - GetObjectScriptId(obj_element, object_id.Receive());
|
| -
|
| - base::win::ScopedComPtr<IHTMLElement2> element;
|
| - element.QueryFrom(obj_element);
|
| - HRESULT hr = S_OK;
|
| -
|
| - for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) {
|
| - base::win::ScopedBstr prop(event_props[i]);
|
| - base::win::ScopedVariant value;
|
| - if (SUCCEEDED(bag->Read(prop, value.Receive(), error_log))) {
|
| - if (value.type() != VT_BSTR ||
|
| - FAILED(hr = CreateScriptBlockForEvent(element, object_id,
|
| - V_BSTR(&value), prop))) {
|
| - DLOG(ERROR) << "Failed to create script block for " << prop
|
| - << base::StringPrintf(L"hr=0x%08X, vt=%i", hr,
|
| - value.type());
|
| - } else {
|
| - DVLOG(1) << "script block created for event " << prop
|
| - << base::StringPrintf(" (0x%08X)", hr) << " connections: " <<
|
| - ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize();
|
| - }
|
| - } else {
|
| - DVLOG(1) << "event property " << prop << " not in property bag";
|
| - }
|
| - }
|
| -
|
| - base::win::ScopedVariant src;
|
| - if (SUCCEEDED(bag->Read(base::win::ScopedBstr(L"src"), src.Receive(),
|
| - error_log))) {
|
| - if (src.type() == VT_BSTR) {
|
| - hr = put_src(V_BSTR(&src));
|
| - DCHECK(hr != E_UNEXPECTED);
|
| - }
|
| - }
|
| -
|
| - base::win::ScopedVariant use_chrome_network;
|
| - if (SUCCEEDED(bag->Read(base::win::ScopedBstr(L"useChromeNetwork"),
|
| - use_chrome_network.Receive(), error_log))) {
|
| - VariantChangeType(use_chrome_network.AsInput(),
|
| - use_chrome_network.AsInput(),
|
| - 0, VT_BOOL);
|
| - if (use_chrome_network.type() == VT_BOOL) {
|
| - hr = put_useChromeNetwork(V_BOOL(&use_chrome_network));
|
| - DCHECK(hr != E_UNEXPECTED);
|
| - }
|
| - }
|
| -
|
| - DLOG_IF(ERROR, FAILED(hr))
|
| - << base::StringPrintf("Failed to load property bag: 0x%08X", hr);
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -const wchar_t g_activex_insecure_content_error[] = {
|
| - L"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>"
|
| - L"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"};
|
| -
|
| -STDMETHODIMP ChromeFrameActivex::put_src(BSTR src) {
|
| - GURL document_url(GetDocumentUrl());
|
| - if (document_url.SchemeIsSecure()) {
|
| - GURL source_url(src);
|
| - if (!source_url.SchemeIsSecure()) {
|
| - Base::put_src(base::win::ScopedBstr(g_activex_insecure_content_error));
|
| - return E_ACCESSDENIED;
|
| - }
|
| - }
|
| - HRESULT hr = S_OK;
|
| - // If we are connecting to an existing ExternalTabContainer instance in
|
| - // Chrome then we should wait for Chrome to initiate the navigation.
|
| - if (!attaching_to_existing_cf_tab_) {
|
| - hr = Base::put_src(src);
|
| - } else {
|
| - url_.Reset(::SysAllocString(src));
|
| - attaching_to_existing_cf_tab_ = false;
|
| - }
|
| - return S_OK;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::IOleObject_SetClientSite(
|
| - IOleClientSite* client_site) {
|
| - HRESULT hr = Base::IOleObject_SetClientSite(client_site);
|
| - if (FAILED(hr) || !client_site) {
|
| - EventHandlers* handlers[] = {
|
| - &onmessage_,
|
| - &onloaderror_,
|
| - &onload_,
|
| - &onreadystatechanged_,
|
| - &onextensionready_,
|
| - };
|
| -
|
| - for (int i = 0; i < arraysize(handlers); ++i)
|
| - handlers[i]->clear();
|
| -
|
| - // Drop privileged mode on uninitialization.
|
| - set_is_privileged(false);
|
| - } else {
|
| - base::win::ScopedComPtr<IHTMLDocument2> document;
|
| - GetContainingDocument(document.Receive());
|
| - if (document) {
|
| - base::win::ScopedBstr url;
|
| - if (SUCCEEDED(document->get_URL(url.Receive())))
|
| - base::WideToUTF8(url, url.Length(), &document_url_);
|
| - }
|
| -
|
| - // Probe to see whether the host implements the privileged service.
|
| - base::win::ScopedComPtr<IChromeFramePrivileged> service;
|
| - HRESULT service_hr = DoQueryService(SID_ChromeFramePrivileged,
|
| - m_spClientSite,
|
| - service.Receive());
|
| - if (SUCCEEDED(service_hr) && service) {
|
| - // Does the host want privileged mode?
|
| - boolean wants_privileged = false;
|
| - service_hr = service->GetWantsPrivileged(&wants_privileged);
|
| -
|
| - if (SUCCEEDED(service_hr) && wants_privileged)
|
| - set_is_privileged(true);
|
| -
|
| - url_fetcher_->set_privileged_mode(is_privileged());
|
| - }
|
| -
|
| - std::wstring profile_name(GetHostProcessName(false));
|
| - if (is_privileged()) {
|
| - base::win::ScopedBstr profile_name_arg;
|
| - service_hr = service->GetChromeProfileName(profile_name_arg.Receive());
|
| - if (S_OK == service_hr && profile_name_arg)
|
| - profile_name.assign(profile_name_arg, profile_name_arg.Length());
|
| - }
|
| -
|
| - std::string utf8_url;
|
| - if (url_.Length()) {
|
| - base::WideToUTF8(url_, url_.Length(), &utf8_url);
|
| - }
|
| -
|
| - InitializeAutomationSettings();
|
| -
|
| -
|
| - url_fetcher_->set_frame_busting(!is_privileged());
|
| - automation_client_->SetUrlFetcher(url_fetcher_.get());
|
| - if (!InitializeAutomation(profile_name, IsIEInPrivate(), true,
|
| - GURL(utf8_url), GURL(), false)) {
|
| - DLOG(ERROR) << "Failed to navigate to url:" << utf8_url;
|
| - return E_FAIL;
|
| - }
|
| -
|
| - // Log a metric that Chrome Frame is being used in Widget mode
|
| - UMA_LAUNCH_TYPE_COUNT(RENDERER_TYPE_CHROME_WIDGET);
|
| - }
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem,
|
| - BSTR* id) {
|
| - DCHECK(object_elem != NULL);
|
| - DCHECK(id != NULL);
|
| -
|
| - HRESULT hr = E_FAIL;
|
| - if (object_elem) {
|
| - base::win::ScopedComPtr<IHTMLElement> elem;
|
| - hr = elem.QueryFrom(object_elem);
|
| - if (elem) {
|
| - hr = elem->get_id(id);
|
| - }
|
| - }
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::GetObjectElement(IHTMLObjectElement** element) {
|
| - DCHECK(m_spClientSite);
|
| - if (!m_spClientSite)
|
| - return E_UNEXPECTED;
|
| -
|
| - base::win::ScopedComPtr<IOleControlSite> site;
|
| - HRESULT hr = site.QueryFrom(m_spClientSite);
|
| - if (site) {
|
| - base::win::ScopedComPtr<IDispatch> disp;
|
| - hr = site->GetExtendedControl(disp.Receive());
|
| - if (disp) {
|
| - hr = disp.QueryInterface(element);
|
| - } else {
|
| - DCHECK(FAILED(hr));
|
| - }
|
| - }
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::CreateScriptBlockForEvent(
|
| - IHTMLElement2* insert_after, BSTR instance_id, BSTR script,
|
| - BSTR event_name) {
|
| - DCHECK(insert_after);
|
| - DCHECK_GT(::SysStringLen(event_name), 0UL); // should always have this
|
| -
|
| - // This might be 0 if not specified in the HTML document.
|
| - if (!::SysStringLen(instance_id)) {
|
| - // TODO(tommi): Should we give ourselves an ID if this happens?
|
| - NOTREACHED() << "Need to handle this";
|
| - return E_INVALIDARG;
|
| - }
|
| -
|
| - base::win::ScopedComPtr<IHTMLDocument2> document;
|
| - HRESULT hr = GetContainingDocument(document.Receive());
|
| - if (SUCCEEDED(hr)) {
|
| - base::win::ScopedComPtr<IHTMLElement> element, new_element;
|
| - document->createElement(base::win::ScopedBstr(L"script"),
|
| - element.Receive());
|
| - if (element) {
|
| - base::win::ScopedComPtr<IHTMLScriptElement> script_element;
|
| - if (SUCCEEDED(hr = script_element.QueryFrom(element))) {
|
| - script_element->put_htmlFor(instance_id);
|
| - script_element->put_event(event_name);
|
| - script_element->put_text(script);
|
| -
|
| - hr = insert_after->insertAdjacentElement(
|
| - base::win::ScopedBstr(L"afterEnd"),
|
| - element,
|
| - new_element.Receive());
|
| - }
|
| - }
|
| - }
|
| -
|
| - return hr;
|
| -}
|
| -
|
| -void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
|
| - const std::string& arg) {
|
| - if (handlers.size()) {
|
| - base::win::ScopedComPtr<IDispatch> event;
|
| - if (SUCCEEDED(CreateDomEvent("event", arg, "", event.Receive()))) {
|
| - FireEvent(handlers, event);
|
| - }
|
| - }
|
| -}
|
| -
|
| -void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
|
| - IDispatch* event) {
|
| - DCHECK(event != NULL);
|
| - VARIANT arg = { VT_DISPATCH };
|
| - arg.pdispVal = event;
|
| - DISPPARAMS params = { &arg, NULL, 1, 0 };
|
| - for (EventHandlers::const_iterator it = handlers.begin();
|
| - it != handlers.end();
|
| - ++it) {
|
| - HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
|
| - DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
|
| - // 0x80020101 == SCRIPT_E_REPORTED.
|
| - // When the script we're invoking has an error, we get this error back.
|
| - DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101)
|
| - << base::StringPrintf(L"Failed to invoke script: 0x%08X", hr);
|
| - }
|
| -}
|
| -
|
| -void ChromeFrameActivex::FireEvent(const EventHandlers& handlers,
|
| - IDispatch* event, BSTR target) {
|
| - DCHECK(event != NULL);
|
| - // Arguments in reverse order to event handler function declaration,
|
| - // because that's what DISPPARAMS requires.
|
| - VARIANT args[2] = { { VT_BSTR }, { VT_DISPATCH }, };
|
| - args[0].bstrVal = target;
|
| - args[1].pdispVal = event;
|
| - DISPPARAMS params = { args, NULL, arraysize(args), 0 };
|
| - for (EventHandlers::const_iterator it = handlers.begin();
|
| - it != handlers.end();
|
| - ++it) {
|
| - HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
|
| - DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
|
| - // 0x80020101 == SCRIPT_E_REPORTED.
|
| - // When the script we're invoking has an error, we get this error back.
|
| - DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101)
|
| - << base::StringPrintf(L"Failed to invoke script: 0x%08X", hr);
|
| - }
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::InstallTopLevelHook(IOleClientSite* client_site) {
|
| - return E_FAIL;
|
| -}
|
| -
|
| -HRESULT ChromeFrameActivex::registerBhoIfNeeded() {
|
| - if (!m_spUnkSite) {
|
| - NOTREACHED() << "Invalid client site";
|
| - return E_FAIL;
|
| - }
|
| -
|
| - if (NavigationManager::GetThreadInstance() != NULL) {
|
| - DVLOG(1) << "BHO already loaded";
|
| - return S_OK;
|
| - }
|
| -
|
| - base::win::ScopedComPtr<IWebBrowser2> web_browser2;
|
| - HRESULT hr = DoQueryService(SID_SWebBrowserApp, m_spUnkSite,
|
| - web_browser2.Receive());
|
| - if (FAILED(hr) || web_browser2.get() == NULL) {
|
| - DLOG(WARNING) << "Failed to get IWebBrowser2 from client site. Error:"
|
| - << base::StringPrintf(" 0x%08X", hr);
|
| - return hr;
|
| - }
|
| -
|
| - wchar_t bho_class_id_as_string[MAX_PATH] = {0};
|
| - StringFromGUID2(CLSID_ChromeFrameBHO, bho_class_id_as_string,
|
| - arraysize(bho_class_id_as_string));
|
| -
|
| - base::win::ScopedComPtr<IObjectWithSite> bho;
|
| - hr = bho.CreateInstance(CLSID_ChromeFrameBHO, NULL, CLSCTX_INPROC_SERVER);
|
| - if (FAILED(hr)) {
|
| - NOTREACHED() << "Failed to register ChromeFrame BHO. Error:"
|
| - << base::StringPrintf(" 0x%08X", hr);
|
| - return hr;
|
| - }
|
| -
|
| - hr = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH, NULL, 0, 0);
|
| - if (FAILED(hr)) {
|
| - DLOG(ERROR) << "Failed to refresh user agent string from registry. "
|
| - << "UrlMkSetSessionOption returned "
|
| - << base::StringPrintf("0x%08x", hr);
|
| - return hr;
|
| - }
|
| -
|
| - hr = bho->SetSite(web_browser2);
|
| - if (FAILED(hr)) {
|
| - NOTREACHED() << "ChromeFrame BHO SetSite failed. Error:"
|
| - << base::StringPrintf(" 0x%08X", hr);
|
| - return hr;
|
| - }
|
| -
|
| - web_browser2->PutProperty(base::win::ScopedBstr(bho_class_id_as_string),
|
| - base::win::ScopedVariant(bho));
|
| - return S_OK;
|
| -}
|
|
|