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 LOG(WARNING) << " " << __FUNCTION__ 102 << "(): Can't open full_path = \"" 103 << full_path.value() << "\"" 104 << " hr = " << logging::SystemErrorCodeToString(hr); 105 } 106 } 107 } 108 109 // Old ShellExecute crashes the process when the command for a given scheme 110 // is empty. This function tells if it is. 111 bool ValidateShellCommandForScheme(const std::string& scheme) { 112 base::win::RegKey key; 113 std::wstring registry_path = base::ASCIIToWide(scheme) + 114 L"\\shell\\open\\command"; 115 key.Open(HKEY_CLASSES_ROOT, registry_path.c_str(), KEY_READ); 116 if (!key.Valid()) 117 return false; 118 DWORD size = 0; 119 key.ReadValue(NULL, NULL, &size, NULL); 120 if (size <= 2) 121 return false; 122 return true; 123 } 124 125 void OpenExternalOnFileThread(const GURL& url) { 126 // Quote the input scheme to be sure that the command does not have 127 // parameters unexpected by the external program. This url should already 128 // have been escaped. 129 std::string escaped_url = url.spec(); 130 escaped_url.insert(0, "\""); 131 escaped_url += "\""; 132 133 // According to Mozilla in uriloader/exthandler/win/nsOSHelperAppService.cpp: 134 // "Some versions of windows (Win2k before SP3, Win XP before SP1) crash in 135 // ShellExecute on long URLs (bug 161357 on bugzilla.mozilla.org). IE 5 and 6 136 // support URLS of 2083 chars in length, 2K is safe." 137 const size_t kMaxUrlLength = 2048; 138 if (escaped_url.length() > kMaxUrlLength) { 139 NOTREACHED(); 140 return; 141 } 142 143 if (base::win::GetVersion() < base::win::VERSION_WIN7) { 144 if (!ValidateShellCommandForScheme(url.scheme())) 145 return; 146 } 147 148 if (reinterpret_cast<ULONG_PTR>(ShellExecuteA(NULL, "open", 149 escaped_url.c_str(), NULL, NULL, 150 SW_SHOWNORMAL)) <= 32) { 151 // We fail to execute the call. We could display a message to the user. 152 // TODO(nsylvain): we should also add a dialog to warn on errors. See 153 // bug 1136923. 154 return; 155 } 156 } 157 158 } // namespace 159 160 namespace platform_util { 161 162 void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) { 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 164 165 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) 166 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 167 168 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 169 base::Bind(&ShowItemInFolderOnFileThread, full_path)); 170 } 171 172 void OpenItem(Profile* profile, const base::FilePath& full_path) { 173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 174 175 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) 176 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 177 178 BrowserThread::PostTask( 179 BrowserThread::FILE, FROM_HERE, 180 base::Bind(base::IgnoreResult(&ui::win::OpenItemViaShell), full_path)); 181 } 182 183 void OpenExternal(Profile* profile, const GURL& url) { 184 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 185 186 if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH && 187 !url.SchemeIsHTTPOrHTTPS()) 188 chrome::ActivateDesktopHelper(chrome::ASH_KEEP_RUNNING); 189 190 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 191 base::Bind(&OpenExternalOnFileThread, url)); 192 } 193 194 #if !defined(USE_AURA) 195 gfx::NativeWindow GetTopLevel(gfx::NativeView view) { 196 return ::GetAncestor(view, GA_ROOT); 197 } 198 199 gfx::NativeView GetParent(gfx::NativeView view) { 200 return ::GetParent(view); 201 } 202 203 bool IsWindowActive(gfx::NativeWindow window) { 204 return ::GetForegroundWindow() == window; 205 } 206 207 void ActivateWindow(gfx::NativeWindow window) { 208 ::SetForegroundWindow(window); 209 } 210 211 bool IsVisible(gfx::NativeView view) { 212 // MSVC complains if we don't include != 0. 213 return ::IsWindowVisible(view) != 0; 214 } 215 #endif 216 217 } // namespace platform_util 218