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 "chrome/browser/platform_util.h" 6 7 #include <commdlg.h> 8 #include <dwmapi.h> 9 #include <shellapi.h> 10 #include <shlobj.h> 11 12 #include "base/bind.h" 13 #include "base/bind_helpers.h" 14 #include "base/files/file_path.h" 15 #include "base/logging.h" 16 #include "base/strings/string_util.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/win/registry.h" 19 #include "base/win/scoped_co_mem.h" 20 #include "base/win/scoped_comptr.h" 21 #include "base/win/windows_version.h" 22 #include "chrome/browser/lifetime/application_lifetime.h" 23 #include "chrome/browser/ui/host_desktop.h" 24 #include "content/public/browser/browser_thread.h" 25 #include "ui/base/win/shell.h" 26 #include "ui/gfx/native_widget_types.h" 27 #include "url/gurl.h" 28 29 using content::BrowserThread; 30 31 namespace { 32 33 void ShowItemInFolderOnFileThread(const base::FilePath& full_path) { 34 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 35 base::FilePath dir = full_path.DirName().AsEndingWithSeparator(); 36 // ParseDisplayName will fail if the directory is "C:", it must be "C:\\". 37 if (dir.empty()) 38 return; 39 40 typedef HRESULT (WINAPI *SHOpenFolderAndSelectItemsFuncPtr)( 41 PCIDLIST_ABSOLUTE pidl_Folder, 42 UINT cidl, 43 PCUITEMID_CHILD_ARRAY pidls, 44 DWORD flags); 45 46 static SHOpenFolderAndSelectItemsFuncPtr open_folder_and_select_itemsPtr = 47 NULL; 48 static bool initialize_open_folder_proc = true; 49 if (initialize_open_folder_proc) { 50 initialize_open_folder_proc = false; 51 // The SHOpenFolderAndSelectItems API is exposed by shell32 version 6 52 // and does not exist in Win2K. We attempt to retrieve this function export 53 // from shell32 and if it does not exist, we just invoke ShellExecute to 54 // open the folder thus losing the functionality to select the item in 55 // the process. 56 HMODULE shell32_base = GetModuleHandle(L"shell32.dll"); 57 if (!shell32_base) { 58 NOTREACHED() << " " << __FUNCTION__ << "(): Can't open shell32.dll"; 59 return; 60 } 61 open_folder_and_select_itemsPtr = 62 reinterpret_cast<SHOpenFolderAndSelectItemsFuncPtr> 63 (GetProcAddress(shell32_base, "SHOpenFolderAndSelectItems")); 64 } 65 if (!open_folder_and_select_itemsPtr) { 66 ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); 67 return; 68 } 69 70 base::win::ScopedComPtr<IShellFolder> desktop; 71 HRESULT hr = SHGetDesktopFolder(desktop.Receive()); 72 if (FAILED(hr)) 73 return; 74 75 base::win::ScopedCoMem<ITEMIDLIST> dir_item; 76 hr = desktop->ParseDisplayName(NULL, NULL, 77 const_cast<wchar_t *>(dir.value().c_str()), 78 NULL, &dir_item, NULL); 79 if (FAILED(hr)) 80 return; 81 82 base::win::ScopedCoMem<ITEMIDLIST> file_item; 83 hr = desktop->ParseDisplayName(NULL, NULL, 84 const_cast<wchar_t *>(full_path.value().c_str()), 85 NULL, &file_item, NULL); 86 if (FAILED(hr)) 87 return; 88 89 const ITEMIDLIST* highlight[] = { file_item }; 90 91 hr = (*open_folder_and_select_itemsPtr)(dir_item, arraysize(highlight), 92 highlight, NULL); 93 94 if (FAILED(hr)) { 95 // On some systems, the above call mysteriously fails with "file not 96 // found" even though the file is there. In these cases, ShellExecute() 97 // seems to work as a fallback (although it won't select the file). 98 if (hr == ERROR_FILE_NOT_FOUND) { 99 ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); 100 } else { 101 LPTSTR message = NULL; 102 DWORD message_length = FormatMessage( 103 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 104 0, hr, 0, reinterpret_cast<LPTSTR>(&message), 0, NULL); 105 LOG(WARNING) << " " << __FUNCTION__ 106 << "(): Can't open full_path = \"" 107 << full_path.value() << "\"" 108 << " hr = " << hr 109 << " " << reinterpret_cast<LPTSTR>(&message); 110 if (message) 111 LocalFree(message); 112 } 113 } 114 } 115 116 // Old ShellExecute crashes the process when the command for a given scheme 117 // is empty. This function tells if it is. 118 bool ValidateShellCommandForScheme(const std::string& scheme) { 119 base::win::RegKey key; 120 std::wstring registry_path = ASCIIToWide(scheme) + 121 L"\\shell\\open\\command"; 122 key.Open(HKEY_CLASSES_ROOT, registry_path.c_str(), KEY_READ); 123 if (!key.Valid()) 124 return false; 125 DWORD size = 0; 126 key.ReadValue(NULL, NULL, &size, NULL); 127 if (size <= 2) 128 return false; 129 return true; 130 } 131 132 void OpenExternalOnFileThread(const GURL& url) { 133 // Quote the input scheme to be sure that the command does not have 134 // parameters unexpected by the external program. This url should already 135 // have been escaped. 136 std::string escaped_url = url.spec(); 137 escaped_url.insert(0, "\""); 138 escaped_url += "\""; 139 140 // According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp: 141 // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in 142 // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6 143 // support URLS of 2083 chars in length, 2K is safe." 144 const size_t kMaxUrlLength = 2048; 145 if (escaped_url.length() > kMaxUrlLength) { 146 NOTREACHED(); 147 return; 148 } 149 150 if (base::win::GetVersion() < base::win::VERSION_WIN7) { 151 if (!ValidateShellCommandForScheme(url.scheme())) 152 return; 153 } 154 155 if (reinterpret_cast<ULONG_PTR>(ShellExecuteA(NULL, "open", 156 escaped_url.c_str(), NULL, NULL, 157 SW_SHOWNORMAL)) <= 32) { 158 // We fail to execute the call. We could display a message to the user. 159 // TODO(nsylvain): we should also add a dialog to warn on errors. See 160 // bug 1136923. 161 return; 162 } 163 } 164 165 } // namespace 166 167 namespace platform_util { 168 169 void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) { 170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 171 172 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) 173 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 174 175 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 176 base::Bind(&ShowItemInFolderOnFileThread, full_path)); 177 } 178 179 void OpenItem(Profile* profile, const base::FilePath& full_path) { 180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 181 182 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) 183 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 184 185 BrowserThread::PostTask( 186 BrowserThread::FILE, FROM_HERE, 187 base::Bind(base::IgnoreResult(&ui::win::OpenItemViaShell), full_path)); 188 } 189 190 void OpenExternal(Profile* profile, const GURL& url) { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 192 193 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH && 194 !url.SchemeIsHTTPOrHTTPS()) 195 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 196 197 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 198 base::Bind(&OpenExternalOnFileThread, url)); 199 } 200 201 #if !defined(USE_AURA) 202 gfx::NativeWindow GetTopLevel(gfx::NativeView view) { 203 return ::GetAncestor(view, GA_ROOT); 204 } 205 206 gfx::NativeView GetParent(gfx::NativeView view) { 207 return ::GetParent(view); 208 } 209 210 bool IsWindowActive(gfx::NativeWindow window) { 211 return ::GetForegroundWindow() == window; 212 } 213 214 void ActivateWindow(gfx::NativeWindow window) { 215 ::SetForegroundWindow(window); 216 } 217 218 bool IsVisible(gfx::NativeView view) { 219 // MSVC complains if we don't include != 0. 220 return ::IsWindowVisible(view) != 0; 221 } 222 #endif 223 224 } // namespace platform_util 225