- add sources.
[platform/framework/web/crosswalk.git] / src / win8 / delegate_execute / command_execute_impl.cc
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/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 "win8/delegate_execute/chrome_util.h"
32 #include "win8/delegate_execute/delegate_execute_util.h"
33
34 namespace {
35
36 // Helper function to retrieve the url from IShellItem interface passed in.
37 // Returns S_OK on success.
38 HRESULT GetUrlFromShellItem(IShellItem* shell_item, string16* url) {
39   DCHECK(shell_item);
40   DCHECK(url);
41   // First attempt to get the url from the underlying IDataObject if any. This
42   // ensures that we get the full url, i.e. including the anchor.
43   // If we fail to get the underlying IDataObject we retrieve the url via the
44   // IShellItem::GetDisplayName function.
45   CComPtr<IDataObject> object;
46   HRESULT hr = shell_item->BindToHandler(NULL,
47                                          BHID_DataObject,
48                                          IID_IDataObject,
49                                          reinterpret_cast<void**>(&object));
50   if (SUCCEEDED(hr)) {
51     DCHECK(object);
52     if (ui::ClipboardUtil::GetPlainText(object, url))
53       return S_OK;
54   }
55
56   base::win::ScopedCoMem<wchar_t> name;
57   hr = shell_item->GetDisplayName(SIGDN_URL, &name);
58   if (hr != S_OK) {
59     AtlTrace("Failed to get display name\n");
60     return hr;
61   }
62
63   *url = static_cast<const wchar_t*>(name);
64   AtlTrace("Retrieved url from display name %ls\n", url->c_str());
65   return S_OK;
66 }
67
68 #if defined(USE_AURA)
69 bool LaunchChromeBrowserProcess() {
70   base::FilePath delegate_exe_path;
71   if (!PathService::Get(base::FILE_EXE, &delegate_exe_path))
72     return false;
73
74   // First try and go up a level to find chrome.exe.
75   base::FilePath chrome_exe_path =
76       delegate_exe_path.DirName()
77                        .DirName()
78                        .Append(chrome::kBrowserProcessExecutableName);
79   if (!base::PathExists(chrome_exe_path)) {
80     // Try looking in the current directory if we couldn't find it one up in
81     // order to support developer installs.
82     chrome_exe_path =
83         delegate_exe_path.DirName()
84                          .Append(chrome::kBrowserProcessExecutableName);
85   }
86
87   if (!base::PathExists(chrome_exe_path)) {
88     AtlTrace("Could not locate chrome.exe at: %ls\n",
89              chrome_exe_path.value().c_str());
90     return false;
91   }
92
93   CommandLine cl(chrome_exe_path);
94
95   // Prevent a Chrome window from showing up on the desktop.
96   cl.AppendSwitch(switches::kSilentLaunch);
97
98   // Tell Chrome to connect to the Metro viewer process.
99   cl.AppendSwitch(switches::kViewerConnect);
100
101   base::LaunchOptions launch_options;
102   launch_options.start_hidden = true;
103
104   return base::LaunchProcess(cl, launch_options, NULL);
105 }
106 #endif  // defined(USE_AURA)
107
108 }  // namespace
109
110 bool CommandExecuteImpl::path_provider_initialized_ = false;
111
112 // CommandExecuteImpl is resposible 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 // regarless of what launch point the user used.
120 //
121 // The general flow when chrome is the default browser 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) if chrome is running return the AHE_ mode of chrome
140 //    c) else we return what GetLaunchMode() tells us, which is:
141 //       i) if the command line --force-xxx is present return that
142 //       ii) if the registry 'launch_mode' exists return that
143 //       iii) if IsTouchEnabledDevice() is true return AHE_IMMERSIVE
144 //       iv) else return AHE_DESKTOP
145 // 6- If we returned AHE_IMMERSIVE in step 5 windows might not call us back
146 //    and simply activate chrome in metro by itself, however in some cases
147 //    it might proceed at step 7.
148 //    As far as we know if we return AHE_DESKTOP then step 7 always happens.
149 // 7- Windows calls CommandExecuteImpl::Execute()
150 //    Here we call GetLaunchMode() which returns the cached answer
151 //    computed at step 5c. which can be:
152 //    a) ECHUIM_DESKTOP then we call LaunchDesktopChrome() that calls
153 //       ::CreateProcess and we exit at this point even on failure.
154 //    b) else we call one of the IApplicationActivationManager activation
155 //       functions depending on the parameters passed in step 4.
156 //    c) If the activation returns E_APPLICATION_NOT_REGISTERED, then we fall
157 //       back to launching chrome on the desktop via LaunchDestopChrome().
158 //
159 // Note that if a command line --force-xxx is present we write that launch mode
160 // in the registry so next time the logic reaches 5c-ii it will use the same
161 // mode again.
162 //
163 // Also note that if we are not the default browser and IsTouchEnabledDevice()
164 // returns true, launching chrome can go all the way to 7c, which might be
165 // a slow way to start chrome.
166 //
167 CommandExecuteImpl::CommandExecuteImpl()
168     : parameters_(CommandLine::NO_PROGRAM),
169       launch_scheme_(INTERNET_SCHEME_DEFAULT),
170       integrity_level_(base::INTEGRITY_UNKNOWN),
171       chrome_mode_(ECHUIM_SYSTEM_LAUNCHER) {
172   memset(&start_info_, 0, sizeof(start_info_));
173   start_info_.cb = sizeof(start_info_);
174
175   // We need to query the user data dir of chrome so we need chrome's
176   // path provider. We can be created multiplie times in a single instance
177   // however so make sure we do this only once.
178   if (!path_provider_initialized_) {
179     chrome::RegisterPathProvider();
180     path_provider_initialized_ = true;
181   }
182 }
183
184 // CommandExecuteImpl
185 STDMETHODIMP CommandExecuteImpl::SetKeyState(DWORD key_state) {
186   AtlTrace("In %hs\n", __FUNCTION__);
187   return S_OK;
188 }
189
190 STDMETHODIMP CommandExecuteImpl::SetParameters(LPCWSTR params) {
191   AtlTrace("In %hs [%S]\n", __FUNCTION__, params);
192   parameters_ = delegate_execute::CommandLineFromParameters(params);
193   return S_OK;
194 }
195
196 STDMETHODIMP CommandExecuteImpl::SetPosition(POINT pt) {
197   AtlTrace("In %hs\n", __FUNCTION__);
198   return S_OK;
199 }
200
201 STDMETHODIMP CommandExecuteImpl::SetShowWindow(int show) {
202   AtlTrace("In %hs  show=%d\n", __FUNCTION__, show);
203   start_info_.wShowWindow = show;
204   start_info_.dwFlags |= STARTF_USESHOWWINDOW;
205   return S_OK;
206 }
207
208 STDMETHODIMP CommandExecuteImpl::SetNoShowUI(BOOL no_show_ui) {
209   AtlTrace("In %hs no_show=%d\n", __FUNCTION__, no_show_ui);
210   return S_OK;
211 }
212
213 STDMETHODIMP CommandExecuteImpl::SetDirectory(LPCWSTR directory) {
214   AtlTrace("In %hs\n", __FUNCTION__);
215   return S_OK;
216 }
217
218 STDMETHODIMP CommandExecuteImpl::GetValue(enum AHE_TYPE* pahe) {
219   AtlTrace("In %hs\n", __FUNCTION__);
220
221   if (!GetLaunchScheme(&display_name_, &launch_scheme_)) {
222     AtlTrace("Failed to get scheme, E_FAIL\n");
223     return E_FAIL;
224   }
225
226   if (integrity_level_ == base::HIGH_INTEGRITY) {
227     // Metro mode apps don't work in high integrity mode.
228     AtlTrace("High integrity, AHE_DESKTOP\n");
229     *pahe = AHE_DESKTOP;
230     return S_OK;
231   }
232
233   if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_F11)) {
234     AtlTrace("Using Shift-F11 debug hook, returning AHE_IMMERSIVE\n");
235     *pahe = AHE_IMMERSIVE;
236
237 #if defined(USE_AURA)
238     // Launch the chrome browser process that metro chrome will connect to.
239     LaunchChromeBrowserProcess();
240 #endif
241
242     return S_OK;
243   }
244
245   if (GetAsyncKeyState(VK_SHIFT) && GetAsyncKeyState(VK_F12)) {
246     AtlTrace("Using Shift-F12 debug hook, returning AHE_DESKTOP\n");
247     *pahe = AHE_DESKTOP;
248     return S_OK;
249   }
250
251   base::FilePath user_data_dir;
252   if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))  {
253     AtlTrace("Failed to get chrome's data dir path, E_FAIL\n");
254     return E_FAIL;
255   }
256
257   bool decision_made = false;
258
259   // New Aura/Ash world we don't want to go throgh FindWindow path
260   // and instead take decision based on launch mode.
261 #if !defined(USE_AURA)
262   HWND chrome_window = base::win::MessageWindow::FindWindow(
263       user_data_dir.value());
264   if (chrome_window) {
265     AtlTrace("Found chrome window %p\n", chrome_window);
266     // The failure cases below are deemed to happen due to the inherently racy
267     // procedure of going from chrome's window to process handle during which
268     // chrome might have exited. Failing here would probably just cause the
269     // user to retry at which point we would do the right thing.
270     DWORD chrome_pid = 0;
271     ::GetWindowThreadProcessId(chrome_window, &chrome_pid);
272     if (!chrome_pid) {
273       AtlTrace("Failed to get chrome's PID, E_FAIL\n");
274       return E_FAIL;
275     }
276     base::win::ScopedHandle process(
277         ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, chrome_pid));
278     if (!process.IsValid()) {
279       AtlTrace("Failed to open chrome's process [%d], E_FAIL\n", chrome_pid);
280       return E_FAIL;
281     }
282
283     if (IsImmersiveProcess(process.Get())) {
284       AtlTrace("Chrome [%d] is inmmersive, AHE_IMMERSIVE\n", chrome_pid);
285       chrome_mode_ = ECHUIM_IMMERSIVE;
286       *pahe = AHE_IMMERSIVE;
287     } else {
288       AtlTrace("Chrome [%d] is Desktop, AHE_DESKTOP\n");
289       chrome_mode_ = ECHUIM_DESKTOP;
290       *pahe = AHE_DESKTOP;
291     }
292
293     decision_made = true;
294   }
295 #endif
296
297   if (!decision_made) {
298     EC_HOST_UI_MODE mode = GetLaunchMode();
299     *pahe = (mode == ECHUIM_DESKTOP) ? AHE_DESKTOP : AHE_IMMERSIVE;
300   }
301
302 #if defined(USE_AURA)
303   if (*pahe == AHE_IMMERSIVE)
304     LaunchChromeBrowserProcess();
305 #endif
306
307   return S_OK;
308 }
309
310 STDMETHODIMP CommandExecuteImpl::Execute() {
311   AtlTrace("In %hs\n", __FUNCTION__);
312
313   if (integrity_level_ == base::HIGH_INTEGRITY)
314     return LaunchDesktopChrome();
315
316   EC_HOST_UI_MODE mode = GetLaunchMode();
317   if (mode == ECHUIM_DESKTOP)
318     return LaunchDesktopChrome();
319
320   HRESULT hr = E_FAIL;
321   CComPtr<IApplicationActivationManager> activation_manager;
322   hr = activation_manager.CoCreateInstance(CLSID_ApplicationActivationManager);
323   if (!activation_manager) {
324     AtlTrace("Failed to get the activation manager, error 0x%x\n", hr);
325     return S_OK;
326   }
327
328   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
329   bool is_per_user_install = InstallUtil::IsPerUserInstall(
330       chrome_exe_.value().c_str());
331   string16 app_id = ShellUtil::GetBrowserModelId(
332       distribution, is_per_user_install);
333
334   DWORD pid = 0;
335   if (launch_scheme_ == INTERNET_SCHEME_FILE &&
336       display_name_.find(installer::kChromeExe) != string16::npos) {
337     AtlTrace("Activating for file\n");
338     hr = activation_manager->ActivateApplication(app_id.c_str(),
339                                                  verb_.c_str(),
340                                                  AO_NONE,
341                                                  &pid);
342   } else {
343     AtlTrace("Activating for protocol\n");
344     hr = activation_manager->ActivateForProtocol(app_id.c_str(),
345                                                  item_array_,
346                                                  &pid);
347   }
348   if (hr == E_APPLICATION_NOT_REGISTERED) {
349     AtlTrace("Metro chrome is not registered, launching in desktop\n");
350     return LaunchDesktopChrome();
351   }
352   AtlTrace("Metro Chrome launch, pid=%d, returned 0x%x\n", pid, hr);
353   return S_OK;
354 }
355
356 STDMETHODIMP CommandExecuteImpl::Initialize(LPCWSTR name,
357                                             IPropertyBag* bag) {
358   AtlTrace("In %hs\n", __FUNCTION__);
359   if (!FindChromeExe(&chrome_exe_))
360     return E_FAIL;
361   delegate_execute::UpdateChromeIfNeeded(chrome_exe_);
362   if (name) {
363     AtlTrace("Verb is %S\n", name);
364     verb_ = name;
365   }
366
367   base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
368                                  &integrity_level_);
369   if (integrity_level_ == base::HIGH_INTEGRITY) {
370     AtlTrace("Delegate execute launched in high integrity level\n");
371   }
372   return S_OK;
373 }
374
375 STDMETHODIMP CommandExecuteImpl::SetSelection(IShellItemArray* item_array) {
376   AtlTrace("In %hs\n", __FUNCTION__);
377   item_array_ = item_array;
378   return S_OK;
379 }
380
381 STDMETHODIMP CommandExecuteImpl::GetSelection(REFIID riid, void** selection) {
382   AtlTrace("In %hs\n", __FUNCTION__);
383   return S_OK;
384 }
385
386 STDMETHODIMP CommandExecuteImpl::AllowForegroundTransfer(void* reserved) {
387   AtlTrace("In %hs\n", __FUNCTION__);
388   return S_OK;
389 }
390
391 // Returns false if chrome.exe cannot be found.
392 // static
393 bool CommandExecuteImpl::FindChromeExe(base::FilePath* chrome_exe) {
394   AtlTrace("In %hs\n", __FUNCTION__);
395   // Look for chrome.exe one folder above delegate_execute.exe (as expected in
396   // Chrome installs). Failing that, look for it alonside delegate_execute.exe.
397   base::FilePath dir_exe;
398   if (!PathService::Get(base::DIR_EXE, &dir_exe)) {
399     AtlTrace("Failed to get current exe path\n");
400     return false;
401   }
402
403   *chrome_exe = dir_exe.DirName().Append(chrome::kBrowserProcessExecutableName);
404   if (!base::PathExists(*chrome_exe)) {
405     *chrome_exe = dir_exe.Append(chrome::kBrowserProcessExecutableName);
406     if (!base::PathExists(*chrome_exe)) {
407       AtlTrace("Failed to find chrome exe file\n");
408       return false;
409     }
410   }
411
412   AtlTrace("Got chrome exe path as %ls\n", chrome_exe->value().c_str());
413   return true;
414 }
415
416 bool CommandExecuteImpl::GetLaunchScheme(
417     string16* display_name, INTERNET_SCHEME* scheme) {
418   if (!item_array_)
419     return false;
420
421   ATLASSERT(display_name);
422   ATLASSERT(scheme);
423
424   DWORD count = 0;
425   item_array_->GetCount(&count);
426
427   if (count != 1) {
428     AtlTrace("Cannot handle %d elements in the IShellItemArray\n", count);
429     return false;
430   }
431
432   CComPtr<IEnumShellItems> items;
433   item_array_->EnumItems(&items);
434   CComPtr<IShellItem> shell_item;
435   HRESULT hr = items->Next(1, &shell_item, &count);
436   if (hr != S_OK) {
437     AtlTrace("Failed to read element from the IShellItemsArray\n");
438     return false;
439   }
440
441   hr = GetUrlFromShellItem(shell_item, display_name);
442   if (FAILED(hr)) {
443     AtlTrace("Failed to get url. Error 0x%x\n", hr);
444     return false;
445   }
446
447   AtlTrace("url [%ls]\n", display_name->c_str());
448
449   wchar_t scheme_name[16];
450   URL_COMPONENTS components = {0};
451   components.lpszScheme = scheme_name;
452   components.dwSchemeLength = sizeof(scheme_name)/sizeof(scheme_name[0]);
453
454   components.dwStructSize = sizeof(components);
455   if (!InternetCrackUrlW(display_name->c_str(), 0, 0, &components)) {
456     AtlTrace("Failed to crack url %ls\n", display_name->c_str());
457     return false;
458   }
459
460   AtlTrace("Launch scheme is [%ls] (%d)\n", scheme_name, components.nScheme);
461   *scheme = components.nScheme;
462   return true;
463 }
464
465 HRESULT CommandExecuteImpl::LaunchDesktopChrome() {
466   AtlTrace("In %hs\n", __FUNCTION__);
467   string16 display_name = display_name_;
468
469   switch (launch_scheme_) {
470     case INTERNET_SCHEME_FILE:
471       // If anything other than chrome.exe is passed in the display name we
472       // should honor it. For e.g. If the user clicks on a html file when
473       // chrome is the default we should treat it as a parameter to be passed
474       // to chrome.
475       if (display_name.find(installer::kChromeExe) != string16::npos)
476         display_name.clear();
477       break;
478
479     default:
480       break;
481   }
482
483   CommandLine chrome(
484       delegate_execute::MakeChromeCommandLine(chrome_exe_, parameters_,
485                                               display_name));
486   string16 command_line(chrome.GetCommandLineString());
487
488   AtlTrace("Formatted command line is %ls\n", command_line.c_str());
489
490   base::win::ScopedProcessInformation proc_info;
491   BOOL ret = CreateProcess(chrome_exe_.value().c_str(),
492                            const_cast<LPWSTR>(command_line.c_str()),
493                            NULL, NULL, FALSE, 0, NULL, NULL, &start_info_,
494                            proc_info.Receive());
495   if (ret) {
496     AtlTrace("Process id is %d\n", proc_info.process_id());
497     AllowSetForegroundWindow(proc_info.process_id());
498   } else {
499     AtlTrace("Process launch failed, error %d\n", ::GetLastError());
500   }
501
502   return S_OK;
503 }
504
505 EC_HOST_UI_MODE CommandExecuteImpl::GetLaunchMode() {
506   // See the header file for an explanation of the mode selection logic.
507   static bool launch_mode_determined = false;
508   static EC_HOST_UI_MODE launch_mode = ECHUIM_DESKTOP;
509
510   const char* modes[] = { "Desktop", "Immersive", "SysLauncher", "??" };
511
512   if (launch_mode_determined)
513     return launch_mode;
514
515   if (chrome_mode_ != ECHUIM_SYSTEM_LAUNCHER) {
516     launch_mode = chrome_mode_;
517     AtlTrace("Launch mode is that of chrome, %s\n", modes[launch_mode]);
518     launch_mode_determined = true;
519     return launch_mode;
520   }
521
522   if (parameters_.HasSwitch(switches::kForceImmersive)) {
523     launch_mode = ECHUIM_IMMERSIVE;
524     launch_mode_determined = true;
525     parameters_ = CommandLine(CommandLine::NO_PROGRAM);
526   } else if (parameters_.HasSwitch(switches::kForceDesktop)) {
527     launch_mode = ECHUIM_DESKTOP;
528     launch_mode_determined = true;
529     parameters_ = CommandLine(CommandLine::NO_PROGRAM);
530   }
531
532   base::win::RegKey reg_key;
533   LONG key_result = reg_key.Create(HKEY_CURRENT_USER,
534                                    chrome::kMetroRegistryPath,
535                                    KEY_ALL_ACCESS);
536   if (key_result != ERROR_SUCCESS) {
537     AtlTrace("Failed to open HKCU %ls key, error 0x%x\n",
538              chrome::kMetroRegistryPath,
539              key_result);
540     if (!launch_mode_determined) {
541       launch_mode = ECHUIM_DESKTOP;
542       launch_mode_determined = true;
543     }
544     return launch_mode;
545   }
546
547   if (launch_mode_determined) {
548     AtlTrace("Launch mode forced by cmdline to %s\n", modes[launch_mode]);
549     reg_key.WriteValue(chrome::kLaunchModeValue,
550                        static_cast<DWORD>(launch_mode));
551     return launch_mode;
552   }
553
554   DWORD reg_value;
555   if (reg_key.ReadValueDW(chrome::kLaunchModeValue,
556                           &reg_value) != ERROR_SUCCESS) {
557     launch_mode = ECHUIM_DESKTOP;
558     AtlTrace("Launch mode forced by heuristics to %s\n", modes[launch_mode]);
559   } else if (reg_value >= ECHUIM_SYSTEM_LAUNCHER) {
560     AtlTrace("Invalid registry launch mode value %u\n", reg_value);
561     launch_mode = ECHUIM_DESKTOP;
562   } else {
563     launch_mode = static_cast<EC_HOST_UI_MODE>(reg_value);
564     AtlTrace("Launch mode forced by registry to %s\n", modes[launch_mode]);
565   }
566
567   launch_mode_determined = true;
568   return launch_mode;
569 }