// Copyright (c) 2012 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. #ifndef CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ #define CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ #include <atlbase.h> #include <atlcom.h> #include <atlctl.h> #include <exdisp.h> #include <wininet.h> #include <shdeprecated.h> // for IBrowserService2 #include <shlguid.h> #include <set> #include <string> #include <vector> #include "base/metrics/histogram.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/scoped_bstr.h" #include "base/win/scoped_comptr.h" #include "base/win/scoped_variant.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/common/url_constants.h" #include "chrome_frame/chrome_frame_plugin.h" #include "chrome_frame/chrome_tab.h" #include "chrome_frame/com_message_event.h" #include "chrome_frame/com_type_info_holder.h" #include "chrome_frame/simple_resource_loader.h" #include "chrome_frame/urlmon_url_request.h" #include "chrome_frame/urlmon_url_request_private.h" #include "chrome_frame/utils.h" #include "grit/chrome_frame_resources.h" #include "grit/generated_resources.h" #include "net/cookies/cookie_monster.h" // Connection point class to support firing IChromeFrameEvents (dispinterface). template<class T> class ATL_NO_VTABLE ProxyDIChromeFrameEvents : public IConnectionPointImpl<T, &DIID_DIChromeFrameEvents> { public: void FireMethodWithParams(ChromeFrameEventDispId dispid, const VARIANT* params, size_t num_params) { T* me = static_cast<T*>(this); // We need to copy the whole vector and AddRef the sinks in case // some would get disconnected as we fire methods. Note that this is not // a threading issue, but a re-entrance issue, because the connection // can be affected by the implementation of the sinks receiving the event. me->Lock(); std::vector< base::win::ScopedComPtr<IUnknown> > sink_array( m_vec.GetSize()); for (int connection = 0; connection < m_vec.GetSize(); ++connection) sink_array[connection] = m_vec.GetAt(connection); me->Unlock(); for (size_t sink = 0; sink < sink_array.size(); ++sink) { DIChromeFrameEvents* events = static_cast<DIChromeFrameEvents*>(sink_array[sink].get()); if (events) { DISPPARAMS disp_params = { const_cast<VARIANT*>(params), NULL, num_params, 0}; HRESULT hr = events->Invoke(static_cast<DISPID>(dispid), DIID_DIChromeFrameEvents, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp_params, NULL, NULL, NULL); DLOG_IF(ERROR, FAILED(hr)) << "invoke(" << dispid << ") failed" << base::StringPrintf("0x%08X", hr); } } } void FireMethodWithParam(ChromeFrameEventDispId dispid, const VARIANT& param) { FireMethodWithParams(dispid, ¶m, 1); } void Fire_onload(IDispatch* event) { VARIANT var = { VT_DISPATCH }; var.pdispVal = event; FireMethodWithParam(CF_EVENT_DISPID_ONLOAD, var); } void Fire_onloaderror(IDispatch* event) { VARIANT var = { VT_DISPATCH }; var.pdispVal = event; FireMethodWithParam(CF_EVENT_DISPID_ONLOADERROR, var); } void Fire_onmessage(IDispatch* event) { VARIANT var = { VT_DISPATCH }; var.pdispVal = event; FireMethodWithParam(CF_EVENT_DISPID_ONMESSAGE, var); } void Fire_onreadystatechanged(long readystate) { // NOLINT VARIANT var = { VT_I4 }; var.lVal = readystate; FireMethodWithParam(CF_EVENT_DISPID_ONREADYSTATECHANGED, var); } void Fire_onprivatemessage(IDispatch* event, BSTR target) { // Arguments in reverse order to the function declaration, because // that's what DISPPARAMS requires. VARIANT args[2] = { { VT_BSTR, }, {VT_DISPATCH, } }; args[0].bstrVal = target; args[1].pdispVal = event; FireMethodWithParams(CF_EVENT_DISPID_ONPRIVATEMESSAGE, args, arraysize(args)); } void Fire_onchannelerror() { // NOLINT FireMethodWithParams(CF_EVENT_DISPID_ONCHANNELERROR, NULL, 0); } void Fire_onclose() { // NOLINT FireMethodWithParams(CF_EVENT_DISPID_ONCLOSE, NULL, 0); } }; extern bool g_first_launch_by_process_; namespace chrome_frame { // Implemented outside this file so that the header doesn't include // automation_messages.h. std::string ActiveXCreateUrl(const GURL& parsed_url, const AttachExternalTabParams& params); int GetDisposition(const AttachExternalTabParams& params); void GetMiniContextMenuData(UINT cmd, const MiniContextMenuParams& params, GURL* referrer, GURL* url); } // namespace chrome_frame // Common implementation for ActiveX and Active Document template <class T, const CLSID& class_id> class ATL_NO_VTABLE ChromeFrameActivexBase : // NOLINT public CComObjectRootEx<CComMultiThreadModel>, public IOleControlImpl<T>, public IOleObjectImpl<T>, public IOleInPlaceActiveObjectImpl<T>, public IViewObjectExImpl<T>, public IOleInPlaceObjectWindowlessImpl<T>, public ISupportErrorInfo, public IQuickActivateImpl<T>, public com_util::IProvideClassInfo2Impl<class_id, DIID_DIChromeFrameEvents>, public com_util::IDispatchImpl<IChromeFrame>, public IConnectionPointContainerImpl<T>, public ProxyDIChromeFrameEvents<T>, public IPropertyNotifySinkCP<T>, public CComCoClass<T, &class_id>, public CComControl<T>, public ChromeFramePlugin<T> { protected: typedef std::set<base::win::ScopedComPtr<IDispatch> > EventHandlers; typedef ChromeFrameActivexBase<T, class_id> BasePlugin; public: ChromeFrameActivexBase() : ready_state_(READYSTATE_UNINITIALIZED), url_fetcher_(new UrlmonUrlRequestManager()), failed_to_fetch_in_place_frame_(false), draw_sad_tab_(false) { m_bWindowOnly = TRUE; url_fetcher_->set_container(static_cast<IDispatch*>(this)); } ~ChromeFrameActivexBase() { url_fetcher_->set_container(NULL); } DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE | OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE | OLEMISC_SETCLIENTSITEFIRST) DECLARE_NOT_AGGREGATABLE(T) BEGIN_COM_MAP(ChromeFrameActivexBase) COM_INTERFACE_ENTRY(IChromeFrame) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IViewObjectEx) COM_INTERFACE_ENTRY(IViewObject2) COM_INTERFACE_ENTRY(IViewObject) COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceObject) COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) COM_INTERFACE_ENTRY(IOleControl) COM_INTERFACE_ENTRY(IOleObject) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY(IQuickActivate) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) COM_INTERFACE_ENTRY(IConnectionPointContainer) COM_INTERFACE_ENTRY_FUNC_BLIND(0, InterfaceNotSupported) END_COM_MAP() BEGIN_CONNECTION_POINT_MAP(T) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) CONNECTION_POINT_ENTRY(DIID_DIChromeFrameEvents) END_CONNECTION_POINT_MAP() BEGIN_MSG_MAP(ChromeFrameActivexBase) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_DOWNLOAD_IN_HOST, OnDownloadRequestInHost) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) CHAIN_MSG_MAP(ChromeFramePlugin<T>) CHAIN_MSG_MAP(CComControl<T>) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() // IViewObjectEx DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE) inline HRESULT IViewObject_Draw(DWORD draw_aspect, LONG index, void* aspect_info, DVTARGETDEVICE* ptd, HDC info_dc, HDC dc, LPCRECTL bounds, LPCRECTL win_bounds) { // ATL ASSERTs if dwDrawAspect is DVASPECT_DOCPRINT, so we cheat. DWORD aspect = draw_aspect; if (aspect == DVASPECT_DOCPRINT) aspect = DVASPECT_CONTENT; return CComControl<T>::IViewObject_Draw(aspect, index, aspect_info, ptd, info_dc, dc, bounds, win_bounds); } DECLARE_PROTECT_FINAL_CONSTRUCT() void SetResourceModule() { SimpleResourceLoader* loader_instance = SimpleResourceLoader::GetInstance(); DCHECK(loader_instance); HMODULE res_dll = loader_instance->GetResourceModuleHandle(); _AtlBaseModule.SetResourceInstance(res_dll); } HRESULT FinalConstruct() { SetResourceModule(); if (!Initialize()) return E_OUTOFMEMORY; // Set to true if this is the first launch by this process. // Used to perform one time tasks. if (g_first_launch_by_process_) { g_first_launch_by_process_ = false; UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.IEVersion", GetIEVersion(), IE_INVALID, IE_10, IE_10 + 1); } return S_OK; } void FinalRelease() { Uninitialize(); } void ResetUrlRequestManager() { url_fetcher_.reset(new UrlmonUrlRequestManager()); } static HRESULT WINAPI InterfaceNotSupported(void* pv, REFIID riid, void** ppv, DWORD dw) { #ifndef NDEBUG wchar_t buffer[64] = {0}; ::StringFromGUID2(riid, buffer, arraysize(buffer)); DVLOG(1) << "E_NOINTERFACE: " << buffer; #endif return E_NOINTERFACE; } // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) { static const IID* interfaces[] = { &IID_IChromeFrame, &IID_IDispatch }; for (int i = 0; i < arraysize(interfaces); ++i) { if (InlineIsEqualGUID(*interfaces[i], riid)) return S_OK; } return S_FALSE; } // Called to draw our control when chrome hasn't been initialized. virtual HRESULT OnDraw(ATL_DRAWINFO& draw_info) { // NOLINT if (NULL == draw_info.prcBounds) { NOTREACHED(); return E_FAIL; } if (draw_sad_tab_) { // TODO(tommi): Draw a proper sad tab. RECT rc = {0}; if (draw_info.prcBounds) { rc.top = draw_info.prcBounds->top; rc.bottom = draw_info.prcBounds->bottom; rc.left = draw_info.prcBounds->left; rc.right = draw_info.prcBounds->right; } else { GetClientRect(&rc); } ::DrawTextA(draw_info.hdcDraw, ":-(", -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } else { // Don't draw anything. } return S_OK; } // Used to setup the document_url_ member needed for completing navigation. // Create external tab (possibly in incognito mode). HRESULT IOleObject_SetClientSite(IOleClientSite* client_site) { // If we currently have a document site pointer, release it. doc_site_.Release(); if (client_site) { doc_site_.QueryFrom(client_site); } if (client_site == NULL) { in_place_frame_.Release(); } return CComControlBase::IOleObject_SetClientSite(client_site); } bool HandleContextMenuCommand(UINT cmd, const MiniContextMenuParams& params) { if (cmd == IDC_ABOUT_CHROME_FRAME) { int tab_handle = automation_client_->tab()->handle(); HostNavigate(GURL("about:version"), GURL(), NEW_WINDOW); return true; } else { switch (cmd) { case IDS_CONTENT_CONTEXT_SAVEAUDIOAS: case IDS_CONTENT_CONTEXT_SAVEVIDEOAS: case IDS_CONTENT_CONTEXT_SAVEIMAGEAS: case IDS_CONTENT_CONTEXT_SAVELINKAS: { GURL referrer, url; chrome_frame::GetMiniContextMenuData(cmd, params, &referrer, &url); DoFileDownloadInIE(UTF8ToWide(url.spec()).c_str()); return true; } case IDC_PRINT: { automation_client_->PrintTab(); return true; } } } return false; } // Should connections initiated by this class try to block // responses served with the X-Frame-Options header? // ActiveX controls genereally will want to do this, // returning true, while true top-level documents // (ActiveDocument servers) will not. Your specialization // of this template should implement this method based on how // it "feels" from a security perspective. If it's hosted in another // scriptable document, return true, else false. // // The base implementation returns true unless we are in privileged // mode, in which case we always trust our container so we return false. bool is_frame_busting_enabled() const { return !is_privileged(); } static void BringWebBrowserWindowToTop(IWebBrowser2* web_browser2) { DCHECK(web_browser2); if (web_browser2) { web_browser2->put_Visible(VARIANT_TRUE); HWND ie_window = NULL; web_browser2->get_HWND(reinterpret_cast<long*>(&ie_window)); ::BringWindowToTop(ie_window); } } protected: virtual void GetProfilePath(const std::wstring& profile_name, base::FilePath* profile_path) { bool is_IE = (lstrcmpi(profile_name.c_str(), kIexploreProfileName) == 0) || (lstrcmpi(profile_name.c_str(), kRundllProfileName) == 0); // Browsers without IDeleteBrowsingHistory in non-priv mode // have their profiles moved into "Temporary Internet Files". if (is_IE && GetIEVersion() < IE_8) { *profile_path = GetIETemporaryFilesFolder(); *profile_path = profile_path->Append(L"Google Chrome Frame"); } else { ChromeFramePlugin::GetProfilePath(profile_name, profile_path); } DVLOG(1) << __FUNCTION__ << ": " << profile_path->value(); } void OnLoad(const GURL& url) { if (ready_state_ < READYSTATE_COMPLETE) { ready_state_ = READYSTATE_COMPLETE; FireOnChanged(DISPID_READYSTATE); } HRESULT hr = InvokeScriptFunction(onload_handler_, url.spec()); } void OnLoadFailed(int error_code, const std::string& url) { HRESULT hr = InvokeScriptFunction(onerror_handler_, url); } void OnMessageFromChromeFrame(const std::string& message, const std::string& origin, const std::string& target) { base::win::ScopedComPtr<IDispatch> message_event; if (SUCCEEDED(CreateDomEvent("message", message, origin, message_event.Receive()))) { base::win::ScopedVariant event_var; event_var.Set(static_cast<IDispatch*>(message_event)); InvokeScriptFunction(onmessage_handler_, event_var.AsInput()); } } virtual void OnTabbedOut(bool reverse) { DCHECK(m_bInPlaceActive); HWND parent = ::GetParent(m_hWnd); ::SetFocus(parent); base::win::ScopedComPtr<IOleControlSite> control_site; control_site.QueryFrom(m_spClientSite); if (control_site) control_site->OnFocus(FALSE); } virtual void OnOpenURL(const GURL& url_to_open, const GURL& referrer, int open_disposition) { HostNavigate(url_to_open, referrer, open_disposition); } // Called when Chrome has decided that a request needs to be treated as a // download. The caller will be the UrlRequest worker thread. // The worker thread will block while we process the request and take // ownership of the request object. // There's room for improvement here and also see todo below. LPARAM OnDownloadRequestInHost(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { ChromeFrameUrl cf_url; cf_url.Parse(UTF8ToWide(GetDocumentUrl())); // Always issue the download request in a new window to ensure that the // currently loaded ChromeFrame document does not inadvartently see an // unload request. This runs javascript unload handlers on the page which // renders the page non functional. VARIANT flags = { VT_I4 }; V_I4(&flags) = navNoHistory; if (!cf_url.attach_to_external_tab()) V_I4(&flags) |= navOpenInNewWindow; DownloadInHostParams* download_params = reinterpret_cast<DownloadInHostParams*>(wparam); DCHECK(download_params); // TODO(tommi): It looks like we might have to switch the request object // into a pass-through request object and serve up any thus far received // content and headers to IE in order to prevent what can currently happen // which is reissuing requests and turning POST into GET. if (download_params->moniker) { NavigateBrowserToMoniker( doc_site_, download_params->moniker, UTF8ToWide(download_params->request_headers).c_str(), download_params->bind_ctx, NULL, download_params->post_data, &flags); } delete download_params; return TRUE; } virtual void OnAttachExternalTab(const AttachExternalTabParams& params) { GURL current_url(static_cast<BSTR>(url_)); std::string url = chrome_frame::ActiveXCreateUrl(current_url, params); // Pass the current document url as the referrer for the new navigation. HostNavigate(GURL(url), current_url, chrome_frame::GetDisposition(params)); } virtual void OnHandleContextMenu(const ContextMenuModel& menu_model, int align_flags, const MiniContextMenuParams& params) { scoped_refptr<BasePlugin> ref(this); ChromeFramePlugin<T>::OnHandleContextMenu(menu_model, align_flags, params); } LRESULT OnCreate(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { // NO_LINT ModifyStyle(0, WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0); url_fetcher_->put_notification_window(m_hWnd); if (automation_client_.get()) { automation_client_->SetParentWindow(m_hWnd); } else { NOTREACHED() << "No automation server"; return -1; } // Only fire the 'interactive' ready state if we aren't there already. if (ready_state_ < READYSTATE_INTERACTIVE) { ready_state_ = READYSTATE_INTERACTIVE; FireOnChanged(DISPID_READYSTATE); } return 0; } LRESULT OnDestroy(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { // NO_LINT DVLOG(1) << __FUNCTION__; return 0; } // ChromeFrameDelegate override virtual void OnAutomationServerReady() { draw_sad_tab_ = false; ChromeFramePlugin<T>::OnAutomationServerReady(); ready_state_ = READYSTATE_COMPLETE; FireOnChanged(DISPID_READYSTATE); } // ChromeFrameDelegate override virtual void OnAutomationServerLaunchFailed( AutomationLaunchResult reason, const std::string& server_version) { DVLOG(1) << __FUNCTION__; if (reason == AUTOMATION_SERVER_CRASHED) draw_sad_tab_ = true; ready_state_ = READYSTATE_UNINITIALIZED; FireOnChanged(DISPID_READYSTATE); } virtual void OnCloseTab() { Fire_onclose(); } // Overridden to take advantage of readystate prop changes and send those // to potential listeners. HRESULT FireOnChanged(DISPID dispid) { if (dispid == DISPID_READYSTATE) { Fire_onreadystatechanged(ready_state_); } return __super::FireOnChanged(dispid); } // IChromeFrame // Property getter/setters for the src attribute, which contains a URL. // The ChromeFrameActivex control initiates navigation to this URL // when instantiated. STDMETHOD(get_src)(BSTR* src) { if (NULL == src) { return E_POINTER; } *src = SysAllocString(url_); return S_OK; } STDMETHOD(put_src)(BSTR src) { if (src == NULL) return E_INVALIDARG; // Switch the src to UTF8 and try to expand to full URL std::string src_utf8; WideToUTF8(src, SysStringLen(src), &src_utf8); std::string full_url = ResolveURL(GetDocumentUrl(), src_utf8); // We can initiate navigation here even if ready_state is not complete. // We do not have to set proxy, and AutomationClient will take care // of navigation just after CreateExternalTab is done. if (!automation_client_->InitiateNavigation(full_url, GetDocumentUrl(), this)) { // TODO(robertshield): Make InitiateNavigation return more useful // error information. return E_INVALIDARG; } // Save full URL in BSTR member url_.Reset(::SysAllocString(UTF8ToWide(full_url).c_str())); return S_OK; } STDMETHOD(get_onload)(VARIANT* onload_handler) { if (NULL == onload_handler) return E_INVALIDARG; *onload_handler = onload_handler_.Copy(); return S_OK; } // Property setter for the onload attribute, which contains a // javascript function to be invoked on successful navigation. STDMETHOD(put_onload)(VARIANT onload_handler) { if (V_VT(&onload_handler) != VT_DISPATCH) { DLOG(WARNING) << "Invalid onload handler type: " << onload_handler.vt << " specified"; return E_INVALIDARG; } onload_handler_ = onload_handler; return S_OK; } // Property getter/setters for the onloaderror attribute, which contains a // javascript function to be invoked on navigation failure. STDMETHOD(get_onloaderror)(VARIANT* onerror_handler) { if (NULL == onerror_handler) return E_INVALIDARG; *onerror_handler = onerror_handler_.Copy(); return S_OK; } STDMETHOD(put_onloaderror)(VARIANT onerror_handler) { if (V_VT(&onerror_handler) != VT_DISPATCH) { DLOG(WARNING) << "Invalid onloaderror handler type: " << onerror_handler.vt << " specified"; return E_INVALIDARG; } onerror_handler_ = onerror_handler; return S_OK; } // Property getter/setters for the onmessage attribute, which contains a // javascript function to be invoked when we receive a message from the // chrome frame. STDMETHOD(put_onmessage)(VARIANT onmessage_handler) { if (V_VT(&onmessage_handler) != VT_DISPATCH) { DLOG(WARNING) << "Invalid onmessage handler type: " << onmessage_handler.vt << " specified"; return E_INVALIDARG; } onmessage_handler_ = onmessage_handler; return S_OK; } STDMETHOD(get_onmessage)(VARIANT* onmessage_handler) { if (NULL == onmessage_handler) return E_INVALIDARG; *onmessage_handler = onmessage_handler_.Copy(); return S_OK; } STDMETHOD(get_readyState)(long* ready_state) { // NOLINT DVLOG(1) << __FUNCTION__; DCHECK(ready_state); if (!ready_state) return E_INVALIDARG; *ready_state = ready_state_; return S_OK; } // Property getter/setters for use_chrome_network flag. This flag // indicates if chrome network stack is to be used for fetching // network requests. STDMETHOD(get_useChromeNetwork)(VARIANT_BOOL* use_chrome_network) { if (!use_chrome_network) return E_INVALIDARG; *use_chrome_network = automation_client_->use_chrome_network() ? VARIANT_TRUE : VARIANT_FALSE; return S_OK; } STDMETHOD(put_useChromeNetwork)(VARIANT_BOOL use_chrome_network) { if (!is_privileged()) { DLOG(ERROR) << "Attempt to set useChromeNetwork in non-privileged mode"; return E_ACCESSDENIED; } automation_client_->set_use_chrome_network( (VARIANT_FALSE != use_chrome_network)); return S_OK; } // Posts a message to the chrome frame. STDMETHOD(postMessage)(BSTR message, VARIANT target) { if (NULL == message) { return E_INVALIDARG; } if (!automation_client_.get()) return E_FAIL; std::string utf8_target; if (target.vt == VT_BSTR) { int len = ::SysStringLen(target.bstrVal); if (len == 1 && target.bstrVal[0] == L'*') { utf8_target = "*"; } else { GURL resolved(target.bstrVal); if (!resolved.is_valid()) { Error(L"Unable to parse the specified target URL."); return E_INVALIDARG; } utf8_target = resolved.spec(); } } else { utf8_target = "*"; } std::string utf8_message; WideToUTF8(message, ::SysStringLen(message), &utf8_message); GURL url(GURL(document_url_).GetOrigin()); std::string origin(url.is_empty() ? "null" : url.spec()); if (!automation_client_->ForwardMessageFromExternalHost(utf8_message, origin, utf8_target)) { Error(L"Failed to post message to chrome frame"); return E_FAIL; } return S_OK; } STDMETHOD(addEventListener)(BSTR event_type, IDispatch* listener, VARIANT use_capture) { EventHandlers* handlers = NULL; HRESULT hr = GetHandlersForEvent(event_type, &handlers); if (FAILED(hr)) return hr; DCHECK(handlers != NULL); handlers->insert(base::win::ScopedComPtr<IDispatch>(listener)); return hr; } STDMETHOD(removeEventListener)(BSTR event_type, IDispatch* listener, VARIANT use_capture) { EventHandlers* handlers = NULL; HRESULT hr = GetHandlersForEvent(event_type, &handlers); if (FAILED(hr)) return hr; DCHECK(handlers != NULL); handlers->erase(base::win::ScopedComPtr<IDispatch>(listener)); return hr; } STDMETHOD(get_version)(BSTR* version) { if (!automation_client_.get()) { NOTREACHED(); return E_FAIL; } if (version == NULL) { return E_INVALIDARG; } *version = SysAllocString(automation_client_->GetVersion().c_str()); return S_OK; } STDMETHOD(postPrivateMessage)(BSTR message, BSTR origin, BSTR target) { if (NULL == message) return E_INVALIDARG; if (!is_privileged()) { DLOG(ERROR) << "Attempt to postPrivateMessage in non-privileged mode"; return E_ACCESSDENIED; } DCHECK(automation_client_.get()); std::string utf8_message, utf8_origin, utf8_target; WideToUTF8(message, ::SysStringLen(message), &utf8_message); WideToUTF8(origin, ::SysStringLen(origin), &utf8_origin); WideToUTF8(target, ::SysStringLen(target), &utf8_target); if (!automation_client_->ForwardMessageFromExternalHost(utf8_message, utf8_origin, utf8_target)) { Error(L"Failed to post message to chrome frame"); return E_FAIL; } return S_OK; } STDMETHOD(installExtension)(BSTR crx_path) { NOTREACHED(); // Deprecated. return E_NOTIMPL; } STDMETHOD(loadExtension)(BSTR path) { NOTREACHED(); // Deprecated. return E_NOTIMPL; } STDMETHOD(getEnabledExtensions)() { NOTREACHED(); // Deprecated. return E_NOTIMPL; } STDMETHOD(registerBhoIfNeeded)() { return E_NOTIMPL; } // Returns the vector of event handlers for a given event (e.g. "load"). // If the event type isn't recognized, the function fills in a descriptive // error (IErrorInfo) and returns E_INVALIDARG. HRESULT GetHandlersForEvent(BSTR event_type, EventHandlers** handlers) { DCHECK(handlers != NULL); // TODO(tommi): make these if() statements data-driven. HRESULT hr = S_OK; const wchar_t* event_type_end = event_type + ::SysStringLen(event_type); if (LowerCaseEqualsASCII(event_type, event_type_end, "message")) { *handlers = &onmessage_; } else if (LowerCaseEqualsASCII(event_type, event_type_end, "load")) { *handlers = &onload_; } else if (LowerCaseEqualsASCII(event_type, event_type_end, "loaderror")) { *handlers = &onloaderror_; } else if (LowerCaseEqualsASCII(event_type, event_type_end, "readystatechanged")) { *handlers = &onreadystatechanged_; } else if (LowerCaseEqualsASCII(event_type, event_type_end, "privatemessage")) { // This event handler is only available in privileged mode. if (is_privileged()) { *handlers = &onprivatemessage_; } else { Error("Event type 'privatemessage' is privileged"); hr = E_ACCESSDENIED; } } else if (LowerCaseEqualsASCII(event_type, event_type_end, "extensionready")) { // This event handler is only available in privileged mode. if (is_privileged()) { *handlers = &onextensionready_; } else { Error("Event type 'extensionready' is privileged"); hr = E_ACCESSDENIED; } } else { Error(base::StringPrintf( "Event type '%ls' not found", event_type).c_str()); hr = E_INVALIDARG; } return hr; } // Creates a new event object that supports the |data| property. // Note: you should supply an empty string for |origin| unless you're // creating a "message" event. HRESULT CreateDomEvent(const std::string& event_type, const std::string& data, const std::string& origin, IDispatch** event) { DCHECK(event_type.length() > 0); // NOLINT DCHECK(event != NULL); CComObject<ComMessageEvent>* ev = NULL; HRESULT hr = CComObject<ComMessageEvent>::CreateInstance(&ev); if (SUCCEEDED(hr)) { ev->AddRef(); base::win::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; } // Helper function to execute a function on a script IDispatch interface. HRESULT InvokeScriptFunction(const VARIANT& script_object, const std::string& param) { base::win::ScopedVariant script_arg(UTF8ToWide(param.c_str()).c_str()); return InvokeScriptFunction(script_object, script_arg.AsInput()); } HRESULT InvokeScriptFunction(const VARIANT& script_object, VARIANT* param) { return InvokeScriptFunction(script_object, param, 1); } HRESULT InvokeScriptFunction(const VARIANT& script_object, VARIANT* params, int param_count) { DCHECK_GE(param_count, 0); DCHECK(params); if (V_VT(&script_object) != VT_DISPATCH || script_object.pdispVal == NULL) { return S_FALSE; } CComPtr<IDispatch> script(script_object.pdispVal); HRESULT hr = script.InvokeN(static_cast<DISPID>(DISPID_VALUE), params, param_count); // 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; } // Gives the browser a chance to handle an accelerator that was // sent to the out of proc chromium instance. // Returns S_OK iff the accelerator was handled by the browser. HRESULT AllowFrameToTranslateAccelerator(const MSG& msg) { static const int kMayTranslateAcceleratorOffset = 0x5c; // Although IBrowserService2 is officially deprecated, it's still alive // and well in IE7 and earlier. We have to use it here to correctly give // the browser a chance to handle keyboard shortcuts. // This happens automatically for activex components that have windows that // belong to the current thread. In that circumstance IE owns the message // loop and can walk the line of components allowing each participant the // chance to handle the keystroke and eventually falls back to // v_MayTranslateAccelerator. However in our case, the message loop is // owned by the out-of-proc chromium instance so IE doesn't have a chance to // fall back on its default behavior. Instead we give IE a chance to // handle the shortcut here. MSG accel_message = msg; accel_message.hwnd = ::GetParent(m_hWnd); HRESULT hr = S_FALSE; base::win::ScopedComPtr<IBrowserService2> bs2; // For non-IE containers, we use the standard IOleInPlaceFrame contract // (which IE does not support). For IE, we try to use IBrowserService2, // but need special handling for IE8 (see below). // // We try to cache an IOleInPlaceFrame for our site. If we fail, we don't // retry, and we fall back to the IBrowserService2 and PostMessage // approaches below. if (!in_place_frame_ && !failed_to_fetch_in_place_frame_) { base::win::ScopedComPtr<IOleInPlaceUIWindow> dummy_ui_window; RECT dummy_pos_rect = {0}; RECT dummy_clip_rect = {0}; OLEINPLACEFRAMEINFO dummy_frame_info = {0}; if (!m_spInPlaceSite || FAILED(m_spInPlaceSite->GetWindowContext(in_place_frame_.Receive(), dummy_ui_window.Receive(), &dummy_pos_rect, &dummy_clip_rect, &dummy_frame_info))) { failed_to_fetch_in_place_frame_ = true; } } // The IBrowserService2 code below (second conditional) explicitly checks // for whether the IBrowserService2::v_MayTranslateAccelerator function is // valid. On IE8 there is one vtable ieframe!c_ImpostorBrowserService2Vtbl // where this function entry is NULL which leads to a crash. We don't know // under what circumstances this vtable is actually used though. if (in_place_frame_) { hr = in_place_frame_->TranslateAccelerator(&accel_message, 0); } else if (S_OK == DoQueryService( SID_STopLevelBrowser, m_spInPlaceSite, bs2.Receive()) && bs2.get() && *(*(reinterpret_cast<void***>(bs2.get())) + kMayTranslateAcceleratorOffset)) { hr = bs2->v_MayTranslateAccelerator(&accel_message); } else { // IE8 doesn't support IBrowserService2 unless you enable a special, // undocumented flag with CoInternetSetFeatureEnabled and even then, // the object you get back implements only a couple of methods of // that interface... all the other entries in the vtable are NULL. // In addition, the class that implements it is called // ImpostorBrowserService2 :) // IE8 does have a new interface though, presumably called // ITabBrowserService or something that can be abbreviated to TBS. // That interface has a method, TranslateAcceleratorTBS that does // call the root MayTranslateAccelerator function, but alas the // first argument to MayTranslateAccelerator is hard coded to FALSE // which means that global accelerators are not handled and we're // out of luck. // A third thing that's notable with regards to IE8 is that // none of the *MayTranslate* functions exist in a vtable inside // ieframe.dll. I checked this by scanning for the address of // those functions inside the dll and found none, which means that // all calls to those functions are relative. // So, for IE8 in certain cases, and for other containers that may // support neither IOleInPlaceFrame or IBrowserService2 our approach // is very simple. Just post the message to our parent window and IE // will pick it up if it's an accelerator. We won't know for sure if // the browser handled the keystroke or not. ::PostMessage(accel_message.hwnd, accel_message.message, accel_message.wParam, accel_message.lParam); } return hr; } virtual void OnAcceleratorPressed(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); DVLOG(1) << __FUNCTION__ << " browser response: " << base::StringPrintf("0x%08x", hr); if (hr != S_OK) { // The WM_SYSKEYDOWN/WM_SYSKEYUP messages are not processed by the // IOleControlSite and IBrowserService2::v_MayTranslateAccelerator // implementations. We need to understand this better. That is for // another day. For now we just post these messages back to the parent // which forwards it off to the frame. This should not cause major // grief for Chrome as it does not need to handle WM_SYSKEY* messages in // in ChromeFrame mode. // TODO(iyengar) // Understand and fix WM_SYSCHAR handling // We should probably unify the accelerator handling for the active // document and the activex. if (accel_message.message == WM_SYSCHAR || accel_message.message == WM_SYSKEYDOWN || accel_message.message == WM_SYSKEYUP) { ::PostMessage(GetParent(), accel_message.message, accel_message.wParam, accel_message.lParam); return; } } // 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); } } } protected: void HostNavigate(const GURL& url_to_open, const GURL& referrer, int open_disposition) { base::win::ScopedComPtr<IWebBrowser2> web_browser2; DoQueryService(SID_SWebBrowserApp, m_spClientSite, web_browser2.Receive()); if (!web_browser2) { NOTREACHED() << "Failed to retrieve IWebBrowser2 interface"; return; } base::win::ScopedVariant url; // Check to see if the URL uses a "view-source:" prefix, if so, open it // using chrome frame full tab mode by using 'cf:' protocol handler. // Also change the disposition to NEW_WINDOW since IE6 doesn't have tabs. if (url_to_open.has_scheme() && (url_to_open.SchemeIs(content::kViewSourceScheme) || url_to_open.SchemeIs(chrome::kAboutScheme))) { std::wstring chrome_url; chrome_url.append(kChromeProtocolPrefix); chrome_url.append(UTF8ToWide(url_to_open.spec())); url.Set(chrome_url.c_str()); open_disposition = NEW_WINDOW; } else { url.Set(UTF8ToWide(url_to_open.spec()).c_str()); } VARIANT flags = { VT_I4 }; V_I4(&flags) = 0; IEVersion ie_version = GetIEVersion(); DCHECK(ie_version != NON_IE && ie_version != IE_UNSUPPORTED); // Since IE6 doesn't support tabs, so we just use window instead. if (ie_version == IE_6) { if (open_disposition == NEW_FOREGROUND_TAB || open_disposition == NEW_BACKGROUND_TAB || open_disposition == NEW_WINDOW || open_disposition == NEW_POPUP) { V_I4(&flags) = navOpenInNewWindow; } else if (open_disposition != CURRENT_TAB) { NOTREACHED() << "Unsupported open disposition in IE6"; } } else { switch (open_disposition) { case NEW_FOREGROUND_TAB: V_I4(&flags) = navOpenInNewTab; break; case NEW_BACKGROUND_TAB: V_I4(&flags) = navOpenInBackgroundTab; break; case NEW_WINDOW: case NEW_POPUP: V_I4(&flags) = navOpenInNewWindow; break; default: break; } } // TODO(sanjeevr): The navOpenInNewWindow flag causes IE to open this // in a new window ONLY if the user has specified // "Always open popups in a new window". Otherwise it opens in a new tab. // We need to investigate more and see if we can force IE to display the // link in a new window. MSHTML uses the below code to force an open in a // new window. But this logic also fails for us. Perhaps this flag is not // honoured if the ActiveDoc is not MSHTML. // Even the HLNF_DISABLEWINDOWRESTRICTIONS flag did not work. // Start of MSHTML-like logic. // CComQIPtr<ITargetFramePriv2> target_frame = web_browser2; // if (target_frame) { // CComPtr<IUri> uri; // CreateUri(UTF8ToWide(open_url_command->url_.spec()).c_str(), // Uri_CREATE_IE_SETTINGS, 0, &uri); // CComPtr<IBindCtx> bind_ctx; // CreateBindCtx(0, &bind_ctx); // target_frame->AggregatedNavigation2( // HLNF_TRUSTFIRSTDOWNLOAD|HLNF_OPENINNEWWINDOW, bind_ctx, NULL, // L"No_Name", uri, L""); // } // End of MSHTML-like logic VARIANT empty = base::win::ScopedVariant::kEmptyVariant; base::win::ScopedVariant http_headers; if (referrer.is_valid()) { std::wstring referrer_header = L"Referer: "; referrer_header += UTF8ToWide(referrer.spec()); referrer_header += L"\r\n\r\n"; http_headers.Set(referrer_header.c_str()); } // IE6 does not support tabs. If Chrome sent us a window open request // indicating that the navigation needs to occur in a foreground tab or // a popup window, then we need to ensure that the new window in IE6 is // brought to the foreground. if (ie_version == IE_6) { ChromeFrameUrl cf_url; cf_url.Parse(static_cast<BSTR>(url_)); if (cf_url.attach_to_external_tab() && (cf_url.disposition() == NEW_FOREGROUND_TAB || cf_url.disposition() == NEW_POPUP)) { BringWebBrowserWindowToTop(web_browser2); } } HRESULT hr = web_browser2->Navigate2(url.AsInput(), &flags, &empty, &empty, http_headers.AsInput()); // If the current window is a popup window then attempting to open a new // tab for the navigation will fail. We attempt to issue the navigation in // a new window in this case. // http://msdn.microsoft.com/en-us/library/aa752133(v=vs.85).aspx if (FAILED(hr) && V_I4(&flags) != navOpenInNewWindow) { V_I4(&flags) = navOpenInNewWindow; hr = web_browser2->Navigate2(url.AsInput(), &flags, &empty, &empty, http_headers.AsInput()); DLOG_IF(ERROR, FAILED(hr)) << "Navigate2 failed with error: " << base::StringPrintf("0x%08X", hr); } } void InitializeAutomationSettings() { static const wchar_t kHandleTopLevelRequests[] = L"HandleTopLevelRequests"; static const wchar_t kUseChromeNetworking[] = L"UseChromeNetworking"; // Query and assign the top-level-request routing, and host networking // settings from the registry. bool top_level_requests = GetConfigBool(true, kHandleTopLevelRequests); bool chrome_network = GetConfigBool(false, kUseChromeNetworking); automation_client_->set_handle_top_level_requests(top_level_requests); automation_client_->set_use_chrome_network(chrome_network); } base::win::ScopedBstr url_; base::win::ScopedComPtr<IOleDocumentSite> doc_site_; // If false, we tried but failed to fetch an IOleInPlaceFrame from our host. // Cached here so we don't try to fetch it every time if we keep failing. bool failed_to_fetch_in_place_frame_; bool draw_sad_tab_; base::win::ScopedComPtr<IOleInPlaceFrame> in_place_frame_; // For more information on the ready_state_ property see: // http://msdn.microsoft.com/en-us/library/aa768179(VS.85).aspx# READYSTATE ready_state_; // The following members contain IDispatch interfaces representing the // onload/onerror/onmessage handlers on the page. base::win::ScopedVariant onload_handler_; base::win::ScopedVariant onerror_handler_; base::win::ScopedVariant onmessage_handler_; EventHandlers onmessage_; EventHandlers onloaderror_; EventHandlers onload_; EventHandlers onreadystatechanged_; EventHandlers onprivatemessage_; EventHandlers onextensionready_; // Handle network requests when host network stack is used. Passed to the // automation client on initialization. scoped_ptr<UrlmonUrlRequestManager> url_fetcher_; }; #endif // CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_