1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome_frame/chrome_frame_activex.h"
12 #include "base/basictypes.h"
13 #include "base/command_line.h"
14 #include "base/debug/trace_event.h"
15 #include "base/file_util.h"
16 #include "base/logging.h"
17 #include "base/memory/singleton.h"
18 #include "base/path_service.h"
19 #include "base/process_util.h"
20 #include "base/string_util.h"
21 #include "base/stringprintf.h"
22 #include "base/strings/string_split.h"
23 #include "base/utf_string_conversions.h"
24 #include "base/win/scoped_bstr.h"
25 #include "base/win/scoped_variant.h"
26 #include "chrome/common/automation_messages.h"
27 #include "chrome/common/chrome_constants.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/test/automation/tab_proxy.h"
30 #include "chrome_frame/utils.h"
31 #include "googleurl/src/gurl.h"
35 // Class used to maintain a mapping from top-level windows to ChromeFrameActivex
37 class TopLevelWindowMapping
{
39 typedef std::vector
<HWND
> WindowList
;
41 static TopLevelWindowMapping
* GetInstance() {
42 return Singleton
<TopLevelWindowMapping
>::get();
45 // Add |cf_window| to the set of windows registered under |top_window|.
46 void AddMapping(HWND top_window
, HWND cf_window
) {
47 top_window_map_lock_
.Lock();
48 top_window_map_
[top_window
].push_back(cf_window
);
49 top_window_map_lock_
.Unlock();
52 // Return the set of Chrome-Frame instances under |window|.
53 WindowList
GetInstances(HWND window
) {
54 top_window_map_lock_
.Lock();
55 WindowList list
= top_window_map_
[window
];
56 top_window_map_lock_
.Unlock();
61 // Constructor is private as this class it to be used as a singleton.
62 // See static method instance().
63 TopLevelWindowMapping() {}
65 friend struct DefaultSingletonTraits
<TopLevelWindowMapping
>;
67 typedef std::map
<HWND
, WindowList
> TopWindowMap
;
68 TopWindowMap top_window_map_
;
70 CComAutoCriticalSection top_window_map_lock_
;
72 DISALLOW_COPY_AND_ASSIGN(TopLevelWindowMapping
);
75 // Message pump hook function that monitors for WM_MOVE and WM_MOVING
76 // messages on a top-level window, and passes notification to the appropriate
77 // Chrome-Frame instances.
78 LRESULT CALLBACK
TopWindowProc(int code
, WPARAM wparam
, LPARAM lparam
) {
79 CWPSTRUCT
*info
= reinterpret_cast<CWPSTRUCT
*>(lparam
);
80 const UINT
&message
= info
->message
;
81 const HWND
&message_hwnd
= info
->hwnd
;
86 TopLevelWindowMapping::WindowList cf_instances
=
87 TopLevelWindowMapping::GetInstance()->GetInstances(message_hwnd
);
88 TopLevelWindowMapping::WindowList::iterator
89 iter(cf_instances
.begin()), end(cf_instances
.end());
90 for (;iter
!= end
; ++iter
) {
91 PostMessage(*iter
, WM_HOST_MOVED_NOTIFICATION
, NULL
, NULL
);
99 return CallNextHookEx(0, code
, wparam
, lparam
);
102 HHOOK
InstallLocalWindowHook(HWND window
) {
106 DWORD proc_thread
= ::GetWindowThreadProcessId(window
, NULL
);
110 // Note that this hook is installed as a LOCAL hook.
111 return ::SetWindowsHookEx(WH_CALLWNDPROC
,
117 } // unnamed namespace
119 namespace chrome_frame
{
120 std::string
ActiveXCreateUrl(const GURL
& parsed_url
,
121 const AttachExternalTabParams
& params
) {
122 return base::StringPrintf(
123 "%hs?attach_external_tab&%I64u&%d&%d&%d&%d&%d&%hs",
124 parsed_url
.GetOrigin().spec().c_str(),
127 params
.dimensions
.x(),
128 params
.dimensions
.y(),
129 params
.dimensions
.width(),
130 params
.dimensions
.height(),
131 params
.profile_name
.c_str());
134 int GetDisposition(const AttachExternalTabParams
& params
) {
135 return params
.disposition
;
138 void GetMiniContextMenuData(UINT cmd
,
139 const MiniContextMenuParams
& params
,
142 *referrer
= params
.frame_url
.is_empty() ? params
.page_url
: params
.frame_url
;
143 *url
= (cmd
== IDS_CONTENT_CONTEXT_SAVELINKAS
?
144 params
.link_url
: params
.src_url
);
147 } // namespace chrome_frame
149 ChromeFrameActivex::ChromeFrameActivex()
150 : chrome_wndproc_hook_(NULL
),
151 attaching_to_existing_cf_tab_(false) {
152 TRACE_EVENT_BEGIN_ETW("chromeframe.createactivex", this, "");
155 HRESULT
ChromeFrameActivex::FinalConstruct() {
156 HRESULT hr
= Base::FinalConstruct();
160 // No need to call FireOnChanged at this point since nobody will be listening.
161 ready_state_
= READYSTATE_LOADING
;
165 ChromeFrameActivex::~ChromeFrameActivex() {
166 // We expect these to be released during a call to SetClientSite(NULL).
167 DCHECK_EQ(0u, onmessage_
.size());
168 DCHECK_EQ(0u, onloaderror_
.size());
169 DCHECK_EQ(0u, onload_
.size());
170 DCHECK_EQ(0u, onreadystatechanged_
.size());
171 DCHECK_EQ(0u, onextensionready_
.size());
173 if (chrome_wndproc_hook_
) {
174 BOOL unhook_success
= ::UnhookWindowsHookEx(chrome_wndproc_hook_
);
175 DCHECK(unhook_success
);
178 // ChromeFramePlugin::Uninitialize()
179 Base::Uninitialize();
181 TRACE_EVENT_END_ETW("chromeframe.createactivex", this, "");
184 LRESULT
ChromeFrameActivex::OnCreate(UINT message
, WPARAM wparam
, LPARAM lparam
,
186 Base::OnCreate(message
, wparam
, lparam
, handled
);
187 // Install the notification hook on the top-level window, so that we can
188 // be notified on move events. Note that the return value is not checked.
189 // This hook is installed here, as opposed to during IOleObject_SetClientSite
190 // because m_hWnd has not yet been assigned during the SetSite call.
191 InstallTopLevelHook(m_spClientSite
);
195 LRESULT
ChromeFrameActivex::OnHostMoved(UINT message
, WPARAM wparam
,
196 LPARAM lparam
, BOOL
& handled
) {
201 HRESULT
ChromeFrameActivex::GetContainingDocument(IHTMLDocument2
** doc
) {
202 base::win::ScopedComPtr
<IOleContainer
> container
;
203 HRESULT hr
= m_spClientSite
->GetContainer(container
.Receive());
205 hr
= container
.QueryInterface(doc
);
209 HRESULT
ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2
** window
) {
210 base::win::ScopedComPtr
<IHTMLDocument2
> document
;
211 HRESULT hr
= GetContainingDocument(document
.Receive());
213 hr
= document
->get_parentWindow(window
);
217 void ChromeFrameActivex::OnLoad(const GURL
& gurl
) {
218 base::win::ScopedComPtr
<IDispatch
> event
;
219 std::string url
= gurl
.spec();
220 if (SUCCEEDED(CreateDomEvent("event", url
, "", event
.Receive())))
223 FireEvent(onload_
, url
);
227 void ChromeFrameActivex::OnLoadFailed(int error_code
, const std::string
& url
) {
228 base::win::ScopedComPtr
<IDispatch
> event
;
229 if (SUCCEEDED(CreateDomEvent("event", url
, "", event
.Receive())))
230 Fire_onloaderror(event
);
232 FireEvent(onloaderror_
, url
);
233 Base::OnLoadFailed(error_code
, url
);
236 void ChromeFrameActivex::OnMessageFromChromeFrame(const std::string
& message
,
237 const std::string
& origin
,
238 const std::string
& target
) {
239 DVLOG(1) << __FUNCTION__
;
241 if (target
.compare("*") != 0) {
244 if (is_privileged()) {
245 // Forward messages if the control is in privileged mode.
246 base::win::ScopedComPtr
<IDispatch
> message_event
;
247 if (SUCCEEDED(CreateDomEvent("message", message
, origin
,
248 message_event
.Receive()))) {
249 base::win::ScopedBstr
target_bstr(UTF8ToWide(target
).c_str());
250 Fire_onprivatemessage(message_event
, target_bstr
);
252 FireEvent(onprivatemessage_
, message_event
, target_bstr
);
255 if (HaveSameOrigin(target
, document_url_
)) {
258 DLOG(WARNING
) << "Dropping posted message since target doesn't match "
259 "the current document's origin. target=" << target
;
267 base::win::ScopedComPtr
<IDispatch
> message_event
;
268 if (SUCCEEDED(CreateDomEvent("message", message
, origin
,
269 message_event
.Receive()))) {
270 Fire_onmessage(message_event
);
272 FireEvent(onmessage_
, message_event
);
274 base::win::ScopedVariant event_var
;
275 event_var
.Set(static_cast<IDispatch
*>(message_event
));
276 InvokeScriptFunction(onmessage_handler_
, event_var
.AsInput());
280 bool ChromeFrameActivex::ShouldShowVersionMismatchDialog(
282 IOleClientSite
* client_site
) {
283 if (!is_privileged
) {
288 base::win::ScopedComPtr
<IChromeFramePrivileged
> service
;
289 HRESULT hr
= DoQueryService(SID_ChromeFramePrivileged
,
292 if (SUCCEEDED(hr
) && service
) {
293 return (S_FALSE
!= service
->ShouldShowVersionMismatchDialog());
301 void ChromeFrameActivex::OnAutomationServerLaunchFailed(
302 AutomationLaunchResult reason
, const std::string
& server_version
) {
303 Base::OnAutomationServerLaunchFailed(reason
, server_version
);
305 if (reason
== AUTOMATION_VERSION_MISMATCH
&&
306 ShouldShowVersionMismatchDialog(is_privileged(), m_spClientSite
)) {
307 UMA_HISTOGRAM_COUNTS("ChromeFrame.VersionMismatchDisplayed", 1);
308 DisplayVersionMismatchWarning(m_hWnd
, server_version
);
312 void ChromeFrameActivex::OnChannelError() {
313 Fire_onchannelerror();
316 HRESULT
ChromeFrameActivex::OnDraw(ATL_DRAWINFO
& draw_info
) { // NOLINT
318 int dc_type
= ::GetObjectType(draw_info
.hicTargetDev
);
319 if (dc_type
== OBJ_ENHMETADC
) {
320 RECT print_bounds
= {0};
321 print_bounds
.left
= draw_info
.prcBounds
->left
;
322 print_bounds
.right
= draw_info
.prcBounds
->right
;
323 print_bounds
.top
= draw_info
.prcBounds
->top
;
324 print_bounds
.bottom
= draw_info
.prcBounds
->bottom
;
326 automation_client_
->Print(draw_info
.hdcDraw
, print_bounds
);
328 hr
= Base::OnDraw(draw_info
);
334 STDMETHODIMP
ChromeFrameActivex::Load(IPropertyBag
* bag
, IErrorLog
* error_log
) {
337 const wchar_t* event_props
[] = {
341 (L
"onreadystatechanged"),
344 base::win::ScopedComPtr
<IHTMLObjectElement
> obj_element
;
345 GetObjectElement(obj_element
.Receive());
347 base::win::ScopedBstr object_id
;
348 GetObjectScriptId(obj_element
, object_id
.Receive());
350 base::win::ScopedComPtr
<IHTMLElement2
> element
;
351 element
.QueryFrom(obj_element
);
354 for (int i
= 0; SUCCEEDED(hr
) && i
< arraysize(event_props
); ++i
) {
355 base::win::ScopedBstr
prop(event_props
[i
]);
356 base::win::ScopedVariant value
;
357 if (SUCCEEDED(bag
->Read(prop
, value
.Receive(), error_log
))) {
358 if (value
.type() != VT_BSTR
||
359 FAILED(hr
= CreateScriptBlockForEvent(element
, object_id
,
360 V_BSTR(&value
), prop
))) {
361 DLOG(ERROR
) << "Failed to create script block for " << prop
362 << base::StringPrintf(L
"hr=0x%08X, vt=%i", hr
,
365 DVLOG(1) << "script block created for event " << prop
366 << base::StringPrintf(" (0x%08X)", hr
) << " connections: " <<
367 ProxyDIChromeFrameEvents
<ChromeFrameActivex
>::m_vec
.GetSize();
370 DVLOG(1) << "event property " << prop
<< " not in property bag";
374 base::win::ScopedVariant src
;
375 if (SUCCEEDED(bag
->Read(base::win::ScopedBstr(L
"src"), src
.Receive(),
377 if (src
.type() == VT_BSTR
) {
378 hr
= put_src(V_BSTR(&src
));
379 DCHECK(hr
!= E_UNEXPECTED
);
383 base::win::ScopedVariant use_chrome_network
;
384 if (SUCCEEDED(bag
->Read(base::win::ScopedBstr(L
"useChromeNetwork"),
385 use_chrome_network
.Receive(), error_log
))) {
386 VariantChangeType(use_chrome_network
.AsInput(),
387 use_chrome_network
.AsInput(),
389 if (use_chrome_network
.type() == VT_BOOL
) {
390 hr
= put_useChromeNetwork(V_BOOL(&use_chrome_network
));
391 DCHECK(hr
!= E_UNEXPECTED
);
395 DLOG_IF(ERROR
, FAILED(hr
))
396 << base::StringPrintf("Failed to load property bag: 0x%08X", hr
);
401 const wchar_t g_activex_insecure_content_error
[] = {
402 L
"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>"
403 L
"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"};
405 STDMETHODIMP
ChromeFrameActivex::put_src(BSTR src
) {
406 GURL
document_url(GetDocumentUrl());
407 if (document_url
.SchemeIsSecure()) {
408 GURL
source_url(src
);
409 if (!source_url
.SchemeIsSecure()) {
410 Base::put_src(base::win::ScopedBstr(g_activex_insecure_content_error
));
411 return E_ACCESSDENIED
;
415 // If we are connecting to an existing ExternalTabContainer instance in
416 // Chrome then we should wait for Chrome to initiate the navigation.
417 if (!attaching_to_existing_cf_tab_
) {
418 hr
= Base::put_src(src
);
420 url_
.Reset(::SysAllocString(src
));
421 attaching_to_existing_cf_tab_
= false;
426 HRESULT
ChromeFrameActivex::IOleObject_SetClientSite(
427 IOleClientSite
* client_site
) {
428 HRESULT hr
= Base::IOleObject_SetClientSite(client_site
);
429 if (FAILED(hr
) || !client_site
) {
430 EventHandlers
* handlers
[] = {
434 &onreadystatechanged_
,
438 for (int i
= 0; i
< arraysize(handlers
); ++i
)
439 handlers
[i
]->clear();
441 // Drop privileged mode on uninitialization.
442 set_is_privileged(false);
444 base::win::ScopedComPtr
<IHTMLDocument2
> document
;
445 GetContainingDocument(document
.Receive());
447 base::win::ScopedBstr url
;
448 if (SUCCEEDED(document
->get_URL(url
.Receive())))
449 WideToUTF8(url
, url
.Length(), &document_url_
);
452 // Probe to see whether the host implements the privileged service.
453 base::win::ScopedComPtr
<IChromeFramePrivileged
> service
;
454 HRESULT service_hr
= DoQueryService(SID_ChromeFramePrivileged
,
457 if (SUCCEEDED(service_hr
) && service
) {
458 // Does the host want privileged mode?
459 boolean wants_privileged
= false;
460 service_hr
= service
->GetWantsPrivileged(&wants_privileged
);
462 if (SUCCEEDED(service_hr
) && wants_privileged
)
463 set_is_privileged(true);
465 url_fetcher_
->set_privileged_mode(is_privileged());
468 std::wstring
profile_name(GetHostProcessName(false));
469 if (is_privileged()) {
470 base::win::ScopedBstr profile_name_arg
;
471 service_hr
= service
->GetChromeProfileName(profile_name_arg
.Receive());
472 if (S_OK
== service_hr
&& profile_name_arg
)
473 profile_name
.assign(profile_name_arg
, profile_name_arg
.Length());
476 std::string utf8_url
;
478 WideToUTF8(url_
, url_
.Length(), &utf8_url
);
481 InitializeAutomationSettings();
484 base::win::ScopedBstr navigation_url
;
485 service
->GetNavigationUrl(navigation_url
.Receive());
486 if (navigation_url
.Length()) {
487 ChromeFrameUrl cf_url
;
488 cf_url
.Parse(navigation_url
.operator BSTR());
489 if (cf_url
.attach_to_external_tab()) {
490 automation_client_
->AttachExternalTab(cf_url
.cookie());
491 attaching_to_existing_cf_tab_
= true;
495 url_fetcher_
->set_frame_busting(!is_privileged());
496 automation_client_
->SetUrlFetcher(url_fetcher_
.get());
497 if (!InitializeAutomation(profile_name
, IsIEInPrivate(), true,
498 GURL(utf8_url
), GURL(), false)) {
499 DLOG(ERROR
) << "Failed to navigate to url:" << utf8_url
;
503 // Log a metric that Chrome Frame is being used in Widget mode
504 UMA_LAUNCH_TYPE_COUNT(RENDERER_TYPE_CHROME_WIDGET
);
510 HRESULT
ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement
* object_elem
,
512 DCHECK(object_elem
!= NULL
);
517 base::win::ScopedComPtr
<IHTMLElement
> elem
;
518 hr
= elem
.QueryFrom(object_elem
);
520 hr
= elem
->get_id(id
);
527 HRESULT
ChromeFrameActivex::GetObjectElement(IHTMLObjectElement
** element
) {
528 DCHECK(m_spClientSite
);
532 base::win::ScopedComPtr
<IOleControlSite
> site
;
533 HRESULT hr
= site
.QueryFrom(m_spClientSite
);
535 base::win::ScopedComPtr
<IDispatch
> disp
;
536 hr
= site
->GetExtendedControl(disp
.Receive());
538 hr
= disp
.QueryInterface(element
);
547 HRESULT
ChromeFrameActivex::CreateScriptBlockForEvent(
548 IHTMLElement2
* insert_after
, BSTR instance_id
, BSTR script
,
550 DCHECK(insert_after
);
551 DCHECK_GT(::SysStringLen(event_name
), 0UL); // should always have this
553 // This might be 0 if not specified in the HTML document.
554 if (!::SysStringLen(instance_id
)) {
555 // TODO(tommi): Should we give ourselves an ID if this happens?
556 NOTREACHED() << "Need to handle this";
560 base::win::ScopedComPtr
<IHTMLDocument2
> document
;
561 HRESULT hr
= GetContainingDocument(document
.Receive());
563 base::win::ScopedComPtr
<IHTMLElement
> element
, new_element
;
564 document
->createElement(base::win::ScopedBstr(L
"script"),
567 base::win::ScopedComPtr
<IHTMLScriptElement
> script_element
;
568 if (SUCCEEDED(hr
= script_element
.QueryFrom(element
))) {
569 script_element
->put_htmlFor(instance_id
);
570 script_element
->put_event(event_name
);
571 script_element
->put_text(script
);
573 hr
= insert_after
->insertAdjacentElement(
574 base::win::ScopedBstr(L
"afterEnd"),
576 new_element
.Receive());
584 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
585 const std::string
& arg
) {
586 if (handlers
.size()) {
587 base::win::ScopedComPtr
<IDispatch
> event
;
588 if (SUCCEEDED(CreateDomEvent("event", arg
, "", event
.Receive()))) {
589 FireEvent(handlers
, event
);
594 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
596 DCHECK(event
!= NULL
);
597 VARIANT arg
= { VT_DISPATCH
};
598 arg
.pdispVal
= event
;
599 DISPPARAMS params
= { &arg
, NULL
, 1, 0 };
600 for (EventHandlers::const_iterator it
= handlers
.begin();
601 it
!= handlers
.end();
603 HRESULT hr
= (*it
)->Invoke(DISPID_VALUE
, IID_NULL
, LOCALE_USER_DEFAULT
,
604 DISPATCH_METHOD
, ¶ms
, NULL
, NULL
, NULL
);
605 // 0x80020101 == SCRIPT_E_REPORTED.
606 // When the script we're invoking has an error, we get this error back.
607 DLOG_IF(ERROR
, FAILED(hr
) && hr
!= 0x80020101)
608 << base::StringPrintf(L
"Failed to invoke script: 0x%08X", hr
);
612 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
613 IDispatch
* event
, BSTR target
) {
614 DCHECK(event
!= NULL
);
615 // Arguments in reverse order to event handler function declaration,
616 // because that's what DISPPARAMS requires.
617 VARIANT args
[2] = { { VT_BSTR
}, { VT_DISPATCH
}, };
618 args
[0].bstrVal
= target
;
619 args
[1].pdispVal
= event
;
620 DISPPARAMS params
= { args
, NULL
, arraysize(args
), 0 };
621 for (EventHandlers::const_iterator it
= handlers
.begin();
622 it
!= handlers
.end();
624 HRESULT hr
= (*it
)->Invoke(DISPID_VALUE
, IID_NULL
, LOCALE_USER_DEFAULT
,
625 DISPATCH_METHOD
, ¶ms
, NULL
, NULL
, NULL
);
626 // 0x80020101 == SCRIPT_E_REPORTED.
627 // When the script we're invoking has an error, we get this error back.
628 DLOG_IF(ERROR
, FAILED(hr
) && hr
!= 0x80020101)
629 << base::StringPrintf(L
"Failed to invoke script: 0x%08X", hr
);
633 HRESULT
ChromeFrameActivex::InstallTopLevelHook(IOleClientSite
* client_site
) {
634 // Get the parent window of the site, and install our hook on the topmost
635 // window of the parent.
636 base::win::ScopedComPtr
<IOleWindow
> ole_window
;
637 HRESULT hr
= ole_window
.QueryFrom(client_site
);
642 hr
= ole_window
->GetWindow(&parent_wnd
);
646 HWND top_window
= ::GetAncestor(parent_wnd
, GA_ROOT
);
647 chrome_wndproc_hook_
= InstallLocalWindowHook(top_window
);
648 if (chrome_wndproc_hook_
)
649 TopLevelWindowMapping::GetInstance()->AddMapping(top_window
, m_hWnd
);
651 return chrome_wndproc_hook_
? S_OK
: E_FAIL
;
654 HRESULT
ChromeFrameActivex::registerBhoIfNeeded() {
656 NOTREACHED() << "Invalid client site";
660 if (NavigationManager::GetThreadInstance() != NULL
) {
661 DVLOG(1) << "BHO already loaded";
665 base::win::ScopedComPtr
<IWebBrowser2
> web_browser2
;
666 HRESULT hr
= DoQueryService(SID_SWebBrowserApp
, m_spUnkSite
,
667 web_browser2
.Receive());
668 if (FAILED(hr
) || web_browser2
.get() == NULL
) {
669 DLOG(WARNING
) << "Failed to get IWebBrowser2 from client site. Error:"
670 << base::StringPrintf(" 0x%08X", hr
);
674 wchar_t bho_class_id_as_string
[MAX_PATH
] = {0};
675 StringFromGUID2(CLSID_ChromeFrameBHO
, bho_class_id_as_string
,
676 arraysize(bho_class_id_as_string
));
678 base::win::ScopedComPtr
<IObjectWithSite
> bho
;
679 hr
= bho
.CreateInstance(CLSID_ChromeFrameBHO
, NULL
, CLSCTX_INPROC_SERVER
);
681 NOTREACHED() << "Failed to register ChromeFrame BHO. Error:"
682 << base::StringPrintf(" 0x%08X", hr
);
686 hr
= UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH
, NULL
, 0, 0);
688 DLOG(ERROR
) << "Failed to refresh user agent string from registry. "
689 << "UrlMkSetSessionOption returned "
690 << base::StringPrintf("0x%08x", hr
);
694 hr
= bho
->SetSite(web_browser2
);
696 NOTREACHED() << "ChromeFrame BHO SetSite failed. Error:"
697 << base::StringPrintf(" 0x%08X", hr
);
701 web_browser2
->PutProperty(base::win::ScopedBstr(bho_class_id_as_string
),
702 base::win::ScopedVariant(bho
));