1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com// Copyright (c) 2011 The Chromium Authors. All rights reserved. 28a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// Use of this source code is governed by a BSD-style license that can be 3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com// found in the LICENSE file. 48a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#include <windows.h> 6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#include <mshtmhst.h> 78a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com#include <urlmon.h> 88a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 9ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#include "base/win/scoped_variant.h" 108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com#include "chrome/installer/util/html_dialog.h" 118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com#pragma comment(lib, "urlmon.lib") 138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 148a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comnamespace installer { 158a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 168a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// Windows implementation of the HTML dialog class. The main danger with 178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// using the IE embedded control as a child window of a custom window is that 188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// it still contains too much browser functionality, allowing the user to do 198a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// things that are not expected of a plain dialog. ShowHTMLDialog API solves 207ffb1b21abcc7bbed5a0fc711f6dd7b9dbb4f577ctguil@chromium.org// that problem but gives us a not very customizable frame. We solve that 218a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// using hooks to end up with a robust dialog at the expense of having to do 228a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// the buttons in html itself, like so: 238a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// 248a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// <form onsubmit="submit_it(this); return false;"> 25f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com// <input name="accept" type="checkbox" /> My cool option 265119bdb952025a30f115b9c6a187173956e55097reed@android.com// <input name="submit" type="submit" value="[accept]" /> 278a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// </form> 288a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// 298a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// function submit_it(f) { 308a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// if (f.accept.checked) { 31f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com// window.returnValue = 1; <-- this matches HTML_DLG_ACCEPT 32f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com// } else { 33f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com// window.returnValue = 2; <-- this matches HTML_DLG_DECLINE 34f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com// } 3559ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com// window.close(); 3659ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com// } 37b6e161937bc890f0aa12ac5e27415d4d260ea6e0junov@chromium.org// 388a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com// Note that on the submit handler you need to set window.returnValue to one of 3959ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com// the values of DialogResult and call window.close(). 4059ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com 4159ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.comclass HTMLDialogWin : public HTMLDialog { 4259ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com public: 438a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com HTMLDialogWin(const std::wstring& url, const std::wstring& param) 442be9e8b407624fa696854b78b407b97a01dbb703reed@google.com : url_(url), param_(param) { 458cad58624bc194390b14a21d0578dfcdd6fbad6freed@google.com if (!mshtml_) 46f2b98d67dcb6fcb3120feede9c72016fc7b3ead8reed@android.com mshtml_ = LoadLibrary(L"MSHTML.DLL"); 478cad58624bc194390b14a21d0578dfcdd6fbad6freed@google.com } 4859ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com 49d3ae77965e94e0efda496f5461cbec4533cb5b16vandebo@chromium.org virtual DialogResult ShowModal(void* parent_window, 5059ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com CustomizationCallback* callback) { 51d3ae77965e94e0efda496f5461cbec4533cb5b16vandebo@chromium.org int result = HTML_DLG_DECLINE; 52ba28d03e94dc221d6a803bf2a84a420b9159255cdjsollen@google.com if (!InternalDoDialog(callback, &result)) 53a2ca41e3afdd8fad5e0e924dec029f33918e0a67djsollen@google.com return HTML_DLG_ERROR; 548a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return static_cast<DialogResult>(result); 5559ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com } 5659ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com 5759ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com virtual std::wstring GetExtraResult() { 588a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com return extra_result_; 5959ccef695cef28a74ab2ea13d5a6c9017af45402reed@google.com } 608a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com private: 625119bdb952025a30f115b9c6a187173956e55097reed@android.com bool InternalDoDialog(CustomizationCallback* callback, int* result); 638a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com static LRESULT CALLBACK MsgFilter(int code, WPARAM wParam, LPARAM lParam); 648a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com 658a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com std::wstring url_; 668a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com std::wstring param_; 678a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com static HHOOK hook_; 688a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com static HINSTANCE mshtml_; 698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com static CustomizationCallback* callback_; 70 std::wstring extra_result_; 71}; 72 73HTMLDialog* CreateNativeHTMLDialog(const std::wstring& url, 74 const std::wstring& param) { 75 return new HTMLDialogWin(url, param); 76} 77 78HHOOK HTMLDialogWin::hook_ = NULL; 79HINSTANCE HTMLDialogWin::mshtml_ = NULL; 80HTMLDialogWin::CustomizationCallback* HTMLDialogWin::callback_ = NULL; 81 82// This hook function gets called for messages bound to the windows that 83// ShowHTMLDialog creates. We tell apart the top window because it has the 84// system menu style. 85LRESULT HTMLDialogWin::MsgFilter(int code, WPARAM wParam, LPARAM lParam) { 86 static bool tweak_window = true; 87 if (lParam && tweak_window) { 88 HWND target_window = reinterpret_cast<MSG*>(lParam)->hwnd; 89 if (target_window) { 90 LONG_PTR style = ::GetWindowLongPtrW(target_window, GWL_STYLE); 91 if (style & WS_SYSMENU) { 92 tweak_window = false; 93 callback_->OnBeforeDisplay(target_window); 94 } 95 } 96 } 97 // Always call the next hook in the chain. 98 return ::CallNextHookEx(hook_, code, wParam, lParam); 99} 100 101bool HTMLDialogWin::InternalDoDialog(CustomizationCallback* callback, 102 int* result) { 103 if (!mshtml_) 104 return false; 105 SHOWHTMLDIALOGFN* show_html_dialog = 106 reinterpret_cast<SHOWHTMLDIALOGFN*>( 107 GetProcAddress(mshtml_, "ShowHTMLDialog")); 108 if (!show_html_dialog) 109 return false; 110 111 IMoniker *url_moniker = NULL; 112 ::CreateURLMonikerEx(NULL, url_.c_str(), &url_moniker, URL_MK_UNIFORM); 113 if (!url_moniker) 114 return false; 115 116 wchar_t* extra_args = NULL; 117 if (callback) { 118 callback->OnBeforeCreation(&extra_args); 119 // Sets a windows hook for this thread only. 120 hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, MsgFilter, NULL, 121 GetCurrentThreadId()); 122 if (hook_) 123 callback_ = callback; 124 } 125 126 // Pass our parameter to the dialog in the dialogArguments property of 127 // the window object. 128 base::win::ScopedVariant dialog_args(param_.c_str()); 129 130 VARIANT v_result; 131 ::VariantInit(&v_result); 132 133 // Creates the window with the embedded IE control in a modal loop. 134 HRESULT hr = show_html_dialog(NULL, 135 url_moniker, 136 dialog_args.AsInput(), 137 extra_args, 138 &v_result); 139 url_moniker->Release(); 140 141 if (v_result.vt == VT_I4) { 142 *result = v_result.intVal; 143 } else if (v_result.vt == VT_BSTR) { 144 *result = HTML_DLG_EXTRA; 145 extra_result_.assign(v_result.bstrVal, SysStringLen(v_result.bstrVal)); 146 } 147 148 ::VariantClear(&v_result); 149 150 if (hook_) { 151 ::UnhookWindowsHookEx(hook_); 152 callback_ = NULL; 153 hook_ = NULL; 154 } 155 return SUCCEEDED(hr); 156} 157 158// EulaHTMLDialog implementation --------------------------------------------- 159 160void EulaHTMLDialog::Customizer::OnBeforeCreation(wchar_t** extra) { 161} 162 163// The customization of the window consists in removing the close button and 164// replacing the existing 'e' icon with the standard informational icon. 165void EulaHTMLDialog::Customizer::OnBeforeDisplay(void* window) { 166 if (!window) 167 return; 168 HWND top_window = static_cast<HWND>(window); 169 LONG_PTR style = ::GetWindowLongPtrW(top_window, GWL_STYLE); 170 ::SetWindowLongPtrW(top_window, GWL_STYLE, style & ~WS_SYSMENU); 171 HICON ico = ::LoadIcon(NULL, IDI_INFORMATION); 172 ::SendMessageW(top_window, WM_SETICON, ICON_SMALL, 173 reinterpret_cast<LPARAM>(ico)); 174} 175 176EulaHTMLDialog::EulaHTMLDialog(const std::wstring& file, 177 const std::wstring& param) { 178 dialog_ = CreateNativeHTMLDialog(file, param); 179} 180 181EulaHTMLDialog::~EulaHTMLDialog() { 182 delete dialog_; 183} 184 185EulaHTMLDialog::Outcome EulaHTMLDialog::ShowModal() { 186 Customizer customizer; 187 HTMLDialog::DialogResult dr = dialog_->ShowModal(NULL, &customizer); 188 if (HTMLDialog::HTML_DLG_ACCEPT == dr) 189 return EulaHTMLDialog::ACCEPTED; 190 else if (HTMLDialog::HTML_DLG_EXTRA == dr) 191 return EulaHTMLDialog::ACCEPTED_OPT_IN; 192 else 193 return EulaHTMLDialog::REJECTED; 194} 195 196} // namespace installer 197