1 // Copyright (c) 2010 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/file_util.h"
15 #include "base/logging.h"
16 #include "base/path_service.h"
17 #include "base/process_util.h"
18 #include "base/scoped_bstr_win.h"
19 #include "base/singleton.h"
20 #include "base/string_util.h"
21 #include "base/trace_event.h"
22 #include "base/utf_string_conversions.h"
23 #include "chrome/common/chrome_constants.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/test/automation/tab_proxy.h"
26 #include "googleurl/src/gurl.h"
27 #include "chrome_frame/utils.h"
31 // Class used to maintain a mapping from top-level windows to ChromeFrameActivex
33 class TopLevelWindowMapping
{
35 typedef std::vector
<HWND
> WindowList
;
37 static TopLevelWindowMapping
* instance() {
38 return Singleton
<TopLevelWindowMapping
>::get();
41 // Add |cf_window| to the set of windows registered under |top_window|.
42 void AddMapping(HWND top_window
, HWND cf_window
) {
43 top_window_map_lock_
.Lock();
44 top_window_map_
[top_window
].push_back(cf_window
);
45 top_window_map_lock_
.Unlock();
48 // Return the set of Chrome-Frame instances under |window|.
49 WindowList
GetInstances(HWND window
) {
50 top_window_map_lock_
.Lock();
51 WindowList list
= top_window_map_
[window
];
52 top_window_map_lock_
.Unlock();
57 // Constructor is private as this class it to be used as a singleton.
58 // See static method instance().
59 TopLevelWindowMapping() {}
61 friend struct DefaultSingletonTraits
<TopLevelWindowMapping
>;
63 typedef std::map
<HWND
, WindowList
> TopWindowMap
;
64 TopWindowMap top_window_map_
;
66 CComAutoCriticalSection top_window_map_lock_
;
68 DISALLOW_COPY_AND_ASSIGN(TopLevelWindowMapping
);
71 // Message pump hook function that monitors for WM_MOVE and WM_MOVING
72 // messages on a top-level window, and passes notification to the appropriate
73 // Chrome-Frame instances.
74 LRESULT CALLBACK
TopWindowProc(int code
, WPARAM wparam
, LPARAM lparam
) {
75 CWPSTRUCT
*info
= reinterpret_cast<CWPSTRUCT
*>(lparam
);
76 const UINT
&message
= info
->message
;
77 const HWND
&message_hwnd
= info
->hwnd
;
82 TopLevelWindowMapping::WindowList cf_instances
=
83 TopLevelWindowMapping::instance()->GetInstances(message_hwnd
);
84 TopLevelWindowMapping::WindowList::iterator
85 iter(cf_instances
.begin()), end(cf_instances
.end());
86 for (;iter
!= end
; ++iter
) {
87 PostMessage(*iter
, WM_HOST_MOVED_NOTIFICATION
, NULL
, NULL
);
95 return CallNextHookEx(0, code
, wparam
, lparam
);
98 HHOOK
InstallLocalWindowHook(HWND window
) {
102 DWORD proc_thread
= ::GetWindowThreadProcessId(window
, NULL
);
106 // Note that this hook is installed as a LOCAL hook.
107 return ::SetWindowsHookEx(WH_CALLWNDPROC
,
113 } // unnamed namespace
115 ChromeFrameActivex::ChromeFrameActivex()
116 : chrome_wndproc_hook_(NULL
) {
117 TRACE_EVENT_BEGIN("chromeframe.createactivex", this, "");
120 HRESULT
ChromeFrameActivex::FinalConstruct() {
121 HRESULT hr
= Base::FinalConstruct();
125 // No need to call FireOnChanged at this point since nobody will be listening.
126 ready_state_
= READYSTATE_LOADING
;
130 ChromeFrameActivex::~ChromeFrameActivex() {
131 // We expect these to be released during a call to SetClientSite(NULL).
132 DCHECK_EQ(0u, onmessage_
.size());
133 DCHECK_EQ(0u, onloaderror_
.size());
134 DCHECK_EQ(0u, onload_
.size());
135 DCHECK_EQ(0u, onreadystatechanged_
.size());
136 DCHECK_EQ(0u, onextensionready_
.size());
138 if (chrome_wndproc_hook_
) {
139 BOOL unhook_success
= ::UnhookWindowsHookEx(chrome_wndproc_hook_
);
140 DCHECK(unhook_success
);
143 // ChromeFramePlugin::Uninitialize()
144 Base::Uninitialize();
146 TRACE_EVENT_END("chromeframe.createactivex", this, "");
149 LRESULT
ChromeFrameActivex::OnCreate(UINT message
, WPARAM wparam
, LPARAM lparam
,
151 Base::OnCreate(message
, wparam
, lparam
, handled
);
152 // Install the notification hook on the top-level window, so that we can
153 // be notified on move events. Note that the return value is not checked.
154 // This hook is installed here, as opposed to during IOleObject_SetClientSite
155 // because m_hWnd has not yet been assigned during the SetSite call.
156 InstallTopLevelHook(m_spClientSite
);
160 LRESULT
ChromeFrameActivex::OnHostMoved(UINT message
, WPARAM wparam
,
161 LPARAM lparam
, BOOL
& handled
) {
166 HRESULT
ChromeFrameActivex::GetContainingDocument(IHTMLDocument2
** doc
) {
167 ScopedComPtr
<IOleContainer
> container
;
168 HRESULT hr
= m_spClientSite
->GetContainer(container
.Receive());
170 hr
= container
.QueryInterface(doc
);
174 HRESULT
ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2
** window
) {
175 ScopedComPtr
<IHTMLDocument2
> document
;
176 HRESULT hr
= GetContainingDocument(document
.Receive());
178 hr
= document
->get_parentWindow(window
);
182 void ChromeFrameActivex::OnLoad(int tab_handle
, const GURL
& gurl
) {
183 ScopedComPtr
<IDispatch
> event
;
184 std::string url
= gurl
.spec();
185 if (SUCCEEDED(CreateDomEvent("event", url
, "", event
.Receive())))
188 FireEvent(onload_
, url
);
189 Base::OnLoad(tab_handle
, gurl
);
192 void ChromeFrameActivex::OnLoadFailed(int error_code
, const std::string
& url
) {
193 ScopedComPtr
<IDispatch
> event
;
194 if (SUCCEEDED(CreateDomEvent("event", url
, "", event
.Receive())))
195 Fire_onloaderror(event
);
197 FireEvent(onloaderror_
, url
);
198 Base::OnLoadFailed(error_code
, url
);
201 void ChromeFrameActivex::OnMessageFromChromeFrame(int tab_handle
,
202 const std::string
& message
,
203 const std::string
& origin
,
204 const std::string
& target
) {
205 DLOG(INFO
) << __FUNCTION__
;
207 if (target
.compare("*") != 0) {
210 if (is_privileged_
) {
211 // Forward messages if the control is in privileged mode.
212 ScopedComPtr
<IDispatch
> message_event
;
213 if (SUCCEEDED(CreateDomEvent("message", message
, origin
,
214 message_event
.Receive()))) {
215 ScopedBstr
target_bstr(UTF8ToWide(target
).c_str());
216 Fire_onprivatemessage(message_event
, target_bstr
);
218 FireEvent(onprivatemessage_
, message_event
, target_bstr
);
221 if (HaveSameOrigin(target
, document_url_
)) {
224 DLOG(WARNING
) << "Dropping posted message since target doesn't match "
225 "the current document's origin. target=" << target
;
233 ScopedComPtr
<IDispatch
> message_event
;
234 if (SUCCEEDED(CreateDomEvent("message", message
, origin
,
235 message_event
.Receive()))) {
236 Fire_onmessage(message_event
);
238 FireEvent(onmessage_
, message_event
);
240 ScopedVariant event_var
;
241 event_var
.Set(static_cast<IDispatch
*>(message_event
));
242 InvokeScriptFunction(onmessage_handler_
, event_var
.AsInput());
246 void ChromeFrameActivex::OnAutomationServerLaunchFailed(
247 AutomationLaunchResult reason
, const std::string
& server_version
) {
248 Base::OnAutomationServerLaunchFailed(reason
, server_version
);
250 if (reason
== AUTOMATION_VERSION_MISMATCH
) {
251 DisplayVersionMismatchWarning(m_hWnd
, server_version
);
255 void ChromeFrameActivex::OnExtensionInstalled(
256 const FilePath
& path
,
258 AutomationMsg_ExtensionResponseValues response
) {
259 ScopedBstr
path_str(path
.value().c_str());
260 Fire_onextensionready(path_str
, response
);
263 void ChromeFrameActivex::OnGetEnabledExtensionsComplete(
265 const std::vector
<FilePath
>& extension_directories
) {
266 SAFEARRAY
* sa
= ::SafeArrayCreateVector(VT_BSTR
, 0,
267 extension_directories
.size());
268 sa
->fFeatures
= sa
->fFeatures
| FADF_BSTR
;
271 for (size_t i
= 0; i
< extension_directories
.size(); ++i
) {
272 LONG index
= static_cast<LONG
>(i
);
273 ::SafeArrayPutElement(sa
, &index
, reinterpret_cast<void*>(
274 CComBSTR(extension_directories
[i
].value().c_str()).Detach()));
277 Fire_ongetenabledextensionscomplete(sa
);
278 ::SafeArrayUnlock(sa
);
279 ::SafeArrayDestroy(sa
);
282 void ChromeFrameActivex::OnChannelError() {
283 Fire_onchannelerror();
286 HRESULT
ChromeFrameActivex::OnDraw(ATL_DRAWINFO
& draw_info
) { // NOLINT
288 int dc_type
= ::GetObjectType(draw_info
.hicTargetDev
);
289 if (dc_type
== OBJ_ENHMETADC
) {
290 RECT print_bounds
= {0};
291 print_bounds
.left
= draw_info
.prcBounds
->left
;
292 print_bounds
.right
= draw_info
.prcBounds
->right
;
293 print_bounds
.top
= draw_info
.prcBounds
->top
;
294 print_bounds
.bottom
= draw_info
.prcBounds
->bottom
;
296 automation_client_
->Print(draw_info
.hdcDraw
, print_bounds
);
298 hr
= Base::OnDraw(draw_info
);
304 STDMETHODIMP
ChromeFrameActivex::Load(IPropertyBag
* bag
, IErrorLog
* error_log
) {
307 const wchar_t* event_props
[] = {
311 (L
"onreadystatechanged"),
314 ScopedComPtr
<IHTMLObjectElement
> obj_element
;
315 GetObjectElement(obj_element
.Receive());
317 ScopedBstr object_id
;
318 GetObjectScriptId(obj_element
, object_id
.Receive());
320 ScopedComPtr
<IHTMLElement2
> element
;
321 element
.QueryFrom(obj_element
);
324 for (int i
= 0; SUCCEEDED(hr
) && i
< arraysize(event_props
); ++i
) {
325 ScopedBstr
prop(event_props
[i
]);
327 if (SUCCEEDED(bag
->Read(prop
, value
.Receive(), error_log
))) {
328 if (value
.type() != VT_BSTR
||
329 FAILED(hr
= CreateScriptBlockForEvent(element
, object_id
,
330 V_BSTR(&value
), prop
))) {
331 DLOG(ERROR
) << "Failed to create script block for " << prop
332 << StringPrintf(L
"hr=0x%08X, vt=%i", hr
, value
.type());
334 DLOG(INFO
) << "script block created for event " << prop
<<
335 StringPrintf(" (0x%08X)", hr
) << " connections: " <<
336 ProxyDIChromeFrameEvents
<ChromeFrameActivex
>::m_vec
.GetSize();
339 DLOG(INFO
) << "event property " << prop
<< " not in property bag";
344 if (SUCCEEDED(bag
->Read(StackBstr(L
"src"), src
.Receive(), error_log
))) {
345 if (src
.type() == VT_BSTR
) {
346 hr
= put_src(V_BSTR(&src
));
347 DCHECK(hr
!= E_UNEXPECTED
);
351 ScopedVariant use_chrome_network
;
352 if (SUCCEEDED(bag
->Read(StackBstr(L
"useChromeNetwork"),
353 use_chrome_network
.Receive(), error_log
))) {
354 VariantChangeType(use_chrome_network
.AsInput(),
355 use_chrome_network
.AsInput(),
357 if (use_chrome_network
.type() == VT_BOOL
) {
358 hr
= put_useChromeNetwork(V_BOOL(&use_chrome_network
));
359 DCHECK(hr
!= E_UNEXPECTED
);
363 DLOG_IF(ERROR
, FAILED(hr
))
364 << StringPrintf("Failed to load property bag: 0x%08X", hr
);
369 const wchar_t g_activex_insecure_content_error
[] = {
370 L
"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>"
371 L
"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"};
373 STDMETHODIMP
ChromeFrameActivex::put_src(BSTR src
) {
374 GURL
document_url(GetDocumentUrl());
375 if (document_url
.SchemeIsSecure()) {
376 GURL
source_url(src
);
377 if (!source_url
.SchemeIsSecure()) {
378 Base::put_src(ScopedBstr(g_activex_insecure_content_error
));
379 return E_ACCESSDENIED
;
382 return Base::put_src(src
);
385 HRESULT
ChromeFrameActivex::IOleObject_SetClientSite(
386 IOleClientSite
* client_site
) {
387 HRESULT hr
= Base::IOleObject_SetClientSite(client_site
);
388 if (FAILED(hr
) || !client_site
) {
389 EventHandlers
* handlers
[] = {
393 &onreadystatechanged_
,
397 for (int i
= 0; i
< arraysize(handlers
); ++i
)
398 handlers
[i
]->clear();
400 // Drop privileged mode on uninitialization.
401 is_privileged_
= false;
403 ScopedComPtr
<IHTMLDocument2
> document
;
404 GetContainingDocument(document
.Receive());
407 if (SUCCEEDED(document
->get_URL(url
.Receive())))
408 WideToUTF8(url
, url
.Length(), &document_url_
);
411 // Probe to see whether the host implements the privileged service.
412 ScopedComPtr
<IChromeFramePrivileged
> service
;
413 HRESULT service_hr
= DoQueryService(SID_ChromeFramePrivileged
,
416 if (SUCCEEDED(service_hr
) && service
) {
417 // Does the host want privileged mode?
418 boolean wants_privileged
= false;
419 service_hr
= service
->GetWantsPrivileged(&wants_privileged
);
421 if (SUCCEEDED(service_hr
) && wants_privileged
)
422 is_privileged_
= true;
424 url_fetcher_
->set_privileged_mode(is_privileged_
);
427 std::wstring chrome_extra_arguments
;
428 std::wstring
profile_name(GetHostProcessName(false));
429 if (is_privileged_
) {
430 // Does the host want to provide extra arguments?
431 ScopedBstr extra_arguments_arg
;
432 service_hr
= service
->GetChromeExtraArguments(
433 extra_arguments_arg
.Receive());
434 if (S_OK
== service_hr
&& extra_arguments_arg
)
435 chrome_extra_arguments
.assign(extra_arguments_arg
,
436 extra_arguments_arg
.Length());
438 ScopedBstr automated_functions_arg
;
439 service_hr
= service
->GetExtensionApisToAutomate(
440 automated_functions_arg
.Receive());
441 if (S_OK
== service_hr
&& automated_functions_arg
) {
442 std::string
automated_functions(
443 WideToASCII(static_cast<BSTR
>(automated_functions_arg
)));
444 functions_enabled_
.clear();
445 // SplitString writes one empty entry for blank strings, so we need this
446 // to allow specifying zero automation of API functions.
447 if (!automated_functions
.empty())
448 SplitString(automated_functions
, ',', &functions_enabled_
);
451 ScopedBstr profile_name_arg
;
452 service_hr
= service
->GetChromeProfileName(profile_name_arg
.Receive());
453 if (S_OK
== service_hr
&& profile_name_arg
)
454 profile_name
.assign(profile_name_arg
, profile_name_arg
.Length());
457 std::string utf8_url
;
459 WideToUTF8(url_
, url_
.Length(), &utf8_url
);
462 url_fetcher_
->set_frame_busting(!is_privileged_
);
463 automation_client_
->SetUrlFetcher(url_fetcher_
.get());
464 if (!InitializeAutomation(profile_name
, chrome_extra_arguments
,
465 IsIEInPrivate(), true, GURL(utf8_url
),
467 DLOG(ERROR
) << "Failed to navigate to url:" << utf8_url
;
475 HRESULT
ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement
* object_elem
,
477 DCHECK(object_elem
!= NULL
);
482 ScopedComPtr
<IHTMLElement
> elem
;
483 hr
= elem
.QueryFrom(object_elem
);
485 hr
= elem
->get_id(id
);
492 HRESULT
ChromeFrameActivex::GetObjectElement(IHTMLObjectElement
** element
) {
493 DCHECK(m_spClientSite
);
497 ScopedComPtr
<IOleControlSite
> site
;
498 HRESULT hr
= site
.QueryFrom(m_spClientSite
);
500 ScopedComPtr
<IDispatch
> disp
;
501 hr
= site
->GetExtendedControl(disp
.Receive());
503 hr
= disp
.QueryInterface(element
);
512 HRESULT
ChromeFrameActivex::CreateScriptBlockForEvent(
513 IHTMLElement2
* insert_after
, BSTR instance_id
, BSTR script
,
515 DCHECK(insert_after
);
516 DCHECK_GT(::SysStringLen(event_name
), 0UL); // should always have this
518 // This might be 0 if not specified in the HTML document.
519 if (!::SysStringLen(instance_id
)) {
520 // TODO(tommi): Should we give ourselves an ID if this happens?
521 NOTREACHED() << "Need to handle this";
525 ScopedComPtr
<IHTMLDocument2
> document
;
526 HRESULT hr
= GetContainingDocument(document
.Receive());
528 ScopedComPtr
<IHTMLElement
> element
, new_element
;
529 document
->createElement(StackBstr(L
"script"), element
.Receive());
531 ScopedComPtr
<IHTMLScriptElement
> script_element
;
532 if (SUCCEEDED(hr
= script_element
.QueryFrom(element
))) {
533 script_element
->put_htmlFor(instance_id
);
534 script_element
->put_event(event_name
);
535 script_element
->put_text(script
);
537 hr
= insert_after
->insertAdjacentElement(StackBstr(L
"afterEnd"),
539 new_element
.Receive());
547 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
548 const std::string
& arg
) {
549 if (handlers
.size()) {
550 ScopedComPtr
<IDispatch
> event
;
551 if (SUCCEEDED(CreateDomEvent("event", arg
, "", event
.Receive()))) {
552 FireEvent(handlers
, event
);
557 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
559 DCHECK(event
!= NULL
);
560 VARIANT arg
= { VT_DISPATCH
};
561 arg
.pdispVal
= event
;
562 DISPPARAMS params
= { &arg
, NULL
, 1, 0 };
563 for (EventHandlers::const_iterator it
= handlers
.begin();
564 it
!= handlers
.end();
566 HRESULT hr
= (*it
)->Invoke(DISPID_VALUE
, IID_NULL
, LOCALE_USER_DEFAULT
,
567 DISPATCH_METHOD
, ¶ms
, NULL
, NULL
, NULL
);
568 // 0x80020101 == SCRIPT_E_REPORTED.
569 // When the script we're invoking has an error, we get this error back.
570 DLOG_IF(ERROR
, FAILED(hr
) && hr
!= 0x80020101)
571 << StringPrintf(L
"Failed to invoke script: 0x%08X", hr
);
575 void ChromeFrameActivex::FireEvent(const EventHandlers
& handlers
,
576 IDispatch
* event
, BSTR target
) {
577 DCHECK(event
!= NULL
);
578 // Arguments in reverse order to event handler function declaration,
579 // because that's what DISPPARAMS requires.
580 VARIANT args
[2] = { { VT_BSTR
}, { VT_DISPATCH
}, };
581 args
[0].bstrVal
= target
;
582 args
[1].pdispVal
= event
;
583 DISPPARAMS params
= { args
, NULL
, arraysize(args
), 0 };
584 for (EventHandlers::const_iterator it
= handlers
.begin();
585 it
!= handlers
.end();
587 HRESULT hr
= (*it
)->Invoke(DISPID_VALUE
, IID_NULL
, LOCALE_USER_DEFAULT
,
588 DISPATCH_METHOD
, ¶ms
, NULL
, NULL
, NULL
);
589 // 0x80020101 == SCRIPT_E_REPORTED.
590 // When the script we're invoking has an error, we get this error back.
591 DLOG_IF(ERROR
, FAILED(hr
) && hr
!= 0x80020101)
592 << StringPrintf(L
"Failed to invoke script: 0x%08X", hr
);
596 HRESULT
ChromeFrameActivex::InstallTopLevelHook(IOleClientSite
* client_site
) {
597 // Get the parent window of the site, and install our hook on the topmost
598 // window of the parent.
599 ScopedComPtr
<IOleWindow
> ole_window
;
600 HRESULT hr
= ole_window
.QueryFrom(client_site
);
605 hr
= ole_window
->GetWindow(&parent_wnd
);
609 HWND top_window
= ::GetAncestor(parent_wnd
, GA_ROOT
);
610 chrome_wndproc_hook_
= InstallLocalWindowHook(top_window
);
611 if (chrome_wndproc_hook_
)
612 TopLevelWindowMapping::instance()->AddMapping(top_window
, m_hWnd
);
614 return chrome_wndproc_hook_
? S_OK
: E_FAIL
;
617 HRESULT
ChromeFrameActivex::registerBhoIfNeeded() {
619 NOTREACHED() << "Invalid client site";
623 if (NavigationManager::GetThreadInstance() != NULL
) {
624 DLOG(INFO
) << "BHO already loaded";
628 ScopedComPtr
<IWebBrowser2
> web_browser2
;
629 HRESULT hr
= DoQueryService(SID_SWebBrowserApp
, m_spUnkSite
,
630 web_browser2
.Receive());
631 if (FAILED(hr
) || web_browser2
.get() == NULL
) {
632 DLOG(WARNING
) << "Failed to get IWebBrowser2 from client site. Error:"
633 << StringPrintf(" 0x%08X", hr
);
637 wchar_t bho_class_id_as_string
[MAX_PATH
] = {0};
638 StringFromGUID2(CLSID_ChromeFrameBHO
, bho_class_id_as_string
,
639 arraysize(bho_class_id_as_string
));
641 ScopedComPtr
<IObjectWithSite
> bho
;
642 hr
= bho
.CreateInstance(CLSID_ChromeFrameBHO
, NULL
, CLSCTX_INPROC_SERVER
);
644 NOTREACHED() << "Failed to register ChromeFrame BHO. Error:"
645 << StringPrintf(" 0x%08X", hr
);
649 hr
= bho
->SetSite(web_browser2
);
651 NOTREACHED() << "ChromeFrame BHO SetSite failed. Error:"
652 << StringPrintf(" 0x%08X", hr
);
656 web_browser2
->PutProperty(ScopedBstr(bho_class_id_as_string
),