Index: chrome_frame/chrome_frame_activex.cc |
=================================================================== |
--- chrome_frame/chrome_frame_activex.cc (revision 0) |
+++ chrome_frame/chrome_frame_activex.cc (revision 0) |
@@ -0,0 +1,505 @@ |
+// Copyright (c) 2009 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 <shdeprecated.h> // for IBrowserService2 |
+#include <wininet.h> |
+ |
+#include <algorithm> |
+ |
+#include "base/basictypes.h" |
+#include "base/command_line.h" |
+#include "base/file_util.h" |
+#include "base/logging.h" |
+#include "base/path_service.h" |
+#include "base/process_util.h" |
+#include "base/scoped_bstr_win.h" |
+#include "base/string_util.h" |
+#include "chrome/common/chrome_constants.h" |
+#include "chrome/common/chrome_switches.h" |
+#include "chrome/test/automation/tab_proxy.h" |
+#include "googleurl/src/gurl.h" |
+#include "chrome_frame/com_message_event.h" |
+#include "chrome_frame/utils.h" |
+ |
+ChromeFrameActivex::ChromeFrameActivex() { |
+} |
+ |
+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(onmessage_.size() == 0); |
+ DCHECK(onloaderror_.size() == 0); |
+ DCHECK(onload_.size() == 0); |
+ DCHECK(onreadystatechanged_.size() == 0); |
+} |
+ |
+LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam, |
+ BOOL& handled) { |
+ Base::OnCreate(message, wparam, lparam, handled); |
+ return 0; |
+} |
+ |
+void ChromeFrameActivex::OnAcceleratorPressed(int tab_handle, |
+ const MSG& accel_message) { |
+ DCHECK(m_spInPlaceSite != NULL); |
+ // Allow our host a chance to handle the accelerator. |
+ // This catches things like Ctrl+F, Ctrl+O etc, but not browser |
+ // accelerators such as F11, Ctrl+T etc. |
+ // (see AllowFrameToTranslateAccelerator for those). |
+ HRESULT hr = TranslateAccelerator(const_cast<MSG*>(&accel_message)); |
+ if (hr != S_OK) |
+ hr = AllowFrameToTranslateAccelerator(accel_message); |
+ |
+ DLOG(INFO) << __FUNCTION__ << " browser response: " |
+ << StringPrintf("0x%08x", hr); |
+ |
+ // Last chance to handle the keystroke is to pass it to chromium. |
+ // We do this last partially because there's no way for us to tell if |
+ // chromium actually handled the keystroke, but also since the browser |
+ // should have first dibs anyway. |
+ if (hr != S_OK && automation_client_.get()) { |
+ TabProxy* tab = automation_client_->tab(); |
+ if (tab) { |
+ tab->ProcessUnhandledAccelerator(accel_message); |
+ } |
+ } |
+} |
+ |
+HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) { |
+ ScopedComPtr<IOleContainer> container; |
+ HRESULT hr = m_spClientSite->GetContainer(container.Receive()); |
+ if (container) |
+ hr = container.QueryInterface(doc); |
+ return hr; |
+} |
+ |
+HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) { |
+ ScopedComPtr<IHTMLDocument2> document; |
+ HRESULT hr = GetContainingDocument(document.Receive()); |
+ if (document) |
+ hr = document->get_parentWindow(window); |
+ return hr; |
+} |
+ |
+void ChromeFrameActivex::OnLoad(int tab_handle, const GURL& gurl) { |
+ ScopedComPtr<IDispatch> event; |
+ std::string url = gurl.spec(); |
+ if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) |
+ Fire_onload(event); |
+ |
+ FireEvent(onload_, url); |
+ |
+ HRESULT hr = InvokeScriptFunction(onload_handler_, url); |
+ |
+ if (ready_state_ < READYSTATE_COMPLETE) { |
+ ready_state_ = READYSTATE_COMPLETE; |
+ FireOnChanged(DISPID_READYSTATE); |
+ } |
+} |
+ |
+void ChromeFrameActivex::OnLoadFailed(int error_code, const std::string& url) { |
+ ScopedComPtr<IDispatch> event; |
+ if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) |
+ Fire_onloaderror(event); |
+ |
+ FireEvent(onloaderror_, url); |
+ |
+ HRESULT hr = InvokeScriptFunction(onerror_handler_, url); |
+} |
+ |
+void ChromeFrameActivex::OnMessageFromChromeFrame(int tab_handle, |
+ const std::string& message, |
+ const std::string& origin, |
+ const std::string& target) { |
+ DLOG(INFO) << __FUNCTION__; |
+ |
+ if (target.compare("*") != 0) { |
+ bool drop = true; |
+ |
+ if (is_privileged_) { |
+ // Forward messages if the control is in privileged mode. |
+ ScopedComPtr<IDispatch> message_event; |
+ if (SUCCEEDED(CreateDomEvent("message", message, origin, |
+ message_event.Receive()))) { |
+ ScopedBstr target_bstr(UTF8ToWide(target).c_str()); |
+ Fire_onprivatemessage(message_event, target_bstr); |
+ |
+ FireEvent(onprivatemessage_, message_event, target_bstr); |
+ } |
+ } else { |
+ if (HaveSameOrigin(target, document_url_)) { |
+ drop = false; |
+ } else { |
+ DLOG(WARNING) << "Dropping posted message since target doesn't match " |
+ "the current document's origin. target=" << target; |
+ } |
+ } |
+ |
+ if (drop) |
+ return; |
+ } |
+ |
+ ScopedComPtr<IDispatch> message_event; |
+ if (SUCCEEDED(CreateDomEvent("message", message, origin, |
+ message_event.Receive()))) { |
+ Fire_onmessage(message_event); |
+ |
+ FireEvent(onmessage_, message_event); |
+ |
+ ScopedVariant event_var; |
+ event_var.Set(static_cast<IDispatch*>(message_event)); |
+ InvokeScriptFunction(onmessage_handler_, event_var.AsInput()); |
+ } |
+} |
+ |
+void ChromeFrameActivex::OnAutomationServerLaunchFailed( |
+ AutomationLaunchResult reason, const std::string& server_version) { |
+ Base::OnAutomationServerLaunchFailed(reason, server_version); |
+ |
+ if (reason == AUTOMATION_VERSION_MISMATCH) { |
+ DisplayVersionMismatchWarning(m_hWnd, server_version); |
+ } |
+} |
+ |
+HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, |
+ const std::string& param) { |
+ ScopedVariant script_arg(UTF8ToWide(param.c_str()).c_str()); |
+ return InvokeScriptFunction(script_object, script_arg.AsInput()); |
+} |
+ |
+HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, |
+ VARIANT* param) { |
+ if (V_VT(&script_object) != VT_DISPATCH) { |
+ return S_FALSE; |
+ } |
+ |
+ CComPtr<IDispatch> script(script_object.pdispVal); |
+ HRESULT hr = script.Invoke1(static_cast<DISPID>(DISPID_VALUE), param); |
+ // 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) << "Failed to invoke script"; |
+ |
+ return hr; |
+} |
+ |
+HRESULT ChromeFrameActivex::OnDraw(ATL_DRAWINFO& draw_info) { // NO_LINT |
+ 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"), |
+ }; |
+ |
+ ScopedComPtr<IHTMLObjectElement> obj_element; |
+ GetObjectElement(obj_element.Receive()); |
+ |
+ ScopedBstr object_id; |
+ GetObjectScriptId(obj_element, object_id.Receive()); |
+ |
+ ScopedComPtr<IHTMLElement2> element; |
+ element.QueryFrom(obj_element); |
+ HRESULT hr = S_OK; |
+ |
+ for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) { |
+ ScopedBstr prop(event_props[i]); |
+ 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 |
+ << StringPrintf(L"hr=0x%08X, vt=%i", hr, value.type()); |
+ } else { |
+ DLOG(INFO) << "script block created for event " << prop << |
+ StringPrintf(" (0x%08X)", hr) << " connections: " << |
+ ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize(); |
+ } |
+ } else { |
+ DLOG(INFO) << "event property " << prop << " not in property bag"; |
+ } |
+ } |
+ |
+ ScopedVariant src; |
+ if (SUCCEEDED(bag->Read(StackBstr(L"src"), src.Receive(), error_log))) { |
+ if (src.type() == VT_BSTR) { |
+ hr = put_src(V_BSTR(&src)); |
+ DCHECK(hr != E_UNEXPECTED); |
+ } |
+ } |
+ |
+ ScopedVariant use_chrome_network; |
+ if (SUCCEEDED(bag->Read(StackBstr(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)) |
+ << StringPrintf("Failed to load property bag: 0x%08X", hr); |
+ |
+ return hr; |
+} |
+ |
+const wchar_t g_activex_mixed_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(ScopedBstr(g_activex_mixed_content_error)); |
+ return E_ACCESSDENIED; |
+ } |
+ } |
+ return Base::put_src(src); |
+} |
+ |
+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_, |
+ }; |
+ |
+ for (int i = 0; i < arraysize(handlers); ++i) |
+ handlers[i]->clear(); |
+ |
+ // Drop privileged mode on uninitialization. |
+ is_privileged_ = false; |
+ } else { |
+ ScopedComPtr<IHTMLDocument2> document; |
+ GetContainingDocument(document.Receive()); |
+ if (document) { |
+ ScopedBstr url; |
+ if (SUCCEEDED(document->get_URL(url.Receive()))) |
+ WideToUTF8(url, url.Length(), &document_url_); |
+ } |
+ |
+ // Probe to see whether the host implements the privileged service. |
+ ScopedComPtr<IChromeFramePrivileged> service; |
+ HRESULT service_hr = DoQueryService(SID_ChromeFramePrivileged, client_site, |
+ 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) |
+ is_privileged_ = true; |
+ } |
+ |
+ std::wstring chrome_extra_arguments; |
+ std::wstring profile_name(GetHostProcessName(false)); |
+ if (is_privileged_) { |
+ // Does the host want to provide extra arguments? |
+ ScopedBstr extra_arguments_arg; |
+ service_hr = service->GetChromeExtraArguments( |
+ extra_arguments_arg.Receive()); |
+ if (S_OK == service_hr && extra_arguments_arg) |
+ chrome_extra_arguments.assign(extra_arguments_arg, |
+ extra_arguments_arg.Length()); |
+ |
+ 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()); |
+ } |
+ |
+ if (!InitializeAutomation(profile_name, chrome_extra_arguments, |
+ IsIEInPrivate())) { |
+ return E_FAIL; |
+ } |
+ } |
+ |
+ return hr; |
+} |
+ |
+HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem, |
+ BSTR* id) { |
+ DCHECK(object_elem != NULL); |
+ DCHECK(id != NULL); |
+ |
+ HRESULT hr = E_FAIL; |
+ if (object_elem) { |
+ 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; |
+ |
+ ScopedComPtr<IOleControlSite> site; |
+ HRESULT hr = site.QueryFrom(m_spClientSite); |
+ if (site) { |
+ 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(::SysStringLen(event_name) > 0); // 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; |
+ } |
+ |
+ ScopedComPtr<IHTMLDocument2> document; |
+ HRESULT hr = GetContainingDocument(document.Receive()); |
+ if (SUCCEEDED(hr)) { |
+ ScopedComPtr<IHTMLElement> element, new_element; |
+ document->createElement(StackBstr(L"script"), element.Receive()); |
+ if (element) { |
+ 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(StackBstr(L"afterEnd"), |
+ element, |
+ new_element.Receive()); |
+ } |
+ } |
+ } |
+ |
+ return hr; |
+} |
+ |
+HRESULT ChromeFrameActivex::CreateDomEvent(const std::string& event_type, |
+ const std::string& data, |
+ const std::string& origin, |
+ IDispatch** event) { |
+ DCHECK(event_type.length() > 0); |
+ DCHECK(event != NULL); |
+ |
+ CComObject<ComMessageEvent>* ev = NULL; |
+ HRESULT hr = CComObject<ComMessageEvent>::CreateInstance(&ev); |
+ if (SUCCEEDED(hr)) { |
+ ev->AddRef(); |
+ |
+ ScopedComPtr<IOleContainer> container; |
+ m_spClientSite->GetContainer(container.Receive()); |
+ if (ev->Initialize(container, data, origin, event_type)) { |
+ *event = ev; |
+ } else { |
+ NOTREACHED() << "event->Initialize"; |
+ ev->Release(); |
+ hr = E_UNEXPECTED; |
+ } |
+ } |
+ |
+ return hr; |
+} |
+ |
+void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, |
+ const std::string& arg) { |
+ if (handlers.size()) { |
+ 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) |
+ << 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) |
+ << StringPrintf(L"Failed to invoke script: 0x%08X", hr); |
+ } |
+} |