browser_win.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. // Copyright (c) 2013 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/browser.h"
  5. #include <windows.h> // windows.h must be included first
  6. #include <atlbase.h>
  7. #include <shlobj.h>
  8. #include <shobjidl.h>
  9. #include "base/base_paths.h"
  10. #include "base/file_version_info.h"
  11. #include "base/files/file_path.h"
  12. #include "base/path_service.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/strings/stringprintf.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "base/threading/thread_restrictions.h"
  17. #include "base/win/registry.h"
  18. #include "base/win/win_util.h"
  19. #include "base/win/windows_version.h"
  20. #include "electron/electron_version.h"
  21. #include "shell/browser/ui/message_box.h"
  22. #include "shell/browser/ui/win/jump_list.h"
  23. #include "shell/common/application_info.h"
  24. #include "shell/common/native_mate_converters/string16_converter.h"
  25. #include "shell/common/skia_util.h"
  26. #include "ui/events/keycodes/keyboard_code_conversion_win.h"
  27. namespace electron {
  28. namespace {
  29. BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
  30. DWORD target_process_id = *reinterpret_cast<DWORD*>(param);
  31. DWORD process_id = 0;
  32. GetWindowThreadProcessId(hwnd, &process_id);
  33. if (process_id == target_process_id) {
  34. SetFocus(hwnd);
  35. return FALSE;
  36. }
  37. return TRUE;
  38. }
  39. bool GetProcessExecPath(base::string16* exe) {
  40. base::FilePath path;
  41. if (!base::PathService::Get(base::FILE_EXE, &path)) {
  42. LOG(ERROR) << "Error getting app exe path";
  43. return false;
  44. }
  45. *exe = path.value();
  46. return true;
  47. }
  48. bool GetProtocolLaunchPath(mate::Arguments* args, base::string16* exe) {
  49. if (!args->GetNext(exe) && !GetProcessExecPath(exe)) {
  50. return false;
  51. }
  52. // Read in optional args arg
  53. std::vector<base::string16> launch_args;
  54. if (args->GetNext(&launch_args) && !launch_args.empty())
  55. *exe = base::StringPrintf(L"\"%ls\" %ls \"%%1\"", exe->c_str(),
  56. base::JoinString(launch_args, L" ").c_str());
  57. else
  58. *exe = base::StringPrintf(L"\"%ls\" \"%%1\"", exe->c_str());
  59. return true;
  60. }
  61. // Windows treats a given scheme as an Internet scheme only if its registry
  62. // entry has a "URL Protocol" key. Check this, otherwise we allow ProgIDs to be
  63. // used as custom protocols which leads to security bugs.
  64. bool IsValidCustomProtocol(const base::string16& scheme) {
  65. if (scheme.empty())
  66. return false;
  67. base::win::RegKey cmd_key(HKEY_CLASSES_ROOT, scheme.c_str(), KEY_QUERY_VALUE);
  68. return cmd_key.Valid() && cmd_key.HasValue(L"URL Protocol");
  69. }
  70. // Windows 8 introduced a new protocol->executable binding system which cannot
  71. // be retrieved in the HKCR registry subkey method implemented below. We call
  72. // AssocQueryString with the new Win8-only flag ASSOCF_IS_PROTOCOL instead.
  73. base::string16 GetAppForProtocolUsingAssocQuery(const GURL& url) {
  74. const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
  75. if (!IsValidCustomProtocol(url_scheme))
  76. return base::string16();
  77. // Query AssocQueryString for a human-readable description of the program
  78. // that will be invoked given the provided URL spec. This is used only to
  79. // populate the external protocol dialog box the user sees when invoking
  80. // an unknown external protocol.
  81. wchar_t out_buffer[1024];
  82. DWORD buffer_size = base::size(out_buffer);
  83. HRESULT hr =
  84. AssocQueryString(ASSOCF_IS_PROTOCOL, ASSOCSTR_FRIENDLYAPPNAME,
  85. url_scheme.c_str(), NULL, out_buffer, &buffer_size);
  86. if (FAILED(hr)) {
  87. DLOG(WARNING) << "AssocQueryString failed!";
  88. return base::string16();
  89. }
  90. return base::string16(out_buffer);
  91. }
  92. base::string16 GetAppForProtocolUsingRegistry(const GURL& url) {
  93. const base::string16 url_scheme = base::ASCIIToUTF16(url.scheme());
  94. if (!IsValidCustomProtocol(url_scheme))
  95. return base::string16();
  96. // First, try and extract the application's display name.
  97. base::string16 command_to_launch;
  98. base::win::RegKey cmd_key_name(HKEY_CLASSES_ROOT, url_scheme.c_str(),
  99. KEY_READ);
  100. if (cmd_key_name.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS &&
  101. !command_to_launch.empty()) {
  102. return command_to_launch;
  103. }
  104. // Otherwise, parse the command line in the registry, and return the basename
  105. // of the program path if it exists.
  106. const base::string16 cmd_key_path = url_scheme + L"\\shell\\open\\command";
  107. base::win::RegKey cmd_key_exe(HKEY_CLASSES_ROOT, cmd_key_path.c_str(),
  108. KEY_READ);
  109. if (cmd_key_exe.ReadValue(NULL, &command_to_launch) == ERROR_SUCCESS) {
  110. base::CommandLine command_line(
  111. base::CommandLine::FromString(command_to_launch));
  112. return command_line.GetProgram().BaseName().value();
  113. }
  114. return base::string16();
  115. }
  116. bool FormatCommandLineString(base::string16* exe,
  117. const std::vector<base::string16>& launch_args) {
  118. if (exe->empty() && !GetProcessExecPath(exe)) {
  119. return false;
  120. }
  121. if (!launch_args.empty()) {
  122. *exe = base::StringPrintf(L"%ls %ls", exe->c_str(),
  123. base::JoinString(launch_args, L" ").c_str());
  124. }
  125. return true;
  126. }
  127. std::unique_ptr<FileVersionInfo> FetchFileVersionInfo() {
  128. base::FilePath path;
  129. if (base::PathService::Get(base::FILE_EXE, &path)) {
  130. base::ThreadRestrictions::ScopedAllowIO allow_io;
  131. return FileVersionInfo::CreateFileVersionInfo(path);
  132. }
  133. return std::unique_ptr<FileVersionInfo>();
  134. }
  135. } // namespace
  136. Browser::UserTask::UserTask() = default;
  137. Browser::UserTask::UserTask(const UserTask&) = default;
  138. Browser::UserTask::~UserTask() = default;
  139. void Browser::Focus(mate::Arguments* args) {
  140. // On Windows we just focus on the first window found for this process.
  141. DWORD pid = GetCurrentProcessId();
  142. EnumWindows(&WindowsEnumerationHandler, reinterpret_cast<LPARAM>(&pid));
  143. }
  144. void Browser::AddRecentDocument(const base::FilePath& path) {
  145. CComPtr<IShellItem> item;
  146. HRESULT hr = SHCreateItemFromParsingName(path.value().c_str(), NULL,
  147. IID_PPV_ARGS(&item));
  148. if (SUCCEEDED(hr)) {
  149. SHARDAPPIDINFO info;
  150. info.psi = item;
  151. info.pszAppID = GetAppUserModelID();
  152. SHAddToRecentDocs(SHARD_APPIDINFO, &info);
  153. }
  154. }
  155. void Browser::ClearRecentDocuments() {
  156. SHAddToRecentDocs(SHARD_APPIDINFO, nullptr);
  157. }
  158. void Browser::SetAppUserModelID(const base::string16& name) {
  159. electron::SetAppUserModelID(name);
  160. }
  161. bool Browser::SetUserTasks(const std::vector<UserTask>& tasks) {
  162. JumpList jump_list(GetAppUserModelID());
  163. if (!jump_list.Begin())
  164. return false;
  165. JumpListCategory category;
  166. category.type = JumpListCategory::Type::TASKS;
  167. category.items.reserve(tasks.size());
  168. JumpListItem item;
  169. item.type = JumpListItem::Type::TASK;
  170. for (const auto& task : tasks) {
  171. item.title = task.title;
  172. item.path = task.program;
  173. item.arguments = task.arguments;
  174. item.icon_path = task.icon_path;
  175. item.icon_index = task.icon_index;
  176. item.description = task.description;
  177. item.working_dir = task.working_dir;
  178. category.items.push_back(item);
  179. }
  180. jump_list.AppendCategory(category);
  181. return jump_list.Commit();
  182. }
  183. bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
  184. mate::Arguments* args) {
  185. if (protocol.empty())
  186. return false;
  187. // Main Registry Key
  188. HKEY root = HKEY_CURRENT_USER;
  189. base::string16 keyPath = L"Software\\Classes\\";
  190. // Command Key
  191. base::string16 wprotocol = base::UTF8ToUTF16(protocol);
  192. base::string16 shellPath = wprotocol + L"\\shell";
  193. base::string16 cmdPath = keyPath + shellPath + L"\\open\\command";
  194. base::win::RegKey classesKey;
  195. base::win::RegKey commandKey;
  196. if (FAILED(classesKey.Open(root, keyPath.c_str(), KEY_ALL_ACCESS)))
  197. // Classes key doesn't exist, that's concerning, but I guess
  198. // we're not the default handler
  199. return true;
  200. if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS)))
  201. // Key doesn't even exist, we can confirm that it is not set
  202. return true;
  203. base::string16 keyVal;
  204. if (FAILED(commandKey.ReadValue(L"", &keyVal)))
  205. // Default value not set, we can confirm that it is not set
  206. return true;
  207. base::string16 exe;
  208. if (!GetProtocolLaunchPath(args, &exe))
  209. return false;
  210. if (keyVal == exe) {
  211. // Let's kill the key
  212. if (FAILED(classesKey.DeleteKey(shellPath.c_str())))
  213. return false;
  214. // Let's clean up after ourselves
  215. base::win::RegKey protocolKey;
  216. base::string16 protocolPath = keyPath + wprotocol;
  217. if (SUCCEEDED(
  218. protocolKey.Open(root, protocolPath.c_str(), KEY_ALL_ACCESS))) {
  219. protocolKey.DeleteValue(L"URL Protocol");
  220. // Overwrite the default value to be empty, we can't delete it right away
  221. protocolKey.WriteValue(L"", L"");
  222. protocolKey.DeleteValue(L"");
  223. }
  224. // If now empty, delete the whole key
  225. classesKey.DeleteEmptyKey(wprotocol.c_str());
  226. return true;
  227. } else {
  228. return true;
  229. }
  230. }
  231. bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
  232. mate::Arguments* args) {
  233. // HKEY_CLASSES_ROOT
  234. // $PROTOCOL
  235. // (Default) = "URL:$NAME"
  236. // URL Protocol = ""
  237. // shell
  238. // open
  239. // command
  240. // (Default) = "$COMMAND" "%1"
  241. //
  242. // However, the "HKEY_CLASSES_ROOT" key can only be written by the
  243. // Administrator user. So, we instead write to "HKEY_CURRENT_USER\
  244. // Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
  245. // anyway, and can be written by unprivileged users.
  246. if (protocol.empty())
  247. return false;
  248. base::string16 exe;
  249. if (!GetProtocolLaunchPath(args, &exe))
  250. return false;
  251. // Main Registry Key
  252. HKEY root = HKEY_CURRENT_USER;
  253. base::string16 keyPath = base::UTF8ToUTF16("Software\\Classes\\" + protocol);
  254. base::string16 urlDecl = base::UTF8ToUTF16("URL:" + protocol);
  255. // Command Key
  256. base::string16 cmdPath = keyPath + L"\\shell\\open\\command";
  257. // Write information to registry
  258. base::win::RegKey key(root, keyPath.c_str(), KEY_ALL_ACCESS);
  259. if (FAILED(key.WriteValue(L"URL Protocol", L"")) ||
  260. FAILED(key.WriteValue(L"", urlDecl.c_str())))
  261. return false;
  262. base::win::RegKey commandKey(root, cmdPath.c_str(), KEY_ALL_ACCESS);
  263. if (FAILED(commandKey.WriteValue(L"", exe.c_str())))
  264. return false;
  265. return true;
  266. }
  267. bool Browser::IsDefaultProtocolClient(const std::string& protocol,
  268. mate::Arguments* args) {
  269. if (protocol.empty())
  270. return false;
  271. base::string16 exe;
  272. if (!GetProtocolLaunchPath(args, &exe))
  273. return false;
  274. // Main Registry Key
  275. HKEY root = HKEY_CURRENT_USER;
  276. base::string16 keyPath = base::UTF8ToUTF16("Software\\Classes\\" + protocol);
  277. // Command Key
  278. base::string16 cmdPath = keyPath + L"\\shell\\open\\command";
  279. base::win::RegKey key;
  280. base::win::RegKey commandKey;
  281. if (FAILED(key.Open(root, keyPath.c_str(), KEY_ALL_ACCESS)))
  282. // Key doesn't exist, we can confirm that it is not set
  283. return false;
  284. if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS)))
  285. // Key doesn't exist, we can confirm that it is not set
  286. return false;
  287. base::string16 keyVal;
  288. if (FAILED(commandKey.ReadValue(L"", &keyVal)))
  289. // Default value not set, we can confirm that it is not set
  290. return false;
  291. // Default value is the same as current file path
  292. return keyVal == exe;
  293. }
  294. base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
  295. // Windows 8 or above has a new protocol association query.
  296. if (base::win::GetVersion() >= base::win::Version::WIN8) {
  297. base::string16 application_name = GetAppForProtocolUsingAssocQuery(url);
  298. if (!application_name.empty())
  299. return application_name;
  300. }
  301. return GetAppForProtocolUsingRegistry(url);
  302. }
  303. bool Browser::SetBadgeCount(int count) {
  304. return false;
  305. }
  306. void Browser::SetLoginItemSettings(LoginItemSettings settings) {
  307. base::string16 keyPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
  308. base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS);
  309. if (settings.open_at_login) {
  310. base::string16 exe = settings.path;
  311. if (FormatCommandLineString(&exe, settings.args)) {
  312. key.WriteValue(GetAppUserModelID(), exe.c_str());
  313. }
  314. } else {
  315. key.DeleteValue(GetAppUserModelID());
  316. }
  317. }
  318. Browser::LoginItemSettings Browser::GetLoginItemSettings(
  319. const LoginItemSettings& options) {
  320. LoginItemSettings settings;
  321. base::string16 keyPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
  322. base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS);
  323. base::string16 keyVal;
  324. if (!FAILED(key.ReadValue(GetAppUserModelID(), &keyVal))) {
  325. base::string16 exe = options.path;
  326. if (FormatCommandLineString(&exe, options.args)) {
  327. settings.open_at_login = keyVal == exe;
  328. }
  329. }
  330. return settings;
  331. }
  332. PCWSTR Browser::GetAppUserModelID() {
  333. return GetRawAppUserModelID();
  334. }
  335. std::string Browser::GetExecutableFileVersion() const {
  336. base::FilePath path;
  337. if (base::PathService::Get(base::FILE_EXE, &path)) {
  338. base::ThreadRestrictions::ScopedAllowIO allow_io;
  339. std::unique_ptr<FileVersionInfo> version_info = FetchFileVersionInfo();
  340. return base::UTF16ToUTF8(version_info->product_version());
  341. }
  342. return ELECTRON_VERSION_STRING;
  343. }
  344. std::string Browser::GetExecutableFileProductName() const {
  345. return GetApplicationName();
  346. }
  347. bool Browser::IsEmojiPanelSupported() {
  348. // emoji picker is supported on Windows 10's Spring 2018 update & above.
  349. return base::win::GetVersion() >= base::win::Version::WIN10_RS4;
  350. }
  351. void Browser::ShowEmojiPanel() {
  352. // This sends Windows Key + '.' (both keydown and keyup events).
  353. // "SendInput" is used because Windows needs to receive these events and
  354. // open the Emoji picker.
  355. INPUT input[4] = {};
  356. input[0].type = INPUT_KEYBOARD;
  357. input[0].ki.wVk = ui::WindowsKeyCodeForKeyboardCode(ui::VKEY_COMMAND);
  358. input[1].type = INPUT_KEYBOARD;
  359. input[1].ki.wVk = ui::WindowsKeyCodeForKeyboardCode(ui::VKEY_OEM_PERIOD);
  360. input[2].type = INPUT_KEYBOARD;
  361. input[2].ki.wVk = ui::WindowsKeyCodeForKeyboardCode(ui::VKEY_COMMAND);
  362. input[2].ki.dwFlags |= KEYEVENTF_KEYUP;
  363. input[3].type = INPUT_KEYBOARD;
  364. input[3].ki.wVk = ui::WindowsKeyCodeForKeyboardCode(ui::VKEY_OEM_PERIOD);
  365. input[3].ki.dwFlags |= KEYEVENTF_KEYUP;
  366. ::SendInput(4, input, sizeof(INPUT));
  367. }
  368. void Browser::ShowAboutPanel() {
  369. base::Value dict(base::Value::Type::DICTIONARY);
  370. std::string aboutMessage = "";
  371. gfx::ImageSkia image;
  372. // grab defaults from Windows .EXE file
  373. std::unique_ptr<FileVersionInfo> exe_info = FetchFileVersionInfo();
  374. dict.SetStringKey("applicationName", exe_info->file_description());
  375. dict.SetStringKey("applicationVersion", exe_info->product_version());
  376. if (about_panel_options_.is_dict()) {
  377. dict.MergeDictionary(&about_panel_options_);
  378. }
  379. std::vector<std::string> stringOptions = {
  380. "applicationName", "applicationVersion", "copyright", "credits"};
  381. const std::string* str;
  382. for (std::string opt : stringOptions) {
  383. if ((str = dict.FindStringKey(opt))) {
  384. aboutMessage.append(*str).append("\r\n");
  385. }
  386. }
  387. if ((str = dict.FindStringKey("iconPath"))) {
  388. base::FilePath path = base::FilePath::FromUTF8Unsafe(*str);
  389. electron::util::PopulateImageSkiaRepsFromPath(&image, path);
  390. }
  391. electron::MessageBoxSettings settings = {};
  392. settings.message = aboutMessage;
  393. settings.icon = image;
  394. settings.type = electron::MessageBoxType::kInformation;
  395. electron::ShowMessageBoxSync(settings);
  396. }
  397. void Browser::SetAboutPanelOptions(const base::DictionaryValue& options) {
  398. about_panel_options_ = options.Clone();
  399. }
  400. } // namespace electron