Home | History | Annotate | Download | only in util
      1 // Copyright (c) 2011 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 <windows.h>
      6 #include <mshtmhst.h>
      7 #include <urlmon.h>
      8 
      9 #include "base/win/scoped_variant.h"
     10 #include "chrome/installer/util/html_dialog.h"
     11 
     12 #pragma comment(lib, "urlmon.lib")
     13 
     14 namespace installer {
     15 
     16 // Windows implementation of the HTML dialog class. The main danger with
     17 // using the IE embedded control as a child window of a custom window is that
     18 // it still contains too much browser functionality, allowing the user to do
     19 // things that are not expected of a plain dialog. ShowHTMLDialog API solves
     20 // that problem but gives us a not very customizable frame. We solve that
     21 // using hooks to end up with a robust dialog at the expense of having to do
     22 // the buttons in html itself, like so:
     23 //
     24 // <form onsubmit="submit_it(this); return false;">
     25 //  <input name="accept" type="checkbox" /> My cool option
     26 //  <input name="submit" type="submit" value="[accept]" />
     27 // </form>
     28 //
     29 // function submit_it(f) {
     30 //  if (f.accept.checked) {
     31 //    window.returnValue = 1;  <-- this matches HTML_DLG_ACCEPT
     32 //  } else {
     33 //    window.returnValue = 2;  <-- this matches HTML_DLG_DECLINE
     34 //  }
     35 //  window.close();
     36 // }
     37 //
     38 // Note that on the submit handler you need to set window.returnValue to one of
     39 // the values of DialogResult and call window.close().
     40 
     41 class HTMLDialogWin : public HTMLDialog {
     42  public:
     43   HTMLDialogWin(const std::wstring& url, const std::wstring& param)
     44       : url_(url), param_(param) {
     45     if (!mshtml_)
     46        mshtml_ = LoadLibrary(L"MSHTML.DLL");
     47   }
     48 
     49   virtual DialogResult ShowModal(void* parent_window,
     50                                  CustomizationCallback* callback) {
     51     int result = HTML_DLG_DECLINE;
     52     if (!InternalDoDialog(callback, &result))
     53       return HTML_DLG_ERROR;
     54     return static_cast<DialogResult>(result);
     55   }
     56 
     57   virtual std::wstring GetExtraResult() {
     58     return extra_result_;
     59   }
     60 
     61  private:
     62   bool InternalDoDialog(CustomizationCallback* callback, int* result);
     63   static LRESULT CALLBACK MsgFilter(int code, WPARAM wParam, LPARAM lParam);
     64 
     65   std::wstring url_;
     66   std::wstring param_;
     67   static HHOOK hook_;
     68   static HINSTANCE mshtml_;
     69   static CustomizationCallback* callback_;
     70   std::wstring extra_result_;
     71 };
     72 
     73 HTMLDialog* CreateNativeHTMLDialog(const std::wstring& url,
     74                                    const std::wstring& param) {
     75   return new HTMLDialogWin(url, param);
     76 }
     77 
     78 HHOOK HTMLDialogWin::hook_ = NULL;
     79 HINSTANCE HTMLDialogWin::mshtml_ = NULL;
     80 HTMLDialogWin::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.
     85 LRESULT 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 
    101 bool 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 
    160 void 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.
    165 void 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 
    176 EulaHTMLDialog::EulaHTMLDialog(const std::wstring& file,
    177                                const std::wstring& param) {
    178   dialog_ = CreateNativeHTMLDialog(file, param);
    179 }
    180 
    181 EulaHTMLDialog::~EulaHTMLDialog() {
    182   delete dialog_;
    183 }
    184 
    185 EulaHTMLDialog::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