OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 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. |
| 4 |
| 5 #include "chrome_frame/chrome_frame_activex.h" |
| 6 |
| 7 #include <shdeprecated.h> // for IBrowserService2 |
| 8 #include <wininet.h> |
| 9 |
| 10 #include <algorithm> |
| 11 |
| 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/string_util.h" |
| 20 #include "chrome/common/chrome_constants.h" |
| 21 #include "chrome/common/chrome_switches.h" |
| 22 #include "chrome/test/automation/tab_proxy.h" |
| 23 #include "googleurl/src/gurl.h" |
| 24 #include "chrome_frame/com_message_event.h" |
| 25 #include "chrome_frame/utils.h" |
| 26 |
| 27 ChromeFrameActivex::ChromeFrameActivex() { |
| 28 } |
| 29 |
| 30 HRESULT ChromeFrameActivex::FinalConstruct() { |
| 31 HRESULT hr = Base::FinalConstruct(); |
| 32 if (FAILED(hr)) |
| 33 return hr; |
| 34 |
| 35 // No need to call FireOnChanged at this point since nobody will be listening. |
| 36 ready_state_ = READYSTATE_LOADING; |
| 37 return S_OK; |
| 38 } |
| 39 |
| 40 ChromeFrameActivex::~ChromeFrameActivex() { |
| 41 // We expect these to be released during a call to SetClientSite(NULL). |
| 42 DCHECK(onmessage_.size() == 0); |
| 43 DCHECK(onloaderror_.size() == 0); |
| 44 DCHECK(onload_.size() == 0); |
| 45 DCHECK(onreadystatechanged_.size() == 0); |
| 46 } |
| 47 |
| 48 LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam, |
| 49 BOOL& handled) { |
| 50 Base::OnCreate(message, wparam, lparam, handled); |
| 51 return 0; |
| 52 } |
| 53 |
| 54 void ChromeFrameActivex::OnAcceleratorPressed(int tab_handle, |
| 55 const MSG& accel_message) { |
| 56 DCHECK(m_spInPlaceSite != NULL); |
| 57 // Allow our host a chance to handle the accelerator. |
| 58 // This catches things like Ctrl+F, Ctrl+O etc, but not browser |
| 59 // accelerators such as F11, Ctrl+T etc. |
| 60 // (see AllowFrameToTranslateAccelerator for those). |
| 61 HRESULT hr = TranslateAccelerator(const_cast<MSG*>(&accel_message)); |
| 62 if (hr != S_OK) |
| 63 hr = AllowFrameToTranslateAccelerator(accel_message); |
| 64 |
| 65 DLOG(INFO) << __FUNCTION__ << " browser response: " |
| 66 << StringPrintf("0x%08x", hr); |
| 67 |
| 68 // Last chance to handle the keystroke is to pass it to chromium. |
| 69 // We do this last partially because there's no way for us to tell if |
| 70 // chromium actually handled the keystroke, but also since the browser |
| 71 // should have first dibs anyway. |
| 72 if (hr != S_OK && automation_client_.get()) { |
| 73 TabProxy* tab = automation_client_->tab(); |
| 74 if (tab) { |
| 75 tab->ProcessUnhandledAccelerator(accel_message); |
| 76 } |
| 77 } |
| 78 } |
| 79 |
| 80 HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) { |
| 81 ScopedComPtr<IOleContainer> container; |
| 82 HRESULT hr = m_spClientSite->GetContainer(container.Receive()); |
| 83 if (container) |
| 84 hr = container.QueryInterface(doc); |
| 85 return hr; |
| 86 } |
| 87 |
| 88 HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) { |
| 89 ScopedComPtr<IHTMLDocument2> document; |
| 90 HRESULT hr = GetContainingDocument(document.Receive()); |
| 91 if (document) |
| 92 hr = document->get_parentWindow(window); |
| 93 return hr; |
| 94 } |
| 95 |
| 96 void ChromeFrameActivex::OnLoad(int tab_handle, const GURL& gurl) { |
| 97 ScopedComPtr<IDispatch> event; |
| 98 std::string url = gurl.spec(); |
| 99 if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) |
| 100 Fire_onload(event); |
| 101 |
| 102 FireEvent(onload_, url); |
| 103 |
| 104 HRESULT hr = InvokeScriptFunction(onload_handler_, url); |
| 105 |
| 106 if (ready_state_ < READYSTATE_COMPLETE) { |
| 107 ready_state_ = READYSTATE_COMPLETE; |
| 108 FireOnChanged(DISPID_READYSTATE); |
| 109 } |
| 110 } |
| 111 |
| 112 void ChromeFrameActivex::OnLoadFailed(int error_code, const std::string& url) { |
| 113 ScopedComPtr<IDispatch> event; |
| 114 if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) |
| 115 Fire_onloaderror(event); |
| 116 |
| 117 FireEvent(onloaderror_, url); |
| 118 |
| 119 HRESULT hr = InvokeScriptFunction(onerror_handler_, url); |
| 120 } |
| 121 |
| 122 void ChromeFrameActivex::OnMessageFromChromeFrame(int tab_handle, |
| 123 const std::string& message, |
| 124 const std::string& origin, |
| 125 const std::string& target) { |
| 126 DLOG(INFO) << __FUNCTION__; |
| 127 |
| 128 if (target.compare("*") != 0) { |
| 129 bool drop = true; |
| 130 |
| 131 if (is_privileged_) { |
| 132 // Forward messages if the control is in privileged mode. |
| 133 ScopedComPtr<IDispatch> message_event; |
| 134 if (SUCCEEDED(CreateDomEvent("message", message, origin, |
| 135 message_event.Receive()))) { |
| 136 ScopedBstr target_bstr(UTF8ToWide(target).c_str()); |
| 137 Fire_onprivatemessage(message_event, target_bstr); |
| 138 |
| 139 FireEvent(onprivatemessage_, message_event, target_bstr); |
| 140 } |
| 141 } else { |
| 142 if (HaveSameOrigin(target, document_url_)) { |
| 143 drop = false; |
| 144 } else { |
| 145 DLOG(WARNING) << "Dropping posted message since target doesn't match " |
| 146 "the current document's origin. target=" << target; |
| 147 } |
| 148 } |
| 149 |
| 150 if (drop) |
| 151 return; |
| 152 } |
| 153 |
| 154 ScopedComPtr<IDispatch> message_event; |
| 155 if (SUCCEEDED(CreateDomEvent("message", message, origin, |
| 156 message_event.Receive()))) { |
| 157 Fire_onmessage(message_event); |
| 158 |
| 159 FireEvent(onmessage_, message_event); |
| 160 |
| 161 ScopedVariant event_var; |
| 162 event_var.Set(static_cast<IDispatch*>(message_event)); |
| 163 InvokeScriptFunction(onmessage_handler_, event_var.AsInput()); |
| 164 } |
| 165 } |
| 166 |
| 167 void ChromeFrameActivex::OnAutomationServerLaunchFailed( |
| 168 AutomationLaunchResult reason, const std::string& server_version) { |
| 169 Base::OnAutomationServerLaunchFailed(reason, server_version); |
| 170 |
| 171 if (reason == AUTOMATION_VERSION_MISMATCH) { |
| 172 DisplayVersionMismatchWarning(m_hWnd, server_version); |
| 173 } |
| 174 } |
| 175 |
| 176 HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, |
| 177 const std::string& param) { |
| 178 ScopedVariant script_arg(UTF8ToWide(param.c_str()).c_str()); |
| 179 return InvokeScriptFunction(script_object, script_arg.AsInput()); |
| 180 } |
| 181 |
| 182 HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, |
| 183 VARIANT* param) { |
| 184 if (V_VT(&script_object) != VT_DISPATCH) { |
| 185 return S_FALSE; |
| 186 } |
| 187 |
| 188 CComPtr<IDispatch> script(script_object.pdispVal); |
| 189 HRESULT hr = script.Invoke1(static_cast<DISPID>(DISPID_VALUE), param); |
| 190 // 0x80020101 == SCRIPT_E_REPORTED. |
| 191 // When the script we're invoking has an error, we get this error back. |
| 192 DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) << "Failed to invoke script"; |
| 193 |
| 194 return hr; |
| 195 } |
| 196 |
| 197 HRESULT ChromeFrameActivex::OnDraw(ATL_DRAWINFO& draw_info) { // NO_LINT |
| 198 HRESULT hr = S_OK; |
| 199 int dc_type = ::GetObjectType(draw_info.hicTargetDev); |
| 200 if (dc_type == OBJ_ENHMETADC) { |
| 201 RECT print_bounds = {0}; |
| 202 print_bounds.left = draw_info.prcBounds->left; |
| 203 print_bounds.right = draw_info.prcBounds->right; |
| 204 print_bounds.top = draw_info.prcBounds->top; |
| 205 print_bounds.bottom = draw_info.prcBounds->bottom; |
| 206 |
| 207 automation_client_->Print(draw_info.hdcDraw, print_bounds); |
| 208 } else { |
| 209 hr = Base::OnDraw(draw_info); |
| 210 } |
| 211 |
| 212 return hr; |
| 213 } |
| 214 |
| 215 STDMETHODIMP ChromeFrameActivex::Load(IPropertyBag* bag, IErrorLog* error_log) { |
| 216 DCHECK(bag); |
| 217 |
| 218 const wchar_t* event_props[] = { |
| 219 (L"onload"), |
| 220 (L"onloaderror"), |
| 221 (L"onmessage"), |
| 222 (L"onreadystatechanged"), |
| 223 }; |
| 224 |
| 225 ScopedComPtr<IHTMLObjectElement> obj_element; |
| 226 GetObjectElement(obj_element.Receive()); |
| 227 |
| 228 ScopedBstr object_id; |
| 229 GetObjectScriptId(obj_element, object_id.Receive()); |
| 230 |
| 231 ScopedComPtr<IHTMLElement2> element; |
| 232 element.QueryFrom(obj_element); |
| 233 HRESULT hr = S_OK; |
| 234 |
| 235 for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) { |
| 236 ScopedBstr prop(event_props[i]); |
| 237 ScopedVariant value; |
| 238 if (SUCCEEDED(bag->Read(prop, value.Receive(), error_log))) { |
| 239 if (value.type() != VT_BSTR || |
| 240 FAILED(hr = CreateScriptBlockForEvent(element, object_id, |
| 241 V_BSTR(&value), prop))) { |
| 242 DLOG(ERROR) << "Failed to create script block for " << prop |
| 243 << StringPrintf(L"hr=0x%08X, vt=%i", hr, value.type()); |
| 244 } else { |
| 245 DLOG(INFO) << "script block created for event " << prop << |
| 246 StringPrintf(" (0x%08X)", hr) << " connections: " << |
| 247 ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize(); |
| 248 } |
| 249 } else { |
| 250 DLOG(INFO) << "event property " << prop << " not in property bag"; |
| 251 } |
| 252 } |
| 253 |
| 254 ScopedVariant src; |
| 255 if (SUCCEEDED(bag->Read(StackBstr(L"src"), src.Receive(), error_log))) { |
| 256 if (src.type() == VT_BSTR) { |
| 257 hr = put_src(V_BSTR(&src)); |
| 258 DCHECK(hr != E_UNEXPECTED); |
| 259 } |
| 260 } |
| 261 |
| 262 ScopedVariant use_chrome_network; |
| 263 if (SUCCEEDED(bag->Read(StackBstr(L"useChromeNetwork"), |
| 264 use_chrome_network.Receive(), error_log))) { |
| 265 VariantChangeType(use_chrome_network.AsInput(), |
| 266 use_chrome_network.AsInput(), |
| 267 0, VT_BOOL); |
| 268 if (use_chrome_network.type() == VT_BOOL) { |
| 269 hr = put_useChromeNetwork(V_BOOL(&use_chrome_network)); |
| 270 DCHECK(hr != E_UNEXPECTED); |
| 271 } |
| 272 } |
| 273 |
| 274 DLOG_IF(ERROR, FAILED(hr)) |
| 275 << StringPrintf("Failed to load property bag: 0x%08X", hr); |
| 276 |
| 277 return hr; |
| 278 } |
| 279 |
| 280 const wchar_t g_activex_mixed_content_error[] = { |
| 281 L"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>" |
| 282 L"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"}; |
| 283 |
| 284 STDMETHODIMP ChromeFrameActivex::put_src(BSTR src) { |
| 285 GURL document_url(GetDocumentUrl()); |
| 286 if (document_url.SchemeIsSecure()) { |
| 287 GURL source_url(src); |
| 288 if (!source_url.SchemeIsSecure()) { |
| 289 Base::put_src(ScopedBstr(g_activex_mixed_content_error)); |
| 290 return E_ACCESSDENIED; |
| 291 } |
| 292 } |
| 293 return Base::put_src(src); |
| 294 } |
| 295 |
| 296 HRESULT ChromeFrameActivex::IOleObject_SetClientSite( |
| 297 IOleClientSite* client_site) { |
| 298 HRESULT hr = Base::IOleObject_SetClientSite(client_site); |
| 299 if (FAILED(hr) || !client_site) { |
| 300 EventHandlers* handlers[] = { |
| 301 &onmessage_, |
| 302 &onloaderror_, |
| 303 &onload_, |
| 304 &onreadystatechanged_, |
| 305 }; |
| 306 |
| 307 for (int i = 0; i < arraysize(handlers); ++i) |
| 308 handlers[i]->clear(); |
| 309 |
| 310 // Drop privileged mode on uninitialization. |
| 311 is_privileged_ = false; |
| 312 } else { |
| 313 ScopedComPtr<IHTMLDocument2> document; |
| 314 GetContainingDocument(document.Receive()); |
| 315 if (document) { |
| 316 ScopedBstr url; |
| 317 if (SUCCEEDED(document->get_URL(url.Receive()))) |
| 318 WideToUTF8(url, url.Length(), &document_url_); |
| 319 } |
| 320 |
| 321 // Probe to see whether the host implements the privileged service. |
| 322 ScopedComPtr<IChromeFramePrivileged> service; |
| 323 HRESULT service_hr = DoQueryService(SID_ChromeFramePrivileged, client_site, |
| 324 service.Receive()); |
| 325 if (SUCCEEDED(service_hr) && service) { |
| 326 // Does the host want privileged mode? |
| 327 boolean wants_privileged = false; |
| 328 service_hr = service->GetWantsPrivileged(&wants_privileged); |
| 329 |
| 330 if (SUCCEEDED(service_hr) && wants_privileged) |
| 331 is_privileged_ = true; |
| 332 } |
| 333 |
| 334 std::wstring chrome_extra_arguments; |
| 335 std::wstring profile_name(GetHostProcessName(false)); |
| 336 if (is_privileged_) { |
| 337 // Does the host want to provide extra arguments? |
| 338 ScopedBstr extra_arguments_arg; |
| 339 service_hr = service->GetChromeExtraArguments( |
| 340 extra_arguments_arg.Receive()); |
| 341 if (S_OK == service_hr && extra_arguments_arg) |
| 342 chrome_extra_arguments.assign(extra_arguments_arg, |
| 343 extra_arguments_arg.Length()); |
| 344 |
| 345 ScopedBstr profile_name_arg; |
| 346 service_hr = service->GetChromeProfileName(profile_name_arg.Receive()); |
| 347 if (S_OK == service_hr && profile_name_arg) |
| 348 profile_name.assign(profile_name_arg, profile_name_arg.Length()); |
| 349 } |
| 350 |
| 351 if (!InitializeAutomation(profile_name, chrome_extra_arguments, |
| 352 IsIEInPrivate())) { |
| 353 return E_FAIL; |
| 354 } |
| 355 } |
| 356 |
| 357 return hr; |
| 358 } |
| 359 |
| 360 HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem, |
| 361 BSTR* id) { |
| 362 DCHECK(object_elem != NULL); |
| 363 DCHECK(id != NULL); |
| 364 |
| 365 HRESULT hr = E_FAIL; |
| 366 if (object_elem) { |
| 367 ScopedComPtr<IHTMLElement> elem; |
| 368 hr = elem.QueryFrom(object_elem); |
| 369 if (elem) { |
| 370 hr = elem->get_id(id); |
| 371 } |
| 372 } |
| 373 |
| 374 return hr; |
| 375 } |
| 376 |
| 377 HRESULT ChromeFrameActivex::GetObjectElement(IHTMLObjectElement** element) { |
| 378 DCHECK(m_spClientSite); |
| 379 if (!m_spClientSite) |
| 380 return E_UNEXPECTED; |
| 381 |
| 382 ScopedComPtr<IOleControlSite> site; |
| 383 HRESULT hr = site.QueryFrom(m_spClientSite); |
| 384 if (site) { |
| 385 ScopedComPtr<IDispatch> disp; |
| 386 hr = site->GetExtendedControl(disp.Receive()); |
| 387 if (disp) { |
| 388 hr = disp.QueryInterface(element); |
| 389 } else { |
| 390 DCHECK(FAILED(hr)); |
| 391 } |
| 392 } |
| 393 |
| 394 return hr; |
| 395 } |
| 396 |
| 397 HRESULT ChromeFrameActivex::CreateScriptBlockForEvent( |
| 398 IHTMLElement2* insert_after, BSTR instance_id, BSTR script, |
| 399 BSTR event_name) { |
| 400 DCHECK(insert_after); |
| 401 DCHECK(::SysStringLen(event_name) > 0); // should always have this |
| 402 |
| 403 // This might be 0 if not specified in the HTML document. |
| 404 if (!::SysStringLen(instance_id)) { |
| 405 // TODO(tommi): Should we give ourselves an ID if this happens? |
| 406 NOTREACHED() << "Need to handle this"; |
| 407 return E_INVALIDARG; |
| 408 } |
| 409 |
| 410 ScopedComPtr<IHTMLDocument2> document; |
| 411 HRESULT hr = GetContainingDocument(document.Receive()); |
| 412 if (SUCCEEDED(hr)) { |
| 413 ScopedComPtr<IHTMLElement> element, new_element; |
| 414 document->createElement(StackBstr(L"script"), element.Receive()); |
| 415 if (element) { |
| 416 ScopedComPtr<IHTMLScriptElement> script_element; |
| 417 if (SUCCEEDED(hr = script_element.QueryFrom(element))) { |
| 418 script_element->put_htmlFor(instance_id); |
| 419 script_element->put_event(event_name); |
| 420 script_element->put_text(script); |
| 421 |
| 422 hr = insert_after->insertAdjacentElement(StackBstr(L"afterEnd"), |
| 423 element, |
| 424 new_element.Receive()); |
| 425 } |
| 426 } |
| 427 } |
| 428 |
| 429 return hr; |
| 430 } |
| 431 |
| 432 HRESULT ChromeFrameActivex::CreateDomEvent(const std::string& event_type, |
| 433 const std::string& data, |
| 434 const std::string& origin, |
| 435 IDispatch** event) { |
| 436 DCHECK(event_type.length() > 0); |
| 437 DCHECK(event != NULL); |
| 438 |
| 439 CComObject<ComMessageEvent>* ev = NULL; |
| 440 HRESULT hr = CComObject<ComMessageEvent>::CreateInstance(&ev); |
| 441 if (SUCCEEDED(hr)) { |
| 442 ev->AddRef(); |
| 443 |
| 444 ScopedComPtr<IOleContainer> container; |
| 445 m_spClientSite->GetContainer(container.Receive()); |
| 446 if (ev->Initialize(container, data, origin, event_type)) { |
| 447 *event = ev; |
| 448 } else { |
| 449 NOTREACHED() << "event->Initialize"; |
| 450 ev->Release(); |
| 451 hr = E_UNEXPECTED; |
| 452 } |
| 453 } |
| 454 |
| 455 return hr; |
| 456 } |
| 457 |
| 458 void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, |
| 459 const std::string& arg) { |
| 460 if (handlers.size()) { |
| 461 ScopedComPtr<IDispatch> event; |
| 462 if (SUCCEEDED(CreateDomEvent("event", arg, "", event.Receive()))) { |
| 463 FireEvent(handlers, event); |
| 464 } |
| 465 } |
| 466 } |
| 467 |
| 468 void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, |
| 469 IDispatch* event) { |
| 470 DCHECK(event != NULL); |
| 471 VARIANT arg = { VT_DISPATCH }; |
| 472 arg.pdispVal = event; |
| 473 DISPPARAMS params = { &arg, NULL, 1, 0 }; |
| 474 for (EventHandlers::const_iterator it = handlers.begin(); |
| 475 it != handlers.end(); |
| 476 ++it) { |
| 477 HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT, |
| 478 DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); |
| 479 // 0x80020101 == SCRIPT_E_REPORTED. |
| 480 // When the script we're invoking has an error, we get this error back. |
| 481 DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) |
| 482 << StringPrintf(L"Failed to invoke script: 0x%08X", hr); |
| 483 } |
| 484 } |
| 485 |
| 486 void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, |
| 487 IDispatch* event, BSTR target) { |
| 488 DCHECK(event != NULL); |
| 489 // Arguments in reverse order to event handler function declaration, |
| 490 // because that's what DISPPARAMS requires. |
| 491 VARIANT args[2] = { { VT_BSTR }, { VT_DISPATCH }, }; |
| 492 args[0].bstrVal = target; |
| 493 args[1].pdispVal = event; |
| 494 DISPPARAMS params = { args, NULL, arraysize(args), 0 }; |
| 495 for (EventHandlers::const_iterator it = handlers.begin(); |
| 496 it != handlers.end(); |
| 497 ++it) { |
| 498 HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT, |
| 499 DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); |
| 500 // 0x80020101 == SCRIPT_E_REPORTED. |
| 501 // When the script we're invoking has an error, we get this error back. |
| 502 DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) |
| 503 << StringPrintf(L"Failed to invoke script: 0x%08X", hr); |
| 504 } |
| 505 } |
OLD | NEW |