native_window_views_win.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. // Copyright (c) 2015 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 <dwmapi.h>
  5. #include <shellapi.h>
  6. #include "content/public/browser/browser_accessibility_state.h"
  7. #include "shell/browser/browser.h"
  8. #include "shell/browser/native_window_views.h"
  9. #include "shell/browser/ui/views/root_view.h"
  10. #include "shell/common/electron_constants.h"
  11. #include "ui/base/win/accessibility_misc_utils.h"
  12. #include "ui/display/display.h"
  13. #include "ui/display/win/screen_win.h"
  14. #include "ui/gfx/geometry/insets.h"
  15. #include "ui/gfx/geometry/resize_utils.h"
  16. #include "ui/views/widget/native_widget_private.h"
  17. // Must be included after other Windows headers.
  18. #include <UIAutomationCoreApi.h>
  19. namespace electron {
  20. namespace {
  21. // Convert Win32 WM_APPCOMMANDS to strings.
  22. const char* AppCommandToString(int command_id) {
  23. switch (command_id) {
  24. case APPCOMMAND_BROWSER_BACKWARD:
  25. return kBrowserBackward;
  26. case APPCOMMAND_BROWSER_FORWARD:
  27. return kBrowserForward;
  28. case APPCOMMAND_BROWSER_REFRESH:
  29. return "browser-refresh";
  30. case APPCOMMAND_BROWSER_STOP:
  31. return "browser-stop";
  32. case APPCOMMAND_BROWSER_SEARCH:
  33. return "browser-search";
  34. case APPCOMMAND_BROWSER_FAVORITES:
  35. return "browser-favorites";
  36. case APPCOMMAND_BROWSER_HOME:
  37. return "browser-home";
  38. case APPCOMMAND_VOLUME_MUTE:
  39. return "volume-mute";
  40. case APPCOMMAND_VOLUME_DOWN:
  41. return "volume-down";
  42. case APPCOMMAND_VOLUME_UP:
  43. return "volume-up";
  44. case APPCOMMAND_MEDIA_NEXTTRACK:
  45. return "media-nexttrack";
  46. case APPCOMMAND_MEDIA_PREVIOUSTRACK:
  47. return "media-previoustrack";
  48. case APPCOMMAND_MEDIA_STOP:
  49. return "media-stop";
  50. case APPCOMMAND_MEDIA_PLAY_PAUSE:
  51. return "media-play-pause";
  52. case APPCOMMAND_LAUNCH_MAIL:
  53. return "launch-mail";
  54. case APPCOMMAND_LAUNCH_MEDIA_SELECT:
  55. return "launch-media-select";
  56. case APPCOMMAND_LAUNCH_APP1:
  57. return "launch-app1";
  58. case APPCOMMAND_LAUNCH_APP2:
  59. return "launch-app2";
  60. case APPCOMMAND_BASS_DOWN:
  61. return "bass-down";
  62. case APPCOMMAND_BASS_BOOST:
  63. return "bass-boost";
  64. case APPCOMMAND_BASS_UP:
  65. return "bass-up";
  66. case APPCOMMAND_TREBLE_DOWN:
  67. return "treble-down";
  68. case APPCOMMAND_TREBLE_UP:
  69. return "treble-up";
  70. case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
  71. return "microphone-volume-mute";
  72. case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
  73. return "microphone-volume-down";
  74. case APPCOMMAND_MICROPHONE_VOLUME_UP:
  75. return "microphone-volume-up";
  76. case APPCOMMAND_HELP:
  77. return "help";
  78. case APPCOMMAND_FIND:
  79. return "find";
  80. case APPCOMMAND_NEW:
  81. return "new";
  82. case APPCOMMAND_OPEN:
  83. return "open";
  84. case APPCOMMAND_CLOSE:
  85. return "close";
  86. case APPCOMMAND_SAVE:
  87. return "save";
  88. case APPCOMMAND_PRINT:
  89. return "print";
  90. case APPCOMMAND_UNDO:
  91. return "undo";
  92. case APPCOMMAND_REDO:
  93. return "redo";
  94. case APPCOMMAND_COPY:
  95. return "copy";
  96. case APPCOMMAND_CUT:
  97. return "cut";
  98. case APPCOMMAND_PASTE:
  99. return "paste";
  100. case APPCOMMAND_REPLY_TO_MAIL:
  101. return "reply-to-mail";
  102. case APPCOMMAND_FORWARD_MAIL:
  103. return "forward-mail";
  104. case APPCOMMAND_SEND_MAIL:
  105. return "send-mail";
  106. case APPCOMMAND_SPELL_CHECK:
  107. return "spell-check";
  108. case APPCOMMAND_MIC_ON_OFF_TOGGLE:
  109. return "mic-on-off-toggle";
  110. case APPCOMMAND_CORRECTION_LIST:
  111. return "correction-list";
  112. case APPCOMMAND_MEDIA_PLAY:
  113. return "media-play";
  114. case APPCOMMAND_MEDIA_PAUSE:
  115. return "media-pause";
  116. case APPCOMMAND_MEDIA_RECORD:
  117. return "media-record";
  118. case APPCOMMAND_MEDIA_FAST_FORWARD:
  119. return "media-fast-forward";
  120. case APPCOMMAND_MEDIA_REWIND:
  121. return "media-rewind";
  122. case APPCOMMAND_MEDIA_CHANNEL_UP:
  123. return "media-channel-up";
  124. case APPCOMMAND_MEDIA_CHANNEL_DOWN:
  125. return "media-channel-down";
  126. case APPCOMMAND_DELETE:
  127. return "delete";
  128. case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
  129. return "dictate-or-command-control-toggle";
  130. default:
  131. return "unknown";
  132. }
  133. }
  134. // Copied from ui/views/win/hwnd_message_handler.cc
  135. gfx::ResizeEdge GetWindowResizeEdge(WPARAM param) {
  136. switch (param) {
  137. case WMSZ_BOTTOM:
  138. return gfx::ResizeEdge::kBottom;
  139. case WMSZ_TOP:
  140. return gfx::ResizeEdge::kTop;
  141. case WMSZ_LEFT:
  142. return gfx::ResizeEdge::kLeft;
  143. case WMSZ_RIGHT:
  144. return gfx::ResizeEdge::kRight;
  145. case WMSZ_TOPLEFT:
  146. return gfx::ResizeEdge::kTopLeft;
  147. case WMSZ_TOPRIGHT:
  148. return gfx::ResizeEdge::kTopRight;
  149. case WMSZ_BOTTOMLEFT:
  150. return gfx::ResizeEdge::kBottomLeft;
  151. case WMSZ_BOTTOMRIGHT:
  152. return gfx::ResizeEdge::kBottomRight;
  153. default:
  154. return gfx::ResizeEdge::kBottomRight;
  155. }
  156. }
  157. bool IsScreenReaderActive() {
  158. UINT screenReader = 0;
  159. SystemParametersInfo(SPI_GETSCREENREADER, 0, &screenReader, 0);
  160. return screenReader && UiaClientsAreListening();
  161. }
  162. } // namespace
  163. std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
  164. HHOOK NativeWindowViews::mouse_hook_ = NULL;
  165. void NativeWindowViews::Maximize() {
  166. // Only use Maximize() when window is NOT transparent style
  167. if (!transparent()) {
  168. if (IsVisible()) {
  169. widget()->Maximize();
  170. } else {
  171. widget()->native_widget_private()->Show(ui::SHOW_STATE_MAXIMIZED,
  172. gfx::Rect());
  173. NotifyWindowShow();
  174. }
  175. } else {
  176. restore_bounds_ = GetBounds();
  177. auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
  178. GetNativeWindow());
  179. SetBounds(display.work_area(), false);
  180. NotifyWindowMaximize();
  181. }
  182. }
  183. bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
  184. std::string command = AppCommandToString(command_id);
  185. NotifyWindowExecuteAppCommand(command);
  186. return false;
  187. }
  188. bool NativeWindowViews::PreHandleMSG(UINT message,
  189. WPARAM w_param,
  190. LPARAM l_param,
  191. LRESULT* result) {
  192. NotifyWindowMessage(message, w_param, l_param);
  193. // Avoid side effects when calling SetWindowPlacement.
  194. if (is_setting_window_placement_) {
  195. // Let Chromium handle the WM_NCCALCSIZE message otherwise the window size
  196. // would be wrong.
  197. // See https://github.com/electron/electron/issues/22393 for more.
  198. if (message == WM_NCCALCSIZE)
  199. return false;
  200. // Otherwise handle the message with default proc,
  201. *result = DefWindowProc(GetAcceleratedWidget(), message, w_param, l_param);
  202. // and tell Chromium to ignore this message.
  203. return true;
  204. }
  205. switch (message) {
  206. // Screen readers send WM_GETOBJECT in order to get the accessibility
  207. // object, so take this opportunity to push Chromium into accessible
  208. // mode if it isn't already, always say we didn't handle the message
  209. // because we still want Chromium to handle returning the actual
  210. // accessibility object.
  211. case WM_GETOBJECT: {
  212. if (checked_for_a11y_support_)
  213. return false;
  214. const DWORD obj_id = static_cast<DWORD>(l_param);
  215. if (obj_id != static_cast<DWORD>(OBJID_CLIENT)) {
  216. return false;
  217. }
  218. if (!IsScreenReaderActive()) {
  219. return false;
  220. }
  221. checked_for_a11y_support_ = true;
  222. auto* const axState = content::BrowserAccessibilityState::GetInstance();
  223. if (axState && !axState->IsAccessibleBrowser()) {
  224. axState->OnScreenReaderDetected();
  225. Browser::Get()->OnAccessibilitySupportChanged();
  226. }
  227. return false;
  228. }
  229. case WM_GETMINMAXINFO: {
  230. WINDOWPLACEMENT wp;
  231. wp.length = sizeof(WINDOWPLACEMENT);
  232. // We do this to work around a Windows bug, where the minimized Window
  233. // would report that the closest display to it is not the one that it was
  234. // previously on (but the leftmost one instead). We restore the position
  235. // of the window during the restore operation, this way chromium can
  236. // use the proper display to calculate the scale factor to use.
  237. if (!last_normal_placement_bounds_.IsEmpty() &&
  238. (IsVisible() || IsMinimized()) &&
  239. GetWindowPlacement(GetAcceleratedWidget(), &wp)) {
  240. wp.rcNormalPosition = last_normal_placement_bounds_.ToRECT();
  241. // When calling SetWindowPlacement, Chromium would do window messages
  242. // handling. But since we are already in PreHandleMSG this would cause
  243. // crash in Chromium under some cases.
  244. //
  245. // We work around the crash by prevent Chromium from handling window
  246. // messages until the SetWindowPlacement call is done.
  247. //
  248. // See https://github.com/electron/electron/issues/21614 for more.
  249. is_setting_window_placement_ = true;
  250. SetWindowPlacement(GetAcceleratedWidget(), &wp);
  251. is_setting_window_placement_ = false;
  252. last_normal_placement_bounds_ = gfx::Rect();
  253. }
  254. return false;
  255. }
  256. case WM_COMMAND:
  257. // Handle thumbar button click message.
  258. if (HIWORD(w_param) == THBN_CLICKED)
  259. return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param));
  260. return false;
  261. case WM_SIZING: {
  262. is_resizing_ = true;
  263. bool prevent_default = false;
  264. gfx::Rect bounds = gfx::Rect(*reinterpret_cast<RECT*>(l_param));
  265. HWND hwnd = GetAcceleratedWidget();
  266. gfx::Rect dpi_bounds = ScreenToDIPRect(hwnd, bounds);
  267. NotifyWindowWillResize(dpi_bounds, GetWindowResizeEdge(w_param),
  268. &prevent_default);
  269. if (prevent_default) {
  270. ::GetWindowRect(hwnd, reinterpret_cast<RECT*>(l_param));
  271. pending_bounds_change_.reset();
  272. return true; // Tells Windows that the Sizing is handled.
  273. }
  274. return false;
  275. }
  276. case WM_SIZE: {
  277. // Handle window state change.
  278. HandleSizeEvent(w_param, l_param);
  279. return false;
  280. }
  281. case WM_EXITSIZEMOVE: {
  282. if (is_resizing_) {
  283. NotifyWindowResized();
  284. is_resizing_ = false;
  285. }
  286. if (is_moving_) {
  287. NotifyWindowMoved();
  288. is_moving_ = false;
  289. }
  290. // If the user dragged or moved the window during one or more
  291. // calls to window.setBounds(), we want to apply the most recent
  292. // one once they are done with the move or resize operation.
  293. if (pending_bounds_change_.has_value()) {
  294. SetBounds(pending_bounds_change_.value(), false /* animate */);
  295. pending_bounds_change_.reset();
  296. }
  297. return false;
  298. }
  299. case WM_MOVING: {
  300. is_moving_ = true;
  301. bool prevent_default = false;
  302. gfx::Rect bounds = gfx::Rect(*reinterpret_cast<RECT*>(l_param));
  303. HWND hwnd = GetAcceleratedWidget();
  304. gfx::Rect dpi_bounds = ScreenToDIPRect(hwnd, bounds);
  305. NotifyWindowWillMove(dpi_bounds, &prevent_default);
  306. if (!movable_ || prevent_default) {
  307. ::GetWindowRect(hwnd, reinterpret_cast<RECT*>(l_param));
  308. pending_bounds_change_.reset();
  309. return true; // Tells Windows that the Move is handled. If not true,
  310. // frameless windows can be moved using
  311. // -webkit-app-region: drag elements.
  312. }
  313. return false;
  314. }
  315. case WM_ENDSESSION: {
  316. if (w_param) {
  317. NotifyWindowEndSession();
  318. }
  319. return false;
  320. }
  321. case WM_PARENTNOTIFY: {
  322. if (LOWORD(w_param) == WM_CREATE) {
  323. // Because of reasons regarding legacy drivers and stuff, a window that
  324. // matches the client area is created and used internally by Chromium.
  325. // This is used when forwarding mouse messages. We only cache the first
  326. // occurrence (the webview window) because dev tools also cause this
  327. // message to be sent.
  328. if (!legacy_window_) {
  329. legacy_window_ = reinterpret_cast<HWND>(l_param);
  330. }
  331. }
  332. return false;
  333. }
  334. case WM_CONTEXTMENU: {
  335. bool prevent_default = false;
  336. NotifyWindowSystemContextMenu(GET_X_LPARAM(l_param),
  337. GET_Y_LPARAM(l_param), &prevent_default);
  338. return prevent_default;
  339. }
  340. case WM_SYSCOMMAND: {
  341. // Mask is needed to account for double clicking title bar to maximize
  342. WPARAM max_mask = 0xFFF0;
  343. if (transparent() && ((w_param & max_mask) == SC_MAXIMIZE)) {
  344. return true;
  345. }
  346. return false;
  347. }
  348. case WM_INITMENU: {
  349. // This is handling the scenario where the menu might get triggered by the
  350. // user doing "alt + space" resulting in system maximization and restore
  351. // being used on transparent windows when that does not work.
  352. if (transparent()) {
  353. HMENU menu = GetSystemMenu(GetAcceleratedWidget(), false);
  354. EnableMenuItem(menu, SC_MAXIMIZE,
  355. MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  356. EnableMenuItem(menu, SC_RESTORE,
  357. MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  358. return true;
  359. }
  360. return false;
  361. }
  362. default: {
  363. return false;
  364. }
  365. }
  366. }
  367. void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) {
  368. // Here we handle the WM_SIZE event in order to figure out what is the current
  369. // window state and notify the user accordingly.
  370. switch (w_param) {
  371. case SIZE_MAXIMIZED:
  372. case SIZE_MINIMIZED: {
  373. WINDOWPLACEMENT wp;
  374. wp.length = sizeof(WINDOWPLACEMENT);
  375. if (GetWindowPlacement(GetAcceleratedWidget(), &wp)) {
  376. last_normal_placement_bounds_ = gfx::Rect(wp.rcNormalPosition);
  377. }
  378. // Note that SIZE_MAXIMIZED and SIZE_MINIMIZED might be emitted for
  379. // multiple times for one resize because of the SetWindowPlacement call.
  380. if (w_param == SIZE_MAXIMIZED &&
  381. last_window_state_ != ui::SHOW_STATE_MAXIMIZED) {
  382. last_window_state_ = ui::SHOW_STATE_MAXIMIZED;
  383. NotifyWindowMaximize();
  384. } else if (w_param == SIZE_MINIMIZED &&
  385. last_window_state_ != ui::SHOW_STATE_MINIMIZED) {
  386. last_window_state_ = ui::SHOW_STATE_MINIMIZED;
  387. NotifyWindowMinimize();
  388. }
  389. break;
  390. }
  391. case SIZE_RESTORED:
  392. switch (last_window_state_) {
  393. case ui::SHOW_STATE_MAXIMIZED:
  394. last_window_state_ = ui::SHOW_STATE_NORMAL;
  395. NotifyWindowUnmaximize();
  396. break;
  397. case ui::SHOW_STATE_MINIMIZED:
  398. if (IsFullscreen()) {
  399. last_window_state_ = ui::SHOW_STATE_FULLSCREEN;
  400. NotifyWindowEnterFullScreen();
  401. } else {
  402. last_window_state_ = ui::SHOW_STATE_NORMAL;
  403. NotifyWindowRestore();
  404. }
  405. break;
  406. default:
  407. break;
  408. }
  409. break;
  410. }
  411. }
  412. void NativeWindowViews::SetForwardMouseMessages(bool forward) {
  413. if (forward && !forwarding_mouse_messages_) {
  414. forwarding_mouse_messages_ = true;
  415. forwarding_windows_.insert(this);
  416. // Subclassing is used to fix some issues when forwarding mouse messages;
  417. // see comments in |SubclassProc|.
  418. SetWindowSubclass(legacy_window_, SubclassProc, 1,
  419. reinterpret_cast<DWORD_PTR>(this));
  420. if (!mouse_hook_) {
  421. mouse_hook_ = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0);
  422. }
  423. } else if (!forward && forwarding_mouse_messages_) {
  424. forwarding_mouse_messages_ = false;
  425. forwarding_windows_.erase(this);
  426. RemoveWindowSubclass(legacy_window_, SubclassProc, 1);
  427. if (forwarding_windows_.empty()) {
  428. UnhookWindowsHookEx(mouse_hook_);
  429. mouse_hook_ = NULL;
  430. }
  431. }
  432. }
  433. LRESULT CALLBACK NativeWindowViews::SubclassProc(HWND hwnd,
  434. UINT msg,
  435. WPARAM w_param,
  436. LPARAM l_param,
  437. UINT_PTR subclass_id,
  438. DWORD_PTR ref_data) {
  439. auto* window = reinterpret_cast<NativeWindowViews*>(ref_data);
  440. switch (msg) {
  441. case WM_MOUSELEAVE: {
  442. // When input is forwarded to underlying windows, this message is posted.
  443. // If not handled, it interferes with Chromium logic, causing for example
  444. // mouseleave events to fire. If those events are used to exit forward
  445. // mode, excessive flickering on for example hover items in underlying
  446. // windows can occur due to rapidly entering and leaving forwarding mode.
  447. // By consuming and ignoring the message, we're essentially telling
  448. // Chromium that we have not left the window despite somebody else getting
  449. // the messages. As to why this is catched for the legacy window and not
  450. // the actual browser window is simply that the legacy window somehow
  451. // makes use of these events; posting to the main window didn't work.
  452. if (window->forwarding_mouse_messages_) {
  453. return 0;
  454. }
  455. break;
  456. }
  457. }
  458. return DefSubclassProc(hwnd, msg, w_param, l_param);
  459. }
  460. LRESULT CALLBACK NativeWindowViews::MouseHookProc(int n_code,
  461. WPARAM w_param,
  462. LPARAM l_param) {
  463. if (n_code < 0) {
  464. return CallNextHookEx(NULL, n_code, w_param, l_param);
  465. }
  466. // Post a WM_MOUSEMOVE message for those windows whose client area contains
  467. // the cursor since they are in a state where they would otherwise ignore all
  468. // mouse input.
  469. if (w_param == WM_MOUSEMOVE) {
  470. for (auto* window : forwarding_windows_) {
  471. // At first I considered enumerating windows to check whether the cursor
  472. // was directly above the window, but since nothing bad seems to happen
  473. // if we post the message even if some other window occludes it I have
  474. // just left it as is.
  475. RECT client_rect;
  476. GetClientRect(window->legacy_window_, &client_rect);
  477. POINT p = reinterpret_cast<MSLLHOOKSTRUCT*>(l_param)->pt;
  478. ScreenToClient(window->legacy_window_, &p);
  479. if (PtInRect(&client_rect, p)) {
  480. WPARAM w = 0; // No virtual keys pressed for our purposes
  481. LPARAM l = MAKELPARAM(p.x, p.y);
  482. PostMessage(window->legacy_window_, WM_MOUSEMOVE, w, l);
  483. }
  484. }
  485. }
  486. return CallNextHookEx(NULL, n_code, w_param, l_param);
  487. }
  488. } // namespace electron