summaryrefslogtreecommitdiffstats
path: root/chrome/installer/util/html_dialog_impl.cc
blob: 0f48041e38034ba03e8daf006eb0bc79154f0663 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright (c) 2011 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 <windows.h>
#include <mshtmhst.h>
#include <urlmon.h>

#include "base/win/scoped_variant.h"
#include "chrome/installer/util/html_dialog.h"

#pragma comment(lib, "urlmon.lib")

namespace installer {

// Windows implementation of the HTML dialog class. The main danger with
// using the IE embedded control as a child window of a custom window is that
// it still contains too much browser functionality, allowing the user to do
// things that are not expected of a plain dialog. ShowHTMLDialog API solves
// that problem but gives us a not very customizable frame. We solve that
// using hooks to end up with a robust dialog at the expense of having to do
// the buttons in html itself, like so:
//
// <form onsubmit="submit_it(this); return false;">
//  <input name="accept" type="checkbox" /> My cool option
//  <input name="submit" type="submit" value="[accept]" />
// </form>
//
// function submit_it(f) {
//  if (f.accept.checked) {
//    window.returnValue = 1;  <-- this matches HTML_DLG_ACCEPT
//  } else {
//    window.returnValue = 2;  <-- this matches HTML_DLG_DECLINE
//  }
//  window.close();
// }
//
// Note that on the submit handler you need to set window.returnValue to one of
// the values of DialogResult and call window.close().

class HTMLDialogWin : public HTMLDialog {
 public:
  HTMLDialogWin(const std::wstring& url, const std::wstring& param)
      : url_(url), param_(param) {
    if (!mshtml_)
       mshtml_ = LoadLibrary(L"MSHTML.DLL");
  }

  virtual DialogResult ShowModal(void* parent_window,
                                 CustomizationCallback* callback) {
    int result = HTML_DLG_DECLINE;
    if (!InternalDoDialog(callback, &result))
      return HTML_DLG_ERROR;
    return static_cast<DialogResult>(result);
  }

  // TODO(cpu): Not yet implemented.
  virtual std::wstring GetExtraResult() {
    return std::wstring();
  }

 private:
  bool InternalDoDialog(CustomizationCallback* callback, int* result);
  static LRESULT CALLBACK MsgFilter(int code, WPARAM wParam, LPARAM lParam);

  std::wstring url_;
  std::wstring param_;
  static HHOOK hook_;
  static HINSTANCE mshtml_;
  static CustomizationCallback* callback_;
};

HTMLDialog* CreateNativeHTMLDialog(const std::wstring& url,
                                   const std::wstring& param) {
  return new HTMLDialogWin(url, param);
}

HHOOK HTMLDialogWin::hook_ = NULL;
HINSTANCE HTMLDialogWin::mshtml_ = NULL;
HTMLDialogWin::CustomizationCallback* HTMLDialogWin::callback_ = NULL;

// This hook function gets called for messages bound to the windows that
// ShowHTMLDialog creates. We tell apart the top window because it has the
// system menu style.
LRESULT HTMLDialogWin::MsgFilter(int code, WPARAM wParam, LPARAM lParam) {
  static bool tweak_window = true;
  if (lParam && tweak_window) {
    HWND target_window = reinterpret_cast<MSG*>(lParam)->hwnd;
    if (target_window) {
      LONG_PTR style = ::GetWindowLongPtrW(target_window, GWL_STYLE);
      if (style & WS_SYSMENU) {
        tweak_window = false;
        callback_->OnBeforeDisplay(target_window);
      }
    }
  }
  // Always call the next hook in the chain.
  return ::CallNextHookEx(hook_, code, wParam, lParam);
}

bool HTMLDialogWin::InternalDoDialog(CustomizationCallback* callback,
                                     int* result) {
  if (!mshtml_)
    return false;
  SHOWHTMLDIALOGFN* show_html_dialog =
      reinterpret_cast<SHOWHTMLDIALOGFN*>(
          GetProcAddress(mshtml_, "ShowHTMLDialog"));
  if (!show_html_dialog)
    return false;

  IMoniker *url_moniker = NULL;
  ::CreateURLMonikerEx(NULL, url_.c_str(), &url_moniker, URL_MK_UNIFORM);
  if (!url_moniker)
    return false;

  wchar_t* extra_args = NULL;
  if (callback) {
    callback->OnBeforeCreation(reinterpret_cast<void**>(&extra_args));
    // Sets a windows hook for this thread only.
    hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, MsgFilter, NULL,
                               GetCurrentThreadId());
    if (hook_)
      callback_ = callback;
  }

  // Pass our parameter to the dialog in the dialogArguments property of
  // the window object.
  base::win::ScopedVariant dialog_args(param_.c_str());

  VARIANT v_result;
  ::VariantInit(&v_result);

  // Creates the window with the embedded IE control in a modal loop.
  HRESULT hr = show_html_dialog(NULL,
                                url_moniker,
                                dialog_args.AsInput(),
                                extra_args,
                                &v_result);
  url_moniker->Release();

  if (v_result.vt == VT_I4)
    *result = v_result.intVal;
  ::VariantClear(&v_result);

  if (hook_) {
    ::UnhookWindowsHookEx(hook_);
    callback_ = NULL;
    hook_ = NULL;
  }
  return SUCCEEDED(hr);
}

// EulaHTMLDialog implementation ---------------------------------------------

void EulaHTMLDialog::Customizer::OnBeforeCreation(void** extra) {
}

// The customization of the window consists in removing the close button and
// replacing the existing 'e' icon with the standard informational icon.
void EulaHTMLDialog::Customizer::OnBeforeDisplay(void* window) {
  if (!window)
    return;
  HWND top_window = static_cast<HWND>(window);
  LONG_PTR style = ::GetWindowLongPtrW(top_window, GWL_STYLE);
  ::SetWindowLongPtrW(top_window, GWL_STYLE, style & ~WS_SYSMENU);
  HICON ico = ::LoadIcon(NULL, IDI_INFORMATION);
  ::SendMessageW(top_window, WM_SETICON, ICON_SMALL,
                 reinterpret_cast<LPARAM>(ico));
}

EulaHTMLDialog::EulaHTMLDialog(const std::wstring& file,
                               const std::wstring& param) {
  dialog_ = CreateNativeHTMLDialog(file, param);
}

EulaHTMLDialog::~EulaHTMLDialog() {
  delete dialog_;
}

EulaHTMLDialog::Outcome EulaHTMLDialog::ShowModal() {
  Customizer customizer;
  HTMLDialog::DialogResult dr = dialog_->ShowModal(NULL, &customizer);
  if (HTMLDialog::HTML_DLG_ACCEPT == dr)
    return EulaHTMLDialog::ACCEPTED;
  else if (HTMLDialog::HTML_DLG_EXTRA == dr)
    return EulaHTMLDialog::ACCEPTED_OPT_IN;
  else
    return EulaHTMLDialog::REJECTED;
}

}  // namespace installer