Home | History | Annotate | Download | only in delegate_execute
      1 // Copyright (c) 2012 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 // Implementation of the CommandExecuteImpl class which implements the
      5 // IExecuteCommand and related interfaces for handling ShellExecute based
      6 // launches of the Chrome browser.
      7 
      8 #include "win8/delegate_execute/command_execute_impl.h"
      9 
     10 #include <shlguid.h>
     11 
     12 #include "base/files/file_util.h"
     13 #include "base/path_service.h"
     14 #include "base/process/launch.h"
     15 #include "base/process/process_handle.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/win/message_window.h"
     18 #include "base/win/registry.h"
     19 #include "base/win/scoped_co_mem.h"
     20 #include "base/win/scoped_handle.h"
     21 #include "base/win/scoped_process_information.h"
     22 #include "base/win/win_util.h"
     23 #include "chrome/common/chrome_constants.h"
     24 #include "chrome/common/chrome_paths.h"
     25 #include "chrome/common/chrome_switches.h"
     26 #include "chrome/installer/util/browser_distribution.h"
     27 #include "chrome/installer/util/install_util.h"
     28 #include "chrome/installer/util/shell_util.h"
     29 #include "chrome/installer/util/util_constants.h"
     30 #include "ui/base/clipboard/clipboard_util_win.h"
     31 #include "ui/base/ui_base_switches.h"
     32 #include "ui/gfx/win/dpi.h"
     33 #include "win8/delegate_execute/chrome_util.h"
     34 #include "win8/delegate_execute/delegate_execute_util.h"
     35 #include "win8/viewer/metro_viewer_constants.h"
     36 
     37 namespace {
     38 // Helper function to retrieve the url from IShellItem interface passed in.
     39 // Returns S_OK on success.
     40 HRESULT GetUrlFromShellItem(IShellItem* shell_item, base::string16* url) {
     41   DCHECK(shell_item);
     42   DCHECK(url);
     43   // First attempt to get the url from the underlying IDataObject if any. This
     44   // ensures that we get the full url, i.e. including the anchor.
     45   // If we fail to get the underlying IDataObject we retrieve the url via the
     46   // IShellItem::GetDisplayName function.
     47   CComPtr<IDataObject> object;
     48   HRESULT hr = shell_item->BindToHandler(NULL,
     49                                          BHID_DataObject,
     50                                          IID_IDataObject,
     51                                          reinterpret_cast<void**>(&object));
     52   if (SUCCEEDED(hr)) {
     53     DCHECK(object);
     54     if (ui::ClipboardUtil::GetPlainText(object, url))
     55       return S_OK;
     56   }
     57 
     58   base::win::ScopedCoMem<wchar_t> name;
     59   hr = shell_item->GetDisplayName(SIGDN_URL, &name);
     60   if (hr != S_OK) {
     61     AtlTrace("Failed to get display name\n");
     62     return hr;
     63   }
     64 
     65   *url = static_cast<const wchar_t*>(name);
     66   AtlTrace("Retrieved url from display name %ls\n", url->c_str());
     67   return S_OK;
     68 }
     69 
     70 bool LaunchChromeBrowserProcess() {
     71   base::FilePath delegate_exe_path;
     72   if (!PathService::Get(base::FILE_EXE, &delegate_exe_path))
     73     return false;
     74 
     75   // First try and go up a level to find chrome.exe.
     76   base::FilePath chrome_exe_path =
     77       delegate_exe_path.DirName()
     78                        .DirName()
     79                        .Append(chrome::kBrowserProcessExecutableName);
     80   if (!base::PathExists(chrome_exe_path)) {
     81     // Try looking in the current directory if we couldn't find it one up in
     82     // order to support developer installs.
     83     chrome_exe_path =
     84         delegate_exe_path.DirName()
     85                          .Append(chrome::kBrowserProcessExecutableName);
     86   }
     87 
     88   if (!base::PathExists(chrome_exe_path)) {
     89     AtlTrace("Could not locate chrome.exe at: %ls\n",
     90              chrome_exe_path.value().c_str());
     91     return false;
     92   }
     93 
     94   CommandLine cl(chrome_exe_path);
     95 
     96   // Prevent a Chrome window from showing up on the desktop.
     97   cl.AppendSwitch(switches::kSilentLaunch);
     98 
     99   // Tell Chrome to connect to the Metro viewer process.
    100   cl.AppendSwitch(switches::kViewerConnect);
    101 
    102   base::LaunchOptions launch_options;
    103   launch_options.start_hidden = true;
    104 
    105   return base::LaunchProcess(cl, launch_options, NULL);
    106 }
    107 
    108 }  // namespace
    109 
    110 bool CommandExecuteImpl::path_provider_initialized_ = false;
    111 
    112 // CommandExecuteImpl is responsible for activating chrome in Windows 8. The
    113 // flow is complicated and this tries to highlight the important events.
    114 // The current approach is to have a single instance of chrome either
    115 // running in desktop or metro mode. If there is no current instance then
    116 // the desktop shortcut launches desktop chrome and the metro tile or search
    117 // charm launches metro chrome.
    118 // If chrome is running then focus/activation is given to the existing one
    119 // regardless of what launch point the user used.
    120 //
    121 // The general flow for activation is as follows:
    122 //
    123 // 1- User interacts with launch point (icon, tile, search, shellexec, etc)
    124 // 2- Windows finds the appid for launch item and resolves it to chrome
    125 // 3- Windows activates CommandExecuteImpl inside a surrogate process
    126 // 4- Windows calls the following sequence of entry points:
    127 //    CommandExecuteImpl::SetShowWindow
    128 //    CommandExecuteImpl::SetPosition
    129 //    CommandExecuteImpl::SetDirectory
    130 //    CommandExecuteImpl::SetParameter
    131 //    CommandExecuteImpl::SetNoShowUI
    132 //    CommandExecuteImpl::SetSelection
    133 //    CommandExecuteImpl::Initialize
    134 //    Up to this point the code basically just gathers values passed in, like
    135 //    the launch scheme (or url) and the activation verb.
    136 // 5- Windows calls CommandExecuteImpl::Getvalue()
    137 //    Here we need to return AHE_IMMERSIVE or AHE_DESKTOP. That depends on:
    138 //    a) if run in high-integrity return AHE_DESKTOP.
    139 //    b) else we return what GetLaunchMode() tells us, which is:
    140 //       i) if chrome is not the default browser, return AHE_DESKTOP
    141 //       ii) if the command line --force-xxx is present return that
    142 //       iii) if the registry 'launch_mode' exists return that
    143 //       iv) else return AHE_DESKTOP
    144 // 6- If we returned AHE_IMMERSIVE in step 5 windows might not call us back
    145 //    and simply activate chrome in metro by itself, however in some cases
    146 //    it might proceed at step 7.
    147 //    As far as we know if we return AHE_DESKTOP then step 7 always happens.
    148 // 7- Windows calls CommandExecuteImpl::Execute()
    149 //    Here we call GetLaunchMode() which returns the cached answer
    150 //    computed at step 5c. which can be:
    151 //    a) ECHUIM_DESKTOP then we call LaunchDesktopChrome() that calls
    152 //       ::CreateProcess and we exit at this point even on failure.
    153 //    b) else we call one of the IApplicationActivationManager activation
    154 //       functions depending on the parameters passed in step 4.
    155 //    c) If the activation returns E_APPLICATION_NOT_REGISTERED, then we fall
    156 //       back to launching chrome on the desktop via LaunchDestopChrome().  Note
    157 //       that this case can lead to strange behavior, because at this point we
    158 //       have pre-launched the browser with:
    159 //       --silent-launch --connect-to-metro-viewer.
    160 //       E_APPLICATION_NOT_REGISTERED is always returned if Chrome is not the
    161 //       default browser (this case will have already been checked for by
    162 //       GetLaunchMode() and AHE_DESKTOP returned), but we don't know if it can
    163 //       be returned for other reasons.
    164 //
    165 // Note that if a command line --force-xxx is present we write that launch mode
    166 // in the registry so next time the logic reaches 5c-ii it will use the same
    167 // mode again.
    168 //
    169 CommandExecuteImpl::CommandExecuteImpl()
    170     : parameters_(CommandLine::NO_PROGRAM),
    171       launch_scheme_(INTERNET_SCHEME_DEFAULT),
    172       integrity_level_(base::INTEGRITY_UNKNOWN) {
    173   memset(&start_info_, 0, sizeof(start_info_));
    174   start_info_.cb = sizeof(start_info_);
    175 
    176   // We need to query the user data dir of chrome so we need chrome's
    177   // path provider. We can be created multiple times in a single instance
    178   // however so make sure we do this only once.
    179   if (!path_provider_initialized_) {
    180     chrome::RegisterPathProvider();
    181     path_provider_initialized_ = true;
    182   }
    183 }
    184 
    185 // CommandExecuteImpl
    186 STDMETHODIMP CommandExecuteImpl::SetKeyState(DWORD key_state) {
    187   return S_OK;
    188 }
    189 
    190 STDMETHODIMP CommandExecuteImpl::SetParameters(LPCWSTR params) {
    191   parameters_ = delegate_execute::CommandLineFromParameters(params);
    192   return S_OK;
    193 }
    194 
    195 STDMETHODIMP CommandExecuteImpl::SetPosition(POINT pt) {
    196   return S_OK;
    197 }
    198 
    199 STDMETHODIMP CommandExecuteImpl::SetShowWindow(int show) {
    200   start_info_.wShowWindow = show;
    201   start_info_.dwFlags |= STARTF_USESHOWWINDOW;
    202   return S_OK;
    203 }
    204 
    205 STDMETHODIMP CommandExecuteImpl::SetNoShowUI(BOOL no_show_ui) {
    206   return S_OK;
    207 }
    208 
    209 STDMETHODIMP CommandExecuteImpl::SetDirectory(LPCWSTR directory) {
    210   return S_OK;
    211 }
    212 
    213 STDMETHODIMP CommandExecuteImpl::GetValue(enum AHE_TYPE* pahe) {
    214   if (!GetLaunchScheme(&display_name_, &launch_scheme_)) {
    215     AtlTrace("Failed to get scheme, E_FAIL\n");
    216     return E_FAIL;
    217   }
    218 
    219   EC_HOST_UI_MODE mode = GetLaunchMode();
    220   *pahe = (mode == ECHUIM_DESKTOP) ? AHE_DESKTOP : AHE_IMMERSIVE;
    221 
    222   // If we're going to return AHE_IMMERSIVE, then both the browser process and
    223   // the metro viewer need to launch and connect before the user can start
    224   // browsing.  However we must not launch the metro viewer until we get a
    225   // call to CommandExecuteImpl::Execute().  If we wait until then to launch
    226   // the browser process as well, it will appear laggy while they connect to
    227   // each other, so we pre-launch the browser process now.
    228   if (*pahe == AHE_IMMERSIVE && verb_ != win8::kMetroViewerConnectVerb) {
    229     LaunchChromeBrowserProcess();
    230   }
    231   return S_OK;
    232 }
    233 
    234 STDMETHODIMP CommandExecuteImpl::Execute() {
    235   AtlTrace("In %hs\n", __FUNCTION__);
    236 
    237   if (integrity_level_ == base::HIGH_INTEGRITY)
    238     return LaunchDesktopChrome();
    239 
    240   EC_HOST_UI_MODE mode = GetLaunchMode();
    241   if (mode == ECHUIM_DESKTOP)
    242     return LaunchDesktopChrome();
    243 
    244   HRESULT hr = E_FAIL;
    245   CComPtr<IApplicationActivationManager> activation_manager;
    246   hr = activation_manager.CoCreateInstance(CLSID_ApplicationActivationManager);
    247   if (!activation_manager) {
    248     AtlTrace("Failed to get the activation manager, error 0x%x\n", hr);
    249     return S_OK;
    250   }
    251 
    252   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    253   bool is_per_user_install = InstallUtil::IsPerUserInstall(
    254       chrome_exe_.value().c_str());
    255   base::string16 app_id = ShellUtil::GetBrowserModelId(
    256       distribution, is_per_user_install);
    257 
    258   DWORD pid = 0;
    259   if (launch_scheme_ == INTERNET_SCHEME_FILE &&
    260       display_name_.find(installer::kChromeExe) != base::string16::npos) {
    261     AtlTrace("Activating for file\n");
    262     hr = activation_manager->ActivateApplication(app_id.c_str(),
    263                                                  verb_.c_str(),
    264                                                  AO_NONE,
    265                                                  &pid);
    266   } else {
    267     AtlTrace("Activating for protocol\n");
    268     hr = activation_manager->ActivateForProtocol(app_id.c_str(),
    269                                                  item_array_,
    270                                                  &pid);
    271   }
    272   if (hr == E_APPLICATION_NOT_REGISTERED) {
    273     AtlTrace("Metro chrome is not registered, launching in desktop\n");
    274     return LaunchDesktopChrome();
    275   }
    276   AtlTrace("Metro Chrome launch, pid=%d, returned 0x%x\n", pid, hr);
    277   return S_OK;
    278 }
    279 
    280 STDMETHODIMP CommandExecuteImpl::Initialize(LPCWSTR name,
    281                                             IPropertyBag* bag) {
    282   if (!FindChromeExe(&chrome_exe_))
    283     return E_FAIL;
    284   delegate_execute::UpdateChromeIfNeeded(chrome_exe_);
    285 
    286   if (name) {
    287     AtlTrace("Verb is %S\n", name);
    288     verb_ = name;
    289   }
    290 
    291   base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
    292                                  &integrity_level_);
    293   return S_OK;
    294 }
    295 
    296 STDMETHODIMP CommandExecuteImpl::SetSelection(IShellItemArray* item_array) {
    297   item_array_ = item_array;
    298   return S_OK;
    299 }
    300 
    301 STDMETHODIMP CommandExecuteImpl::GetSelection(REFIID riid, void** selection) {
    302   return S_OK;
    303 }
    304 
    305 STDMETHODIMP CommandExecuteImpl::AllowForegroundTransfer(void* reserved) {
    306   return S_OK;
    307 }
    308 
    309 // Returns false if chrome.exe cannot be found.
    310 // static
    311 bool CommandExecuteImpl::FindChromeExe(base::FilePath* chrome_exe) {
    312   // Look for chrome.exe one folder above delegate_execute.exe (as expected in
    313   // Chrome installs). Failing that, look for it alonside delegate_execute.exe.
    314   base::FilePath dir_exe;
    315   if (!PathService::Get(base::DIR_EXE, &dir_exe)) {
    316     AtlTrace("Failed to get current exe path\n");
    317     return false;
    318   }
    319 
    320   *chrome_exe = dir_exe.DirName().Append(chrome::kBrowserProcessExecutableName);
    321   if (!base::PathExists(*chrome_exe)) {
    322     *chrome_exe = dir_exe.Append(chrome::kBrowserProcessExecutableName);
    323     if (!base::PathExists(*chrome_exe)) {
    324       AtlTrace("Failed to find chrome exe file\n");
    325       return false;
    326     }
    327   }
    328   return true;
    329 }
    330 
    331 bool CommandExecuteImpl::GetLaunchScheme(
    332     base::string16* display_name, INTERNET_SCHEME* scheme) {
    333   if (!item_array_)
    334     return false;
    335 
    336   ATLASSERT(display_name);
    337   ATLASSERT(scheme);
    338 
    339   DWORD count = 0;
    340   item_array_->GetCount(&count);
    341 
    342   if (count != 1) {
    343     AtlTrace("Cannot handle %d elements in the IShellItemArray\n", count);
    344     return false;
    345   }
    346 
    347   CComPtr<IEnumShellItems> items;
    348   item_array_->EnumItems(&items);
    349   CComPtr<IShellItem> shell_item;
    350   HRESULT hr = items->Next(1, &shell_item, &count);
    351   if (hr != S_OK) {
    352     AtlTrace("Failed to read element from the IShellItemsArray\n");
    353     return false;
    354   }
    355 
    356   hr = GetUrlFromShellItem(shell_item, display_name);
    357   if (FAILED(hr)) {
    358     AtlTrace("Failed to get url. Error 0x%x\n", hr);
    359     return false;
    360   }
    361 
    362   wchar_t scheme_name[16];
    363   URL_COMPONENTS components = {0};
    364   components.lpszScheme = scheme_name;
    365   components.dwSchemeLength = sizeof(scheme_name)/sizeof(scheme_name[0]);
    366 
    367   components.dwStructSize = sizeof(components);
    368   if (!InternetCrackUrlW(display_name->c_str(), 0, 0, &components)) {
    369     AtlTrace("Failed to crack url %ls\n", display_name->c_str());
    370     return false;
    371   }
    372 
    373   AtlTrace("Launch scheme is [%ls] (%d)\n", scheme_name, components.nScheme);
    374   *scheme = components.nScheme;
    375   return true;
    376 }
    377 
    378 HRESULT CommandExecuteImpl::LaunchDesktopChrome() {
    379   base::string16 display_name = display_name_;
    380 
    381   switch (launch_scheme_) {
    382     case INTERNET_SCHEME_FILE:
    383       // If anything other than chrome.exe is passed in the display name we
    384       // should honor it. For e.g. If the user clicks on a html file when
    385       // chrome is the default we should treat it as a parameter to be passed
    386       // to chrome.
    387       if (display_name.find(installer::kChromeExe) != base::string16::npos)
    388         display_name.clear();
    389       break;
    390 
    391     default:
    392       break;
    393   }
    394 
    395   CommandLine chrome(
    396       delegate_execute::MakeChromeCommandLine(chrome_exe_, parameters_,
    397                                               display_name));
    398   base::string16 command_line(chrome.GetCommandLineString());
    399 
    400   AtlTrace("Formatted command line is %ls\n", command_line.c_str());
    401 
    402   PROCESS_INFORMATION temp_process_info = {};
    403   BOOL ret = CreateProcess(chrome_exe_.value().c_str(),
    404                            &command_line[0],
    405                            NULL, NULL, FALSE, 0, NULL, NULL, &start_info_,
    406                            &temp_process_info);
    407   if (ret) {
    408     base::win::ScopedProcessInformation proc_info(temp_process_info);
    409     AtlTrace("Process id is %d\n", proc_info.process_id());
    410     AllowSetForegroundWindow(proc_info.process_id());
    411   } else {
    412     AtlTrace("Process launch failed, error %d\n", ::GetLastError());
    413   }
    414 
    415   return S_OK;
    416 }
    417 
    418 EC_HOST_UI_MODE CommandExecuteImpl::GetLaunchMode() {
    419   // See the header file for an explanation of the mode selection logic.
    420   static bool launch_mode_determined = false;
    421   static EC_HOST_UI_MODE launch_mode = ECHUIM_DESKTOP;
    422 
    423   const char* modes[] = { "Desktop", "Immersive", "SysLauncher", "??" };
    424 
    425   if (launch_mode_determined)
    426     return launch_mode;
    427 
    428   if (integrity_level_ == base::HIGH_INTEGRITY) {
    429     // Metro mode apps don't work in high integrity mode.
    430     AtlTrace("High integrity: launching in desktop mode\n");
    431     launch_mode = ECHUIM_DESKTOP;
    432     launch_mode_determined = true;
    433     return launch_mode;
    434   }
    435 
    436   base::FilePath chrome_exe;
    437   if (!FindChromeExe(&chrome_exe) ||
    438       ShellUtil::GetChromeDefaultStateFromPath(chrome_exe) !=
    439           ShellUtil::IS_DEFAULT) {
    440     AtlTrace("Chrome is not default: launching in desktop mode\n");
    441     launch_mode = ECHUIM_DESKTOP;
    442     launch_mode_determined = true;
    443     return launch_mode;
    444   }
    445 
    446   if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_F11)) {
    447     AtlTrace("Hotkey: launching in immersive mode\n");
    448     launch_mode = ECHUIM_IMMERSIVE;
    449     launch_mode_determined = true;
    450     return launch_mode;
    451   }
    452 
    453   // From here on, if we can, we will write the outcome
    454   // of this function to the registry.
    455   if (parameters_.HasSwitch(switches::kForceImmersive)) {
    456     launch_mode = ECHUIM_IMMERSIVE;
    457     launch_mode_determined = true;
    458     parameters_ = CommandLine(CommandLine::NO_PROGRAM);
    459   } else if (parameters_.HasSwitch(switches::kForceDesktop)) {
    460     launch_mode = ECHUIM_DESKTOP;
    461     launch_mode_determined = true;
    462     parameters_ = CommandLine(CommandLine::NO_PROGRAM);
    463   }
    464 
    465   base::win::RegKey reg_key;
    466   LONG key_result = reg_key.Create(HKEY_CURRENT_USER,
    467                                    chrome::kMetroRegistryPath,
    468                                    KEY_ALL_ACCESS);
    469   if (key_result != ERROR_SUCCESS) {
    470     AtlTrace("Failed to open HKCU %ls key, error 0x%x\n",
    471              chrome::kMetroRegistryPath,
    472              key_result);
    473     if (!launch_mode_determined) {
    474       // If we cannot open the key and we don't know the
    475       // launch mode we default to desktop mode.
    476       launch_mode = ECHUIM_DESKTOP;
    477       launch_mode_determined = true;
    478     }
    479     return launch_mode;
    480   }
    481 
    482   if (launch_mode_determined) {
    483     AtlTrace("Launch mode forced by cmdline to %s\n", modes[launch_mode]);
    484     reg_key.WriteValue(chrome::kLaunchModeValue,
    485                        static_cast<DWORD>(launch_mode));
    486     return launch_mode;
    487   }
    488 
    489   // Use the previous mode if available. Else launch in desktop mode.
    490   DWORD reg_value;
    491   if (reg_key.ReadValueDW(chrome::kLaunchModeValue,
    492                           &reg_value) != ERROR_SUCCESS) {
    493     launch_mode = ECHUIM_DESKTOP;
    494     AtlTrace("Can't read registry, defaulting to %s\n", modes[launch_mode]);
    495   } else if (reg_value >= ECHUIM_SYSTEM_LAUNCHER) {
    496     AtlTrace("Invalid registry launch mode value %u\n", reg_value);
    497     launch_mode = ECHUIM_DESKTOP;
    498   } else {
    499     launch_mode = static_cast<EC_HOST_UI_MODE>(reg_value);
    500     AtlTrace("Launch mode forced by registry to %s\n", modes[launch_mode]);
    501   }
    502 
    503   launch_mode_determined = true;
    504   return launch_mode;
    505 }
    506