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