Another take on menu's. This uses the hosting menu scroll view container as a menuba...
[chromium-blink-merge.git] / chrome_frame / chrome_frame_activex.cc
blob04012980587e62b5a804a48d254071bfe0038600
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"
7 #include <wininet.h>
9 #include <algorithm>
10 #include <map>
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"
29 namespace {
31 // Class used to maintain a mapping from top-level windows to ChromeFrameActivex
32 // instances.
33 class TopLevelWindowMapping {
34 public:
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();
53 return list;
56 private:
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;
79 switch (message) {
80 case WM_MOVE:
81 case WM_MOVING: {
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);
89 break;
91 default:
92 break;
95 return CallNextHookEx(0, code, wparam, lparam);
98 HHOOK InstallLocalWindowHook(HWND window) {
99 if (!window)
100 return NULL;
102 DWORD proc_thread = ::GetWindowThreadProcessId(window, NULL);
103 if (!proc_thread)
104 return NULL;
106 // Note that this hook is installed as a LOCAL hook.
107 return ::SetWindowsHookEx(WH_CALLWNDPROC,
108 TopWindowProc,
109 NULL,
110 proc_thread);
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();
122 if (FAILED(hr))
123 return hr;
125 // No need to call FireOnChanged at this point since nobody will be listening.
126 ready_state_ = READYSTATE_LOADING;
127 return S_OK;
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,
150 BOOL& handled) {
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);
157 return 0;
160 LRESULT ChromeFrameActivex::OnHostMoved(UINT message, WPARAM wparam,
161 LPARAM lparam, BOOL& handled) {
162 Base::OnHostMoved();
163 return 0;
166 HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) {
167 ScopedComPtr<IOleContainer> container;
168 HRESULT hr = m_spClientSite->GetContainer(container.Receive());
169 if (container)
170 hr = container.QueryInterface(doc);
171 return hr;
174 HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) {
175 ScopedComPtr<IHTMLDocument2> document;
176 HRESULT hr = GetContainingDocument(document.Receive());
177 if (document)
178 hr = document->get_parentWindow(window);
179 return hr;
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())))
186 Fire_onload(event);
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) {
208 bool drop = true;
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);
220 } else {
221 if (HaveSameOrigin(target, document_url_)) {
222 drop = false;
223 } else {
224 DLOG(WARNING) << "Dropping posted message since target doesn't match "
225 "the current document's origin. target=" << target;
229 if (drop)
230 return;
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,
257 void* user_data,
258 AutomationMsg_ExtensionResponseValues response) {
259 ScopedBstr path_str(path.value().c_str());
260 Fire_onextensionready(path_str, response);
263 void ChromeFrameActivex::OnGetEnabledExtensionsComplete(
264 void* user_data,
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;
269 ::SafeArrayLock(sa);
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
287 HRESULT hr = S_OK;
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);
297 } else {
298 hr = Base::OnDraw(draw_info);
301 return hr;
304 STDMETHODIMP ChromeFrameActivex::Load(IPropertyBag* bag, IErrorLog* error_log) {
305 DCHECK(bag);
307 const wchar_t* event_props[] = {
308 (L"onload"),
309 (L"onloaderror"),
310 (L"onmessage"),
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);
322 HRESULT hr = S_OK;
324 for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) {
325 ScopedBstr prop(event_props[i]);
326 ScopedVariant value;
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());
333 } else {
334 DLOG(INFO) << "script block created for event " << prop <<
335 StringPrintf(" (0x%08X)", hr) << " connections: " <<
336 ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize();
338 } else {
339 DLOG(INFO) << "event property " << prop << " not in property bag";
343 ScopedVariant src;
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(),
356 0, VT_BOOL);
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);
366 return 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[] = {
390 &onmessage_,
391 &onloaderror_,
392 &onload_,
393 &onreadystatechanged_,
394 &onextensionready_,
397 for (int i = 0; i < arraysize(handlers); ++i)
398 handlers[i]->clear();
400 // Drop privileged mode on uninitialization.
401 is_privileged_ = false;
402 } else {
403 ScopedComPtr<IHTMLDocument2> document;
404 GetContainingDocument(document.Receive());
405 if (document) {
406 ScopedBstr url;
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,
414 m_spClientSite,
415 service.Receive());
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;
458 if (url_.Length()) {
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),
466 GURL())) {
467 DLOG(ERROR) << "Failed to navigate to url:" << utf8_url;
468 return E_FAIL;
472 return hr;
475 HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem,
476 BSTR* id) {
477 DCHECK(object_elem != NULL);
478 DCHECK(id != NULL);
480 HRESULT hr = E_FAIL;
481 if (object_elem) {
482 ScopedComPtr<IHTMLElement> elem;
483 hr = elem.QueryFrom(object_elem);
484 if (elem) {
485 hr = elem->get_id(id);
489 return hr;
492 HRESULT ChromeFrameActivex::GetObjectElement(IHTMLObjectElement** element) {
493 DCHECK(m_spClientSite);
494 if (!m_spClientSite)
495 return E_UNEXPECTED;
497 ScopedComPtr<IOleControlSite> site;
498 HRESULT hr = site.QueryFrom(m_spClientSite);
499 if (site) {
500 ScopedComPtr<IDispatch> disp;
501 hr = site->GetExtendedControl(disp.Receive());
502 if (disp) {
503 hr = disp.QueryInterface(element);
504 } else {
505 DCHECK(FAILED(hr));
509 return hr;
512 HRESULT ChromeFrameActivex::CreateScriptBlockForEvent(
513 IHTMLElement2* insert_after, BSTR instance_id, BSTR script,
514 BSTR event_name) {
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";
522 return E_INVALIDARG;
525 ScopedComPtr<IHTMLDocument2> document;
526 HRESULT hr = GetContainingDocument(document.Receive());
527 if (SUCCEEDED(hr)) {
528 ScopedComPtr<IHTMLElement> element, new_element;
529 document->createElement(StackBstr(L"script"), element.Receive());
530 if (element) {
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"),
538 element,
539 new_element.Receive());
544 return hr;
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,
558 IDispatch* event) {
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();
565 ++it) {
566 HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
567 DISPATCH_METHOD, &params, 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();
586 ++it) {
587 HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT,
588 DISPATCH_METHOD, &params, 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);
601 if (FAILED(hr))
602 return hr;
604 HWND parent_wnd;
605 hr = ole_window->GetWindow(&parent_wnd);
606 if (FAILED(hr))
607 return hr;
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() {
618 if (!m_spUnkSite) {
619 NOTREACHED() << "Invalid client site";
620 return E_FAIL;
623 if (NavigationManager::GetThreadInstance() != NULL) {
624 DLOG(INFO) << "BHO already loaded";
625 return S_OK;
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);
634 return 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);
643 if (FAILED(hr)) {
644 NOTREACHED() << "Failed to register ChromeFrame BHO. Error:"
645 << StringPrintf(" 0x%08X", hr);
646 return hr;
649 hr = bho->SetSite(web_browser2);
650 if (FAILED(hr)) {
651 NOTREACHED() << "ChromeFrame BHO SetSite failed. Error:"
652 << StringPrintf(" 0x%08X", hr);
653 return hr;
656 web_browser2->PutProperty(ScopedBstr(bho_class_id_as_string),
657 ScopedVariant(bho));
658 return S_OK;