1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 7147 7148 7149 7150 7151 7152 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 7640 7641 7642 7643 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 7933 7934 7935 7936 7937 7938 7939 7940 7941 7942 7943 7944 7945 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966 7967 7968 7969 7970 7971 7972 7973 7974 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 8278 8279 8280 8281 8282 8283 8284 8285 8286 8287 8288 8289 8290 8291 8292 8293 8294 8295 8296 8297 8298 8299 8300 8301 8302 8303 8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 8314 8315 8316 8317 8318 8319 8320 8321 8322 8323 8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413 8414 8415 8416 8417 8418 8419 8420 8421 8422 8423 8424 8425 8426 8427 8428 8429 8430 8431 8432 8433 8434 8435 8436 8437 8438 8439 8440 8441 8442 8443 8444 8445 8446 8447 8448 8449 8450 8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464 8465 8466 8467 8468 8469 8470 8471 8472 8473 8474 8475 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485 8486 8487 8488 8489 8490 8491 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706 8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731 8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 8771 8772 8773 8774 8775 8776 8777 8778 8779 8780 8781 8782 8783 8784 8785 8786 8787 8788 8789 8790 8791 8792 8793 8794 8795 8796 8797 8798 8799 8800 8801 8802 8803 8804 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sts=2 sw=2 et cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* nsWindow - Native window management and event handling.
*
* nsWindow is organized into a set of major blocks and
* block subsections. The layout is as follows:
*
* Includes
* Variables
* nsIWidget impl.
* nsIWidget methods and utilities
* nsSwitchToUIThread impl.
* nsSwitchToUIThread methods and utilities
* Moz events
* Event initialization
* Event dispatching
* Native events
* Wndproc(s)
* Event processing
* OnEvent event handlers
* IME management and accessibility
* Transparency
* Popup hook handling
* Misc. utilities
* Child window impl.
*
* Search for "BLOCK:" to find major blocks.
* Search for "SECTION:" to find specific sections.
*
* Blocks should be split out into separate files if they
* become unmanageable.
*
* Notable related sources:
*
* nsWindowDefs.h - Definitions, macros, structs, enums
* and general setup.
* nsWindowDbg.h/.cpp - Debug related code and directives.
* nsWindowGfx.h/.cpp - Graphics and painting.
*
*/
/**************************************************************
**************************************************************
**
** BLOCK: Includes
**
** Include headers.
**
**************************************************************
**************************************************************/
#include "gfx2DGlue.h"
#include "gfxEnv.h"
#include "gfxPlatform.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Likely.h"
#include "mozilla/PreXULSkeletonUI.h"
#include "mozilla/Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ipc/MessageChannel.h"
#include <algorithm>
#include <limits>
#include "mozilla/widget/WinEventObserver.h"
#include "mozilla/widget/WinMessages.h"
#include "nsLookAndFeel.h"
#include "nsMenuPopupFrame.h"
#include "nsWindow.h"
#include "nsWindowTaskbarConcealer.h"
#include "nsAppRunner.h"
#include <appmodel.h>
#include <shellapi.h>
#include <windows.h>
#include <wtsapi32.h>
#include <process.h>
#include <commctrl.h>
#include <unknwn.h>
#include <psapi.h>
#include <rpc.h>
#include <propvarutil.h>
#include <propkey.h>
#include "mozilla/Logging.h"
#include "prtime.h"
#include "prenv.h"
#include "nsContentUtils.h"
#include "nsISupportsPrimitives.h"
#include "nsITheme.h"
#include "nsIObserverService.h"
#include "nsIScreenManager.h"
#include "imgIContainer.h"
#include "nsIFile.h"
#include "nsIRollupListener.h"
#include "nsIClipboard.h"
#include "WinMouseScrollHandler.h"
#include "nsFontMetrics.h"
#include "nsIFontEnumerator.h"
#include "nsFont.h"
#include "nsRect.h"
#include "nsThreadUtils.h"
#include "nsNativeCharsetUtils.h"
#include "nsGkAtoms.h"
#include "nsCRT.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsWidgetsCID.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsString.h"
#include "mozilla/Components.h"
#include "nsNativeThemeWin.h"
#include "nsXULPopupManager.h"
#include "nsWindowsDllInterceptor.h"
#include "nsLayoutUtils.h"
#include "nsWindowGfx.h"
#include "gfxWindowsPlatform.h"
#include "gfxDWriteFonts.h"
#include "nsPrintfCString.h"
#include "mozilla/Preferences.h"
#include "SystemTimeConverter.h"
#include "WinTaskbar.h"
#include "WidgetUtils.h"
#include "WinWindowOcclusionTracker.h"
#include "nsIWidgetListener.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/widget/nsAutoRollup.h"
#include "mozilla/widget/PlatformWidgetTypes.h"
#include "mozilla/widget/Screen.h"
#include "nsStyleConsts.h"
#include "nsBidiKeyboard.h"
#include "nsStyleConsts.h"
#include "gfxConfig.h"
#include "InProcessWinCompositorWidget.h"
#include "InputDeviceUtils.h"
#include "ScreenHelperWin.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsNativeAppSupportWin.h"
#include "nsIGfxInfo.h"
#include "nsUXThemeConstants.h"
#include "KeyboardLayout.h"
#include "nsNativeDragTarget.h"
#include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
#include <zmouse.h>
#include <richedit.h>
#ifdef ACCESSIBILITY
# ifdef DEBUG
# include "mozilla/a11y/Logging.h"
# endif
# include "mozilla/a11y/Compatibility.h"
# include "oleidl.h"
# include <uiautomation.h>
# include <winuser.h>
# include "nsAccessibilityService.h"
# include "mozilla/a11y/DocAccessible.h"
# include "mozilla/a11y/LazyInstantiator.h"
# include "mozilla/a11y/Platform.h"
# if !defined(WINABLEAPI)
# include <winable.h>
# endif // !defined(WINABLEAPI)
#endif
#include "WindowsUIUtils.h"
#include "nsWindowDefs.h"
#include "nsCrashOnException.h"
#include "nsIContent.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "WinIMEHandler.h"
#include "npapi.h"
#include <d3d11.h>
// ERROR from wingdi.h (below) gets undefined by some code.
// #define ERROR 0
// #define RGN_ERROR ERROR
#define ERROR 0
#if !defined(SM_CONVERTIBLESLATEMODE)
# define SM_CONVERTIBLESLATEMODE 0x2003
#endif
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "InputData.h"
#include "mozilla/TaskController.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "DirectManipulationOwner.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::plugins;
/**************************************************************
**************************************************************
**
** BLOCK: Variables
**
** nsWindow Class static initializations and global variables.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: nsWindow statics
*
**************************************************************/
static const wchar_t kUser32LibName[] = L"user32.dll";
uint32_t nsWindow::sInstanceCount = 0;
bool nsWindow::sIsOleInitialized = false;
MOZ_CONSTINIT nsIWidget::Cursor nsWindow::sCurrentCursor = {};
nsWindow* nsWindow::sCurrentWindow = nullptr;
bool nsWindow::sJustGotDeactivate = false;
bool nsWindow::sJustGotActivate = false;
bool nsWindow::sIsInMouseCapture = false;
// Urgent-message reentrancy depth for the static `WindowProc` callback.
//
// Three unfortunate facts collide:
//
// 𝛼) Some messages must be processed promptly. If not, Windows will leave the
// receiving window in an intermediate, and potentially unusable, state until
// the WindowProc invocation that is handling it returns.
//
// 𝛽) Some messages have indefinitely long processing time. These are mostly
// messages which may cause us to enter a nested modal loop (via
// `SpinEventLoopUntil` or similar).
//
// 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
// reentrantly reinvoked from the kernel while we're blocking _on_ the
// kernel, even briefly, during processing of other messages. (Relevant
// search term: `KeUserModeCallback`.)
//
// The nightmare scenario, then, is that during processing of an 𝛼-message, we
// briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
// takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
// bug 1842170.)
//
// There is little we can do to prevent the first half of this scenario. 𝛼) and
// 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
// need to make blocking calls to process 𝛼-messages. (We may not even be aware
// that we're making such calls, if they're undocumented implementation details
// of another API.)
//
// In an ideal world, WindowProc would always return promptly (or at least in
// bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
// states would instead be implemented in async fashion. In practice, that's far
// easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
// al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
// cutting architectural tasks, each of potentially unbounded scope. For now,
// and for the foreseeable future, we're stuck with them.
//
// We therefore simply punt. More specifically: if a known 𝛽-message jumps the
// queue to come in while we're in the middle of processing a known 𝛼-message,
// we:
// * properly queue the message for processing later;
// * respond to the 𝛽-message as though we actually had processed it; and
// * just hope that it can wait until we get around to it.
//
// The word "known" requires a bit of justification. There is no canonical set
// of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
// can't safely assume that all messages are 𝛼-messages, as that could cause
// 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
// event loop is active. We also can't assume all messages are 𝛽-messages,
// since one 𝛼-message jumping the queue while processing another 𝛼-message is
// part of normal and required operation for windowed Windows applications.
//
// So we simply add messages to those sets as we identify them. (Or, preferably,
// rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
//
// ---
//
// The actual value of `sDepth` is the number of active invocations of
// `WindowProc` that are processing known 𝛼-messages.
size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
// Hook Data Members for Dropdowns. sProcessHook Tells the
// hook methods whether they should be processing the hook
// messages.
HHOOK nsWindow::sMsgFilterHook = nullptr;
HHOOK nsWindow::sCallProcHook = nullptr;
HHOOK nsWindow::sCallMouseHook = nullptr;
bool nsWindow::sProcessHook = false;
UINT nsWindow::sRollupMsgId = 0;
HWND nsWindow::sRollupMsgWnd = nullptr;
UINT nsWindow::sHookTimerId = 0;
bool nsWindow::sIsRestoringSession = false;
bool nsWindow::sTouchInjectInitialized = false;
InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
static SystemTimeConverter<DWORD>& TimeConverter() {
static SystemTimeConverter<DWORD> timeConverterSingleton;
return timeConverterSingleton;
}
static const wchar_t* GetMainWindowClass();
static const wchar_t* ChooseWindowClass(mozilla::widget::WindowType);
// This method registers the given window class, and returns the class name.
static void RegisterWindowClass(const wchar_t* aClassName, UINT aExtraStyle,
LPWSTR aIconID);
// Global event hook for window cloaking. Never deregistered.
// - `Nothing` if not yet set.
// - `Some(nullptr)` if no attempt should be made to set it.
static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
namespace mozilla {
class CurrentWindowsTimeGetter {
public:
explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
DWORD GetCurrentTime() const { return ::GetTickCount(); }
void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
DWORD currentTime = GetCurrentTime();
if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
// There's already one inflight with this timestamp. Don't
// send a duplicate.
return;
}
sBackwardsSkewStamp = Some(aNow);
sLastPostTime = currentTime;
static_assert(sizeof(WPARAM) >= sizeof(DWORD),
"Can't fit a DWORD in a WPARAM");
::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
}
static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
TimeStamp* aOutSkewStamp) {
if (aPostTime != sLastPostTime) {
// The SKEWFIX message is stale; we've sent a new one since then.
// Ignore this one.
return false;
}
MOZ_ASSERT(sBackwardsSkewStamp);
*aOutSkewStamp = sBackwardsSkewStamp.value();
sBackwardsSkewStamp = Nothing();
return true;
}
private:
static Maybe<TimeStamp> sBackwardsSkewStamp;
static DWORD sLastPostTime;
HWND mWnd;
};
Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
} // namespace mozilla
/**************************************************************
*
* SECTION: globals variables
*
**************************************************************/
static const char* sScreenManagerContractID =
"@mozilla.org/gfx/screenmanager;1";
extern mozilla::LazyLogModule gWindowsLog;
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
// General purpose user32.dll hook object
MOZ_RUNINIT static WindowsDllInterceptor sUser32Intercept;
// When the client area is extended out into the default window frame area,
// this is the minimum amount of space along the edge of resizable windows
// we will always display a resize cursor in, regardless of the underlying
// content.
static const int32_t kResizableBorderMinSize = 3;
// Getting this object from the window server can be expensive. Keep it
// around, also get it off the main thread. (See bug 1640852)
StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
static bool gInitializedVirtualDesktopManager = false;
// We should never really try to accelerate windows bigger than this. In some
// cases this might lead to no D3D9 acceleration where we could have had it
// but D3D9 does not reliably report when it supports bigger windows. 8192
// is as safe as we can get, we know at least D3D10 hardware always supports
// this, other hardware we expect to report correctly in D3D9.
#define MAX_ACCELERATED_DIMENSION 8192
// On window open (as well as after), Windows has an unfortunate habit of
// sending rather a lot of WM_NCHITTEST messages. Because we have to do point
// to DOM target conversions for these, we cache responses for a given
// coordinate this many milliseconds:
#define HITTEST_CACHE_LIFETIME_MS 50
/**
* Used to prevent dispatching eMouseMove events that do not originate from user
* input.
*/
class nsWindow::LastMouseMoveData {
public:
/**
* Return true if eMouseMove whose point is aPoint, input source is
* aInputSource and pointerId is aPointerId should not be dispatched to avoid
* unexpected behavior in content.
*
* @param aPoint The ref-point where the new mouse move occurred.
* @param aInputSource The input source of the mouse move event.
* @param aPointerId The pointerId of the mouse move event. If there is
* no specific one because of a mouse input, specify 0
* which won't be referred anyway.
* @return true if the event should not cause eMouseMove event in the content.
*/
template <typename POINTOrLayoutDeviceIntPoint>
[[nodiscard]] static bool ShouldIgnoreMouseMoveOf(
const POINTOrLayoutDeviceIntPoint& aPoint, uint16_t aInputSource,
uint32_t aPointerId) {
return sInstance.ShouldIgnoreMouseMoveOfImpl(aPoint, aInputSource,
aPointerId);
}
/**
* Forget the last mouse move point caused by both mouse and non-mouse.
*/
static void Clear() { sInstance.ClearImpl(); }
/**
* Called when nsWindow will dispatch an eMouseMove event.
*
* @param aPoint The ref-point where the native mouse move occurred.
* @param aInputSource The input source of the mouse move event.
* @param aPointerId The pointerId of the mouse move event. If there is
* no specific one because of a mouse input, specify 0
* which won't be referred anyway.
*/
static void WillDispatchMouseMoveOf(const LayoutDeviceIntPoint& aPoint,
uint16_t aInputSource,
uint32_t aPointerId) {
sInstance.WillDispatchMouseMoveOfImpl(aPoint, aInputSource, aPointerId);
}
private:
LastMouseMoveData() = default;
template <typename POINTOrLayoutDeviceIntPoint>
[[nodiscard]] bool ShouldIgnoreMouseMoveOfImpl(
const POINTOrLayoutDeviceIntPoint& aPoint, uint16_t aInputSource,
uint32_t aPointerId) const {
// Suppress mouse moves caused by widget creation which is fired at same
// screen position with the last mouse position. And also we should ignore
// odd mouse move events which are caused by some tablet drivers. They try
// to restore the mouse position and restore the pen position again
// continuously. We don't want such events for avoiding the mouse cursor to
// flicker, avoiding to dispatching synthesized mouse/pointer boundary
// events and avoiding to switch `:hover` state because they waste a lot of
// CPU resource. So, let's ignore native mouse move events which occurs at
// the same position of the last point with the same pointer.
return PointsEqual(LastPoint(aInputSource, aPointerId), aPoint);
}
void ClearImpl() {
mLastPointByMouse.reset();
mLastPointAndPointerIdByNonMouse.reset();
}
void WillDispatchMouseMoveOfImpl(const LayoutDeviceIntPoint& aPoint,
uint16_t aInputSource, uint32_t aPointerId) {
POINT& lastPoint = [&]() -> POINT& {
if (IsMouse(aInputSource)) {
if (mLastPointByMouse.isNothing()) {
mLastPointByMouse.emplace();
}
return mLastPointByMouse.ref();
}
if (mLastPointAndPointerIdByNonMouse.isNothing()) {
mLastPointAndPointerIdByNonMouse.emplace();
}
mLastPointAndPointerIdByNonMouse->mPointerId = aPointerId;
return mLastPointAndPointerIdByNonMouse->mPoint;
}();
lastPoint.x = aPoint.x.value;
lastPoint.y = aPoint.y.value;
}
/**
* Return true if aInputSource is "mouse".
*/
[[nodiscard]] static bool IsMouse(uint16_t aInputSource) {
return aInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE;
}
/**
* Return true if aPoint and aOtherPoint are the same point.
*/
template <typename PointType>
[[nodiscard]] static bool PointsEqual(const Maybe<POINT>& aPoint,
const PointType& aOtherPoint);
/**
* Return the last mouse move event position of aPointerId if aInputSource is
* not "mouse" or of the mouse if aInputSource is "mouse".
*/
[[nodiscard]] Maybe<POINT> LastPoint(uint16_t aInputSource,
uint32_t aPointerId) const {
return IsMouse(aInputSource)
? mLastPointByMouse
: (IsLastNonMousePointerId(aPointerId)
? Some(mLastPointAndPointerIdByNonMouse->mPoint)
: Nothing());
}
/**
* Return true if aPointerId is same as the last non-mouse pointerId.
*/
[[nodiscard]] bool IsLastNonMousePointerId(uint32_t aPointerId) const {
return mLastPointAndPointerIdByNonMouse &&
mLastPointAndPointerIdByNonMouse->mPointerId == aPointerId;
}
// We don't need to take care of pointerId of mouse because it's ignored by
// PresShell, PointerEventHandler and EventStateManager to handle mouse
// boundary events and CSS hover state.
Maybe<POINT> mLastPointByMouse;
// We need to manage the last non-mouse pointer location and pointerId as a
// pair because pointerId is meaningful for the non-mouse input sources.
struct LastNonMousePointerMoveData {
POINT mPoint = {0};
uint32_t mPointerId = 0;
};
Maybe<LastNonMousePointerMoveData> mLastPointAndPointerIdByNonMouse;
static LastMouseMoveData sInstance;
};
template <>
bool nsWindow::LastMouseMoveData::PointsEqual(const Maybe<POINT>& aPoint,
const POINT& aOtherPoint) {
return aPoint.isSome() && aPoint->x == aOtherPoint.x &&
aPoint->y == aOtherPoint.y;
}
template <>
bool nsWindow::LastMouseMoveData::PointsEqual(
const Maybe<POINT>& aPoint, const LayoutDeviceIntPoint& aOtherPoint) {
return aPoint.isSome() && aPoint->x == aOtherPoint.x.value &&
aPoint->y == aOtherPoint.y.value;
}
nsWindow::LastMouseMoveData nsWindow::LastMouseMoveData::sInstance;
#if defined(ACCESSIBILITY)
namespace mozilla {
/**
* Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
* injecting tiptsf.dll. The touchscreen process then posts registered messages
* to our main thread. The tiptsf hook picks up those registered messages and
* uses them as commands, some of which call into UIA, which then sends
* WM_GETOBJECT to us.
*
* We can get ahead of this by installing our own thread-local WH_GETMESSAGE
* hook. Since thread-local hooks are called ahead of global hooks, we will
* see these registered messages before tiptsf does. At this point we can then
* raise a flag that blocks a11y before invoking CallNextHookEx which will then
* invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
* flag by calling TIPMessageHandler::IsA11yBlocked().
*
* For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
* function that also calls into UIA.
*/
class TIPMessageHandler {
public:
~TIPMessageHandler() {
if (mHook) {
::UnhookWindowsHookEx(mHook);
}
}
static void Initialize() {
if (sInstance) {
return;
}
sInstance = new TIPMessageHandler();
ClearOnShutdown(&sInstance);
}
static bool IsA11yBlocked() {
if (!sInstance) {
return false;
}
return sInstance->mA11yBlockCount > 0;
}
private:
TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
MOZ_ASSERT(NS_IsMainThread());
// Registered messages used by tiptsf
mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
::GetCurrentThreadId());
MOZ_ASSERT(mHook);
if (!sSendMessageTimeoutWStub) {
sUser32Intercept.Init("user32.dll");
DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
MOZ_ASSERT(hooked);
}
}
class MOZ_RAII A11yInstantiationBlocker {
public:
A11yInstantiationBlocker() {
if (!TIPMessageHandler::sInstance) {
return;
}
++TIPMessageHandler::sInstance->mA11yBlockCount;
} // namespace mozilla
~A11yInstantiationBlocker() {
if (!TIPMessageHandler::sInstance) {
return;
}
MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
--TIPMessageHandler::sInstance->mA11yBlockCount;
}
};
friend class A11yInstantiationBlocker;
static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
if (aCode < 0 || !sInstance) {
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
MSG* msg = reinterpret_cast<MSG*>(aLParam);
UINT& msgCode = msg->message;
for (uint32_t i = 0; i < std::size(sInstance->mMessages); ++i) {
if (msgCode == sInstance->mMessages[i]) {
A11yInstantiationBlocker block;
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
}
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
WPARAM aWParam, LPARAM aLParam,
UINT aFlags, UINT aTimeout,
PDWORD_PTR aMsgResult) {
// We don't want to handle this unless the message is a WM_GETOBJECT that we
// want to block, and the aHwnd is a nsWindow that belongs to the current
// (i.e., main) thread.
if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
(static_cast<LONG>(aLParam) != OBJID_CLIENT &&
static_cast<LONG>(aLParam) != UiaRootObjectId) ||
!::NS_IsMainThread() || !WinUtils::GetNSWindowPtr(aHwnd) ||
!IsA11yBlocked()) {
return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
aTimeout, aMsgResult);
}
// In this case we want to fake the result that would happen if we had
// decided not to handle WM_GETOBJECT in our WndProc. We hand the message
// off to DefWindowProc to accomplish this.
*aMsgResult = static_cast<DWORD_PTR>(
::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
return static_cast<LRESULT>(TRUE);
}
static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
sSendMessageTimeoutWStub;
static StaticAutoPtr<TIPMessageHandler> sInstance;
HHOOK mHook;
UINT mMessages[7];
uint32_t mA11yBlockCount;
};
WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
TIPMessageHandler::sSendMessageTimeoutWStub;
StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
} // namespace mozilla
#endif // defined(ACCESSIBILITY)
namespace mozilla {
// This task will get the VirtualDesktopManager from the generic thread pool
// since doing this on the main thread on startup causes performance issues.
//
// See bug 1640852.
//
// This should be fine and should not require any locking, as when the main
// thread will access it, if it races with this function it will either find
// it to be null or to have a valid value.
class InitializeVirtualDesktopManagerTask : public Task {
public:
InitializeVirtualDesktopManagerTask()
: Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
bool GetName(nsACString& aName) override {
aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
return true;
}
#endif
virtual TaskResult Run() override {
RefPtr<IVirtualDesktopManager> desktopManager;
HRESULT hr = ::CoCreateInstance(
CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
__uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
if (FAILED(hr)) {
return TaskResult::Complete;
}
gVirtualDesktopManager = desktopManager;
return TaskResult::Complete;
}
};
// Ground-truth query: does Windows claim the window is cloaked right now?
static bool IsCloaked(HWND hwnd) {
DWORD cloakedState;
HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
sizeof(cloakedState));
if (FAILED(hr)) {
MOZ_LOG(sCloakingLog, LogLevel::Warning,
("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
return false;
}
return cloakedState != 0;
}
} // namespace mozilla
/**************************************************************
**************************************************************
**
** BLOCK: nsIWidget impl.
**
** nsIWidget interface implementation, broken down into
** sections.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: nsWindow construction and destruction
*
**************************************************************/
nsWindow::nsWindow()
: nsIWidget(BorderStyle::Default),
mFrameState(std::in_place, this),
mPIPWindow(false),
mMicaBackdrop(false),
mLastPaintEndTime(TimeStamp::Now()),
mCachedHitTestTime(TimeStamp::Now()),
mSizeConstraintsScale(GetDefaultScale().scale) {
if (!gInitializedVirtualDesktopManager) {
TaskController::Get()->AddTask(
MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
gInitializedVirtualDesktopManager = true;
}
// Global initialization
if (!sInstanceCount) {
// Global app registration id for Win7 and up. See
// WinTaskbar.cpp for details.
// MSIX packages explicitly do not support setting the appid from within
// the app, as it is set in the package manifest instead.
if (!WinUtils::HasPackageIdentity()) {
mozilla::widget::WinTaskbar::RegisterAppUserModelID();
}
if (!StaticPrefs::ui_key_layout_load_when_first_needed()) {
KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
}
#if defined(ACCESSIBILITY)
mozilla::TIPMessageHandler::Initialize();
#endif // defined(ACCESSIBILITY)
if (SUCCEEDED(::OleInitialize(nullptr))) {
sIsOleInitialized = true;
}
NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
MouseScrollHandler::Initialize();
RedirectedKeyDownMessageManager::Forget();
} // !sInstanceCount
sInstanceCount++;
}
nsWindow::~nsWindow() {
mInDtor = true;
// If the widget was released without calling Destroy() then the native window
// still exists, and we need to destroy it. Destroy() will early-return if it
// was already called. In any case it is important to call it before
// destroying mPresentLock (cf. 1156182).
Destroy();
// Free app icon resources. This must happen after `OnDestroy` (see bug
// 708033).
if (mIconSmall) ::DestroyIcon(mIconSmall);
if (mIconBig) ::DestroyIcon(mIconBig);
sInstanceCount--;
// Global shutdown
if (sInstanceCount == 0) {
sCurrentCursor = {};
if (sIsOleInitialized) {
// When we reach here, IMEHandler::Terminate() should've already been
// called because it causes releasing the last nsWindow instance.
// However, it **could** occur that we are shutting down without giving
// IME focus, but we need to release TSF objects before the following
// ::OleUninitialize() call. Fortunately, it's fine to call the method
// twice so that we can always call it here.
IMEHandler::Terminate();
::OleFlushClipboard();
::OleUninitialize();
sIsOleInitialized = false;
}
}
NS_IF_RELEASE(mNativeDragTarget);
}
/**************************************************************
*
* SECTION: nsIWidget::Create, nsIWidget::Destroy
*
* Creating and destroying windows for this widget.
*
**************************************************************/
void nsWindow::SendAnAPZEvent(InputData& aEvent) {
LRESULT popupHandlingResult;
if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
// We need to consume the event after using it to roll up the popup(s).
return;
}
if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
// Give the swipe tracker a first pass at the event. If a new pan gesture
// has been started since the beginning of the swipe, the swipe tracker
// will know to ignore the event.
nsEventStatus status =
mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
if (status == nsEventStatus_eConsumeNoDefault) {
return;
}
}
APZEventResult result;
if (mAPZC) {
result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
}
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return;
}
MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
aEvent.mInputType == PINCHGESTURE_INPUT);
if (aEvent.mInputType == PANGESTURE_INPUT) {
PanGestureInput& panInput = aEvent.AsPanGestureInput();
WidgetWheelEvent event = panInput.ToWidgetEvent(this);
if (!mAPZC) {
if (MayStartSwipeForNonAPZ(panInput)) {
return;
}
} else {
event = MayStartSwipeForAPZ(panInput, result);
}
ProcessUntransformedAPZEvent(&event, result);
return;
}
PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
ProcessUntransformedAPZEvent(&event, result);
}
void nsWindow::RecreateDirectManipulationIfNeeded() {
DestroyDirectManipulation();
if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
return;
}
if (!(StaticPrefs::apz_allow_zooming() ||
StaticPrefs::apz_windows_use_direct_manipulation()) ||
StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
return;
}
mDmOwner = MakeUnique<DirectManipulationOwner>(this);
LayoutDeviceIntRect bounds = mBounds;
mDmOwner->Init(bounds);
}
void nsWindow::ResizeDirectManipulationViewport() {
if (mDmOwner) {
LayoutDeviceIntRect bounds = mBounds;
mDmOwner->ResizeViewport(bounds);
}
}
void nsWindow::DestroyDirectManipulation() {
if (mDmOwner) {
mDmOwner->Destroy();
mDmOwner.reset();
}
}
namespace mozilla::widget {
// A mask specifying the window-styles associated with window-chrome.
constexpr static const WindowStyles kChromeStylesMask{
.style = WS_CAPTION | WS_THICKFRAME,
.ex = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE |
WS_EX_STATICEDGE,
};
WindowStyles WindowStyles::FromHWND(HWND aWnd) {
return {.style = ::GetWindowLongPtrW(aWnd, GWL_STYLE),
.ex = ::GetWindowLongPtrW(aWnd, GWL_EXSTYLE)};
}
void SetWindowStyles(HWND aWnd, const WindowStyles& aStyles) {
VERIFY_WINDOW_STYLE(aStyles.style);
::SetWindowLongPtrW(aWnd, GWL_STYLE, aStyles.style);
::SetWindowLongPtrW(aWnd, GWL_EXSTYLE, aStyles.ex);
}
} // namespace mozilla::widget
// Create the proper widget
nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect,
const widget::InitData& aInitData) {
// Historical note: there was once some belief and/or intent that nsWindows
// could be created on arbitrary threads, and this may still be reflected in
// some comments.
MOZ_ASSERT(NS_IsMainThread());
// Ensure that the hidden window exists, so that broadcast Windows messages
// (WM_FONTCHANGE et al.) are received and processed.
WinEventWindow::Ensure();
MOZ_DIAGNOSTIC_ASSERT(aInitData.mWindowType != WindowType::Invisible);
mBounds = aRect;
// Ensure that the toolkit is created.
nsToolkit::GetToolkit();
BaseCreate(aParent, aInitData);
HWND parent =
aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
mIsRTL = aInitData.mRTL;
mPIPWindow = aInitData.mPIPWindow;
mOpeningAnimationSuppressed = aInitData.mIsAnimationSuppressed;
mAlwaysOnTop = aInitData.mAlwaysOnTop;
mIsAlert = aInitData.mIsAlert;
mResizable = aInitData.mResizable;
Styles desiredStyles{
.style = static_cast<LONG_PTR>(WindowStyle()),
.ex = static_cast<LONG_PTR>(WindowExStyle()),
};
if (mWindowType != WindowType::Popup) {
// See if the caller wants to explicitly set clip children and clip siblings
if (aInitData.mClipChildren) {
desiredStyles.style |= WS_CLIPCHILDREN;
} else {
desiredStyles.style &= ~WS_CLIPCHILDREN;
}
if (aInitData.mClipSiblings) {
desiredStyles.style |= WS_CLIPSIBLINGS;
}
}
const wchar_t* className = ChooseWindowClass(mWindowType);
// Take specific actions when creating the first top-level window
static bool sFirstTopLevelWindowCreated = false;
if (aInitData.mWindowType == WindowType::TopLevel && !aParent &&
!sFirstTopLevelWindowCreated) {
sFirstTopLevelWindowCreated = true;
mWnd = ConsumePreXULSkeletonUIHandle();
if (mWnd) {
MOZ_ASSERT(desiredStyles.style == kPreXULSkeletonUIWindowStyle,
"The skeleton UI window style should match the expected "
"style for the first window created");
MOZ_ASSERT(desiredStyles.ex == kPreXULSkeletonUIWindowStyleEx,
"The skeleton UI window extended style should match the "
"expected extended style for the first window created");
MOZ_ASSERT(
::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
"The skeleton UI window should be created on the same thread as "
"other windows");
mIsShowingPreXULSkeletonUI = true;
// If we successfully consumed the pre-XUL skeleton UI, just update
// our internal state to match what is currently being displayed.
mIsVisible = true;
mIsCloaked = mozilla::IsCloaked(mWnd);
mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
mBounds = mLastPaintBounds = GetBounds();
// Reset the WNDPROC for this window and its whole class, as we had
// to use our own WNDPROC when creating the the skeleton UI window.
::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(
WinUtils::NonClientDpiScalingDefWindowProcW));
::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
reinterpret_cast<LONG_PTR>(
WinUtils::NonClientDpiScalingDefWindowProcW));
}
}
if (!mWnd) {
mWnd =
::CreateWindowExW(desiredStyles.ex, className, L"", desiredStyles.style,
aRect.X(), aRect.Y(), aRect.Width(), aRect.Height(),
parent, nullptr, nsToolkit::mDllInstance, nullptr);
if (!mWnd) {
NS_WARNING("nsWindow CreateWindowEx failed.");
return NS_ERROR_FAILURE;
}
}
{
// Some of the chrome mask window styles can be added implicitly by
// CreateWindowEx, but we really don't want that.
// To be safe, only deal with those bits for now, instead of just
// overriding with extendedStyle or style.
// This can happen with non-native alert windows for example.
const auto actualStyles = Styles::FromHWND(mWnd);
auto newStyles = (actualStyles & ~kChromeStylesMask) |
(desiredStyles & kChromeStylesMask);
if (newStyles != actualStyles) {
SetWindowStyles(mWnd, newStyles);
}
}
if (!sWinCloakEventHook) {
MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
// C++03 lambda approximation until P2173R1 is available (-std=c++2b)
struct StdcallLambda {
static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime) {
const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
nsWindow::OnCloakEvent(hwnd, isCloaked);
}
};
const HWINEVENTHOOK hook = ::SetWinEventHook(
EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
&StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
sWinCloakEventHook = Some(hook);
if (!hook) {
const DWORD err = ::GetLastError();
MOZ_LOG(sCloakingLog, LogLevel::Error,
("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
err));
}
}
{
// Although permanent Private Browsing mode is indeed Private Browsing,
// we choose to make it look like regular Firefox in terms of the icon
// it uses (which also means we shouldn't use the Private Browsing
// AUMID).
bool usePrivateAumid =
Preferences::GetBool("browser.privateWindowSeparation.enabled", true) &&
aInitData.mIsPrivate &&
!StaticPrefs::browser_privatebrowsing_autostart();
RefPtr<IPropertyStore> pPropStore;
if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
getter_AddRefs(pPropStore)))) {
PROPVARIANT pv;
nsAutoString aumid;
// Make sure we're using the correct AUMID so that taskbar
// grouping works properly
(void)NS_WARN_IF(!mozilla::widget::WinTaskbar::GenerateAppUserModelID(
aumid, usePrivateAumid));
if (!usePrivateAumid && widget::WinUtils::HasPackageIdentity()) {
// On MSIX we should always have a provided process AUMID
// that we can explicitly assign to a regular window.
UINT32 maxLength = MAX_PATH;
aumid.SetLength(maxLength);
(void)NS_WARN_IF(
GetCurrentApplicationUserModelId(&maxLength, aumid.get()));
}
if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
pPropStore->Commit();
}
PropVariantClear(&pv);
}
}
HICON icon = ::LoadIconW(
::GetModuleHandleW(nullptr),
MAKEINTRESOURCEW(usePrivateAumid ? IDI_PBMODE : IDI_APPICON));
SetBigIcon(icon);
SetSmallIcon(icon);
}
// If mDefaultScale is set before mWnd has been set, it will have the scale of
// the primary monitor, rather than the monitor that the window is actually
// on. For non-popup windows this gets corrected by the WM_DPICHANGED message
// which resets mDefaultScale, but for popup windows we don't reset
// mDefaultScale on that message. In order to ensure that popup windows
// spawned on a non-primary monitor end up with the correct scale, we reset
// mDefaultScale here so that it gets recomputed using the correct monitor now
// that we have a mWnd.
mDefaultScale = -1.0;
if (mIsRTL) {
DWORD dwAttribute = TRUE;
DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
sizeof dwAttribute);
}
// Default to the system color scheme unless getting told otherwise.
SetColorScheme(Nothing());
if (mOpeningAnimationSuppressed) {
SuppressAnimation(true);
}
if (mAlwaysOnTop) {
::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
if (MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
// Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
//
// We create two zero-sized windows as descendants of the top-level window,
// like so:
//
// Top-level window (MozillaWindowClass)
// FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
// FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
//
// We need to have the middle window, otherwise the Trackpoint driver
// will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
// sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
// window hierarchy until they are handled by nsWindow::WindowProc.
// WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
// but these do not propagate automatically, so we have the window
// procedure pretend that they were dispatched to the top-level window
// instead.
//
// The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
// is given below so that it catches the Trackpoint driver's heuristics.
HWND scrollContainerWnd = ::CreateWindowW(
className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
HWND scrollableWnd = ::CreateWindowW(
className, L"FAKETRACKPOINTSCROLLABLE",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
// Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
// WindowProcInternal can distinguish it from the top-level window
// easily.
::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
// Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
// old window procedure in its "user data".
WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
}
// We will start receiving native events after associating with our native
// window. We will also become the output of WinUtils::GetNSWindowPtr for that
// window.
if (!AssociateWithNativeWindow()) {
return NS_ERROR_FAILURE;
}
// Starting with Windows XP, a process always runs within a terminal services
// session. In order to play nicely with RDP, fast user switching, and the
// lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
// our HWND in order to receive this message.
DebugOnly<BOOL> wtsRegistered =
::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
mDefaultIMC.Init(this);
IMEHandler::InitInputContext(this, mInputContext);
static bool a11yPrimed = false;
if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
a11yPrimed = true;
if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
}
}
RecreateDirectManipulationIfNeeded();
return NS_OK;
}
void nsWindow::LocalesChanged() {
bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
if (mIsRTL != isRTL) {
DWORD dwAttribute = isRTL;
DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
sizeof dwAttribute);
mIsRTL = isRTL;
}
}
// Close this nsWindow
void nsWindow::Destroy() {
// WM_DESTROY has already fired, avoid calling it twice
if (mOnDestroyCalled) return;
// Don't destroy windows that have file pickers open, we'll tear these down
// later once the picker is closed.
mDestroyCalled = true;
if (mPickerDisplayCount) return;
// During the destruction of all of our children, make sure we don't get
// deleted.
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
DestroyDirectManipulation();
/**
* On windows the LayerManagerOGL destructor wants the widget to be around for
* cleanup. It also would like to have the HWND intact, so we nullptr it here.
*/
DestroyLayerManager();
// The DestroyWindow function destroys the specified window. The function
// sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
// and remove the keyboard focus from it. The function also destroys the
// window's menu, flushes the thread message queue, destroys timers, removes
// clipboard ownership, and breaks the clipboard viewer chain (if the window
// is at the top of the viewer chain).
//
// If the specified window is a parent or owner window, DestroyWindow
// automatically destroys the associated child or owned windows when it
// destroys the parent or owner window. The function first destroys child or
// owned windows, and then it destroys the parent or owner window.
VERIFY(::DestroyWindow(mWnd));
// Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
// OnDestroy() didn't get called, call it now.
if (!mOnDestroyCalled) {
MSGResult msgResult;
mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
OnDestroy();
}
}
/**************************************************************
*
* SECTION: Window class utilities
*
* Utilities for calculating the proper window class name for
* Create window.
*
**************************************************************/
static void RegisterWindowClass(const wchar_t* aClassName, UINT aExtraStyle,
LPWSTR aIconID) {
WNDCLASSW wc = {};
if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
// already registered
return;
}
wc.style = CS_DBLCLKS | aExtraStyle;
wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
wc.hInstance = nsToolkit::mDllInstance;
wc.hIcon =
aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
wc.lpszClassName = aClassName;
// Since we discard WM_ERASEBKGND events, the window-class background brush is
// mostly not used -- it shows up when resizing, but scarcely ever otherwise.
//
// In theory we could listen for theme changes and set this brush to an
// appropriate background color as needed; but given the hoops Win32 makes us
// jump through to change class data, it's probably not worth the trouble.
// (See bug 1901875.) Instead, we just make it dark grey, which is probably
// acceptable in either light or dark mode.
wc.hbrBackground = (HBRUSH)::GetStockObject(DKGRAY_BRUSH);
// Failures are ignored as they are handled when ::CreateWindow fails
::RegisterClassW(&wc);
}
static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
static const wchar_t* ChooseWindowClass(WindowType aWindowType) {
const wchar_t* className = [aWindowType] {
switch (aWindowType) {
case WindowType::Dialog:
return kClassNameDialog;
case WindowType::Popup:
return kClassNameDropShadow;
default:
return GetMainWindowClass();
}
}();
RegisterWindowClass(className, 0, gStockApplicationIcon);
return className;
}
/**************************************************************
*
* SECTION: Window styles utilities
*
* Return the proper windows styles and extended styles.
*
**************************************************************/
const DWORD kTitlebarItemsWindowStyles =
WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
const DWORD kAllBorderStyles =
kTitlebarItemsWindowStyles | WS_THICKFRAME | WS_DLGFRAME;
static DWORD WindowStylesRemovedForBorderStyle(BorderStyle aStyle) {
if (aStyle == BorderStyle::Default || aStyle == BorderStyle::All) {
return 0;
}
if (aStyle == BorderStyle::None) {
return kAllBorderStyles;
}
DWORD toRemove = 0;
if (!(aStyle & BorderStyle::Border)) {
toRemove |= WS_BORDER;
}
if (!(aStyle & BorderStyle::Title)) {
toRemove |= WS_DLGFRAME;
}
if (!(aStyle & (BorderStyle::Menu | BorderStyle::Close))) {
// Looks like getting rid of the system menu also does away with the close
// box. So, we only get rid of the system menu and the close box if you
// want neither. How does the Windows "Dialog" window class get just
// closebox and no sysmenu? Who knows.
toRemove |= WS_SYSMENU;
}
if (!(aStyle & BorderStyle::ResizeH)) {
toRemove |= WS_THICKFRAME;
}
if (!(aStyle & BorderStyle::Minimize)) {
toRemove |= WS_MINIMIZEBOX;
}
if (!(aStyle & BorderStyle::Maximize)) {
toRemove |= WS_MAXIMIZEBOX;
}
return toRemove;
}
// Return nsWindow styles
DWORD nsWindow::WindowStyle() {
DWORD style;
switch (mWindowType) {
case WindowType::Dialog:
style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
DS_MODALFRAME | WS_CLIPCHILDREN;
if (mBorderStyle != BorderStyle::Default) {
style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
}
break;
case WindowType::Popup:
style = WS_OVERLAPPED | WS_POPUP | WS_CLIPCHILDREN;
break;
default:
NS_ERROR("unknown border style");
[[fallthrough]];
case WindowType::TopLevel:
style = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER |
WS_THICKFRAME | kTitlebarItemsWindowStyles;
break;
}
style &= ~WindowStylesRemovedForBorderStyle(mBorderStyle);
VERIFY_WINDOW_STYLE(style);
return style;
}
// Return nsWindow extended styles
DWORD nsWindow::WindowExStyle() {
switch (mWindowType) {
case WindowType::Popup: {
DWORD extendedStyle = WS_EX_TOOLWINDOW;
if (mPopupLevel == PopupLevel::Top) {
extendedStyle |= WS_EX_TOPMOST;
}
return extendedStyle;
}
case WindowType::Dialog:
case WindowType::TopLevel:
case WindowType::Invisible:
break;
}
if (mIsAlert) {
MOZ_ASSERT(mWindowType == WindowType::Dialog,
"Expect alert windows to have type=dialog");
return WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
}
return WS_EX_WINDOWEDGE;
}
/**************************************************************
*
* SECTION: Native window association utilities
*
* Used in Create and Destroy. A nsWindow can associate with its
* underlying native window mWnd. Once a native window is
* associated with a nsWindow, its native events will be handled
* by the static member function nsWindow::WindowProc. Moreover,
* the association will be registered in the WinUtils association
* list, that is, calling WinUtils::GetNSWindowPtr on the native
* window will return the associated nsWindow. This is used in
* nsWindow::WindowProc to correctly dispatch native events to
* the handler methods defined in nsWindow, even though it is a
* static member function.
*
* After dissociation, the native events of the native window will
* no longer be handled by nsWindow::WindowProc, and will thus not
* be dispatched to the nsWindow native event handler methods.
* Moreover, the association will no longer be registered in the
* WinUtils association list, so calling WinUtils::GetNSWindowPtr
* on the native window will return nullptr.
*
**************************************************************/
bool nsWindow::AssociateWithNativeWindow() {
if (!mWnd || !IsWindow(mWnd)) {
NS_ERROR("Invalid window handle");
return false;
}
// Connect the this pointer to the native window handle.
// This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
// uses WinUtils::GetNSWindowPtr internally.
WinUtils::SetNSWindowPtr(mWnd, this);
::SetLastError(ERROR_SUCCESS);
const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
NS_ERROR("Failure in SetWindowLongPtrW");
WinUtils::SetNSWindowPtr(mWnd, nullptr);
return false;
}
mPrevWndProc.emplace(prevWndProc);
return true;
}
void nsWindow::DissociateFromNativeWindow() {
if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
return;
}
DebugOnly<WNDPROC> wndProcBeforeDissociate =
reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
// If we've used the Windows App SDK to remove the minimize/maximize/close
// entries from the titlebar, then the Windows App SDK sets its own WNDPROC
// own the window, so this assertion would fail. But we only do this if
// Mica is available.
NS_ASSERTION(WinUtils::MicaAvailable() ||
wndProcBeforeDissociate == nsWindow::WindowProc,
"Unstacked an unexpected native window procedure");
WinUtils::SetNSWindowPtr(mWnd, nullptr);
mPrevWndProc.reset();
}
void nsWindow::DidClearParent(nsIWidget*) {
if (mWindowType == WindowType::Popup || !mWnd) {
return;
}
::SetParent(mWnd, nullptr);
RecreateDirectManipulationIfNeeded();
}
static int32_t RoundDown(double aDouble) {
return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
: static_cast<int32_t>(ceil(aDouble));
}
float nsWindow::GetDPI() { return GetDefaultScaleInternal() * 96.0f; }
double nsWindow::GetDefaultScaleInternal() {
if (mDefaultScale <= 0.0) {
mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
}
return mDefaultScale;
}
int32_t nsWindow::LogToPhys(double aValue) {
return WinUtils::LogToPhys(
::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
}
nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
}
nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
if (IsTopLevelWidget()) {
// Must use a flag instead of mWindowType to tell if the window is the
// owned by the topmost widget, because a child window can be embedded
// inside a HWND which is not associated with a nsIWidget.
return nullptr;
}
// If this widget has already been destroyed, pretend we have no parent.
// This corresponds to code in Destroy which removes the destroyed
// widget from its parent's child list.
if (mInDtor || mOnDestroyCalled) return nullptr;
// aIncludeOwner set to true implies walking the parent chain to retrieve the
// root owner. aIncludeOwner set to false implies the search will stop at the
// true parent (default).
nsWindow* widget = nullptr;
if (mWnd) {
HWND parent = nullptr;
if (aIncludeOwner)
parent = ::GetParent(mWnd);
else
parent = ::GetAncestor(mWnd, GA_PARENT);
if (parent) {
widget = WinUtils::GetNSWindowPtr(parent);
if (widget) {
// If the widget is in the process of being destroyed then
// do NOT return it
if (widget->mInDtor) {
widget = nullptr;
}
}
}
}
return widget;
}
/**************************************************************
*
* SECTION: nsIWidget::Show
*
* Hide or show this component.
*
**************************************************************/
void nsWindow::Show(bool aState) {
if (aState && mIsShowingPreXULSkeletonUI) {
// The first time we decide to actually show the window is when we decide
// that we've taken over the window from the skeleton UI, and we should
// no longer treat resizes / moves specially.
//
// NOTE(emilio): mIsShowingPreXULSkeletonUI feels a bit odd, or at least
// misnamed. During regular startup we create the skeleton UI, then the
// early blank window consumes it, and at that point we set
// mIsShowingPreXULSkeletonUI to false, but in fact, we're still showing
// the skeleton UI (because the blank window is, well, blank). We should
// consider guarding this with !mIsEarlyBlankWindow...
mIsShowingPreXULSkeletonUI = false;
// Concomitantly, this is also when we change the cursor away from the
// default "wait" cursor.
SetCursor(Cursor{eCursor_standard});
#if defined(ACCESSIBILITY)
// If our HWND has focus and the a11y engine hasn't started yet, fire a
// focus win event. Windows already did this when the skeleton UI appeared,
// but a11y wouldn't have been able to start at that point even if a client
// responded. Firing this now gives clients the chance to respond with
// WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
// this if the a11y engine has already started because it has probably
// already fired focus on a descendant.
if (::GetFocus() == mWnd && !GetAccService()) {
::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
}
#endif // defined(ACCESSIBILITY)
}
MOZ_ASSERT_IF(mWindowType == WindowType::Popup,
ChooseWindowClass(mWindowType) == kClassNameDropShadow);
bool syncInvalidate = false;
bool wasVisible = mIsVisible;
// Set the status now so that anyone asking during ShowWindow or
// SetWindowPos would get the correct answer.
mIsVisible = aState;
if (mWnd) {
if (aState) {
if (!wasVisible && mWindowType == WindowType::TopLevel) {
// speed up the initial paint after show for
// top level windows:
syncInvalidate = true;
// Cloak (or uncloak) the window.
//
// (DWMWA_CLOAK is effectively orthogonal to any cloaking done by the
// shell to implement virtual desktops; we don't have to worry about
// accidentally forcing something on another desktop to become visible.)
constexpr static const auto CloakWindow = [](HWND hwnd, BOOL state) {
::DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &state, sizeof(state));
};
// Clear the window using a theme-appropriate color.
constexpr static const auto ClearWindow = [](HWND hwnd) {
// default background color from current theme
auto const bgcolor = LookAndFeel::Color(
StyleSystemColor::Window, PreferenceSheet::ColorSchemeForChrome(),
LookAndFeel::UseStandins::No, NS_RGB(0, 0, 0));
HBRUSH brush = ::CreateSolidBrush(NSRGB_2_COLOREF(bgcolor));
if (NS_WARN_IF(!brush)) {
// GDI object cap hit, possibly?
return;
}
auto const _releaseBrush =
MakeScopeExit([&] { ::DeleteObject(brush); });
HDC hdc = ::GetWindowDC(hwnd);
MOZ_ASSERT(hdc);
auto const _cleanupDC =
MakeScopeExit([&] { ::ReleaseDC(hwnd, hdc); });
RECT rect;
::GetWindowRect(hwnd, &rect); // includes non-client area
// Convert from screen- to client-coordinates, accounting for the
// desktop (or, in theory, us) possibly being WS_EX_LAYOUTRTL...
::MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&rect, 2);
// ... then convert from client- to window- coordinates, with no
// separate RTL-handling needed.
::OffsetRect(&rect, -rect.left, -rect.top);
::FillRect(hdc, &rect, brush);
};
if (!mHasBeenShown) {
// On creation, the window's content is not specified; in practice,
// it's observed to usually be full of bright white, regardless of any
// window-class options. DWM will happily render that unspecified
// content to the screen before we get a chance to process a
// WM_ERASEBKGND event (or, indeed, anything else). To avoid dark-mode
// users being assaulted with a bright white flash, we need to draw
// something on top of that at least once before showing the window.
//
// Unfortunately, there's a bit of a catch-22 here: until the window
// has been set "visible" at least once, it doesn't have a backing
// surface, so we can't draw anything to it! To work around this, we
// cloak the window before "showing" it.
CloakWindow(mWnd, TRUE);
}
// Set the cursor before showing the window to avoid the default wait
// cursor.
SetCursor(Cursor{eCursor_standard});
switch (mFrameState->GetSizeMode()) {
case nsSizeMode_Fullscreen:
::ShowWindow(mWnd, SW_SHOW);
break;
case nsSizeMode_Maximized:
::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
break;
case nsSizeMode_Minimized:
::ShowWindow(mWnd, SW_SHOWMINIMIZED);
break;
default:
if (CanTakeFocus() && !mAlwaysOnTop) {
::ShowWindow(mWnd, SW_SHOWNORMAL);
} else {
::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
// Don't flicker the window if we're restoring session
if (!sIsRestoringSession) {
(void)GetAttention(2);
}
}
break;
}
if (!mHasBeenShown) {
// Now that ::ShowWindow() has been called once, the window surface
// actually exists, so we can draw to it. Fill it with the theme's
// background color before uncloaking it to complete the Show().
ClearWindow(mWnd);
CloakWindow(mWnd, FALSE);
// bug 1833841: Initialize mWorkspaceId asynchronously so that we
// don't try to update it synchronously on WM_CLOSE. Calling
// GetWindowDesktopId() is very slow and doing it can cause WM_CLOSE
// to "timeout" and fail to close other windows. (if the user selected
// "Close all windows" in the taskbar)
AsyncUpdateWorkspaceID();
mHasBeenShown = true;
}
} else {
DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
if (wasVisible) {
flags |= SWP_NOZORDER;
}
if (mAlwaysOnTop || mIsAlert) {
flags |= SWP_NOACTIVATE;
}
if (mWindowType == WindowType::Popup) {
// ensure popups are the topmost of the TOPMOST
// layer. Remember not to set the SWP_NOZORDER
// flag as that might allow the taskbar to overlap
// the popup.
flags |= SWP_NOACTIVATE | SWP_NOOWNERZORDER;
HWND owner = ::GetWindow(mWnd, GW_OWNER);
if (owner) {
// PopupLevel::Top popups should be above all else. All other
// types should be placed in front of their owner, without
// changing the owner's z-level relative to other windows.
if (mPopupLevel != PopupLevel::Top) {
::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
::SetWindowPos(
owner, mWnd, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
} else {
::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
}
} else {
::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
}
} else {
if (mWindowType == WindowType::Dialog && !CanTakeFocus())
flags |= SWP_NOACTIVATE;
::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
}
}
} else {
if (mWindowType != WindowType::Dialog) {
::ShowWindow(mWnd, SW_HIDE);
} else {
::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
SWP_NOACTIVATE);
}
}
}
if (!wasVisible && aState) {
Invalidate();
if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
::UpdateWindow(mWnd);
}
}
if (mOpeningAnimationSuppressed) {
SuppressAnimation(false);
}
}
/**************************************************************
*
* SECTION: nsIWidget::IsVisible
*
* Returns the visibility state.
*
**************************************************************/
// Return true if the component is visible, false otherwise.
//
// This does not take cloaking into account.
bool nsWindow::IsVisible() const { return mIsVisible; }
/**************************************************************
*
* SECTION: Touch and APZ-related functions
*
**************************************************************/
void nsWindow::RegisterTouchWindow() {
mTouchWindow = true;
::RegisterTouchWindow(mWnd, TWF_WANTPALM);
::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
}
BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
if (win) {
::RegisterTouchWindow(aWnd, TWF_WANTPALM);
}
return TRUE;
}
void nsWindow::LockAspectRatio(bool aShouldLock) {
if (aShouldLock) {
mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
} else {
mAspectRatio = 0.0;
}
}
/**************************************************************
*
* SECTION: nsIWidget::SetInputRegion
*
* Sets whether the window should ignore mouse events.
*
**************************************************************/
void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
mInputRegion = aInputRegion;
}
/**************************************************************
*
* SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
*
* Repositioning and sizing a window.
*
**************************************************************/
void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
SizeConstraints c = aConstraints;
if (mWindowType != WindowType::Popup && mResizable) {
c.mMinSize.width =
std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
c.mMinSize.height =
std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
}
if (mMaxTextureSize > 0) {
// We can't make ThebesLayers bigger than this anyway.. no point it letting
// a window grow bigger as we won't be able to draw content there in
// general.
c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
}
mSizeConstraintsScale = GetDefaultScale().scale;
nsIWidget::SetSizeConstraints(c);
}
const SizeConstraints nsWindow::GetSizeConstraints() {
double scale = GetDefaultScale().scale;
if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
return mSizeConstraints;
}
scale /= mSizeConstraintsScale;
SizeConstraints c = mSizeConstraints;
if (c.mMinSize.width != NS_MAXSIZE) {
c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
}
if (c.mMinSize.height != NS_MAXSIZE) {
c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
}
if (c.mMaxSize.width != NS_MAXSIZE) {
c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
}
if (c.mMaxSize.height != NS_MAXSIZE) {
c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
}
return c;
}
// Move this component
void nsWindow::Move(const DesktopPoint& aTopLeft) {
if (mWindowType == WindowType::TopLevel ||
mWindowType == WindowType::Dialog) {
SetSizeMode(nsSizeMode_Normal);
}
// for top-level windows only, convert coordinates from desktop pixels
// (the "parent" coordinate space) to the window's device pixel space
auto topLeft =
LayoutDeviceIntPoint::Round(aTopLeft * GetDesktopToDeviceScale());
// Check to see if window needs to be moved first
// to avoid a costly call to SetWindowPos. This check
// can not be moved to the calling code in nsView, because
// some platforms do not position child windows correctly
// Only perform this check for non-popup windows, since the positioning can
// in fact change even when the x/y do not. We always need to perform the
// check. See bug #97805 for details.
if (mWindowType != WindowType::Popup && mBounds.TopLeft() == topLeft) {
// Nothing to do, since it is already positioned correctly.
return;
}
// Normally, when the skeleton UI is disabled, we resize+move the window
// before showing it in order to ensure that it restores to the correct
// position when the user un-maximizes it. However, when we are using the
// skeleton UI, this results in the skeleton UI window being moved around
// undesirably before being locked back into the maximized position. To
// avoid this, we simply set the placement to restore to via
// SetWindowPlacement. It's a little bit more of a dance, though, since we
// need to convert the workspace coords that SetWindowPlacement uses to the
// screen space coordinates we normally use with SetWindowPos.
if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
VERIFY(::GetWindowPlacement(mWnd, &pl));
HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
if (NS_WARN_IF(!monitor)) {
return;
}
MONITORINFO mi = {sizeof(MONITORINFO)};
VERIFY(::GetMonitorInfo(monitor, &mi));
int32_t deltaX = topLeft.x.value + mi.rcWork.left - mi.rcMonitor.left -
pl.rcNormalPosition.left;
int32_t deltaY = topLeft.y.value + mi.rcWork.top - mi.rcMonitor.top -
pl.rcNormalPosition.top;
pl.rcNormalPosition.left += deltaX;
pl.rcNormalPosition.right += deltaX;
pl.rcNormalPosition.top += deltaY;
pl.rcNormalPosition.bottom += deltaY;
VERIFY(::SetWindowPlacement(mWnd, &pl));
return;
}
mBounds.MoveTo(topLeft);
if (mWnd) {
#ifdef DEBUG
// complain if a window is moved offscreen (legal, but potentially
// worrisome)
if (IsTopLevelWidget()) { // only a problem for top-level windows
// Make sure this window is actually on the screen before we move it
// XXX: Needs multiple monitor support
HDC dc = ::GetDC(mWnd);
if (dc) {
if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
RECT workArea;
::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
// no annoying assertions. just mention the issue.
if (topLeft.x < 0 || topLeft.x >= workArea.right || topLeft.y < 0 ||
topLeft.y >= workArea.bottom) {
MOZ_LOG(gWindowsLog, LogLevel::Info,
("window moved to offscreen position\n"));
}
}
::ReleaseDC(mWnd, dc);
}
}
#endif
UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
double oldScale = mDefaultScale;
mResizeState = IN_SIZEMOVE;
VERIFY(::SetWindowPos(mWnd, nullptr, topLeft.x, topLeft.y, 0, 0, flags));
mResizeState = NOT_RESIZING;
if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
ChangedDPI();
}
ResizeDirectManipulationViewport();
}
}
// Resize this component
void nsWindow::Resize(const DesktopSize& aSize, bool aRepaint) {
// for top-level windows only, convert coordinates from desktop pixels
// (the "parent" coordinate space) to the window's device pixel space
auto size = LayoutDeviceIntSize::Round(aSize * GetDesktopToDeviceScale());
NS_ASSERTION(size.width >= 0, "Negative width passed to nsWindow::Resize");
NS_ASSERTION(size.height >= 0, "Negative height passed to nsWindow::Resize");
if (size.width < 0 || size.height < 0) {
gfxCriticalNoteOnce << "Negative passed to Resize(" << size.width << ", "
<< size.height << ") repaint: " << aRepaint;
}
ConstrainSize(&size.width, &size.height);
// Avoid unnecessary resizing calls
if (mBounds.Size() == size) {
if (aRepaint) {
Invalidate();
}
return;
}
// Refer to the comment above a similar check in nsWindow::Move
if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
VERIFY(::GetWindowPlacement(mWnd, &pl));
pl.rcNormalPosition.right = pl.rcNormalPosition.left + size.width;
pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + size.height;
mResizeState = RESIZING;
VERIFY(::SetWindowPlacement(mWnd, &pl));
mResizeState = NOT_RESIZING;
return;
}
// Set cached value for lightweight and printing
bool wasLocking = mAspectRatio != 0.0;
mBounds.SizeTo(size);
if (wasLocking) {
LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
}
if (mWnd) {
UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
if (!aRepaint) {
flags |= SWP_NOREDRAW;
}
double oldScale = mDefaultScale;
mResizeState = RESIZING;
VERIFY(::SetWindowPos(mWnd, nullptr, 0, 0, size.width, size.height, flags));
mResizeState = NOT_RESIZING;
if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
ChangedDPI();
}
ResizeDirectManipulationViewport();
}
if (aRepaint) Invalidate();
}
// Resize this component
void nsWindow::Resize(const DesktopRect& aRect, bool aRepaint) {
// for top-level windows only, convert coordinates from desktop pixels
// (the "parent" coordinate space) to the window's device pixel space
auto topLeft =
LayoutDeviceIntPoint::Round(aRect.TopLeft() * GetDesktopToDeviceScale());
auto size =
LayoutDeviceIntSize::Round(aRect.Size() * GetDesktopToDeviceScale());
NS_ASSERTION(size.width >= 0, "Negative width passed to nsWindow::Resize");
NS_ASSERTION(size.height >= 0, "Negative height passed to nsWindow::Resize");
if (size.width < 0 || size.height < 0) {
gfxCriticalNoteOnce << "Negative passed to Resize(" << size
<< ") repaint: " << aRepaint;
}
ConstrainSize(&size.width, &size.height);
// Avoid unnecessary resizing calls
if (mBounds.IsEqualRect(topLeft.x, topLeft.y, size.width, size.height)) {
if (aRepaint) {
Invalidate();
}
return;
}
// Refer to the comment above a similar check in nsWindow::Move
if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
VERIFY(::GetWindowPlacement(mWnd, &pl));
HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
if (NS_WARN_IF(!monitor)) {
return;
}
MONITORINFO mi = {sizeof(MONITORINFO)};
VERIFY(::GetMonitorInfo(monitor, &mi));
int32_t deltaX = topLeft.x.value + mi.rcWork.left - mi.rcMonitor.left -
pl.rcNormalPosition.left;
int32_t deltaY = topLeft.y.value + mi.rcWork.top - mi.rcMonitor.top -
pl.rcNormalPosition.top;
pl.rcNormalPosition.left += deltaX;
pl.rcNormalPosition.right = pl.rcNormalPosition.left + size.width;
pl.rcNormalPosition.top += deltaY;
pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + size.height;
VERIFY(::SetWindowPlacement(mWnd, &pl));
return;
}
// Set cached value for lightweight and printing
mBounds = LayoutDeviceIntRect(topLeft, size);
if (mWnd) {
UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
if (!aRepaint) {
flags |= SWP_NOREDRAW;
}
double oldScale = mDefaultScale;
mResizeState = RESIZING;
VERIFY(::SetWindowPos(mWnd, nullptr, topLeft.x, topLeft.y, size.width,
size.height, flags));
mResizeState = NOT_RESIZING;
if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
ChangedDPI();
}
if (mTransitionWnd) {
// If we have a fullscreen transition window, we need to make
// it topmost again, otherwise the taskbar may be raised by
// the system unexpectedly when we leave fullscreen state.
::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
ResizeDirectManipulationViewport();
}
if (aRepaint) Invalidate();
}
/**************************************************************
*
* SECTION: Window state.
*
* nsIWidget::SetSizeMode, nsIWidget::ConstrainPosition
*
* Positioning, restore, minimize, and maximize.
*
**************************************************************/
static UINT GetCurrentShowCmd(HWND aWnd) {
WINDOWPLACEMENT pl;
pl.length = sizeof(pl);
::GetWindowPlacement(aWnd, &pl);
return pl.showCmd;
}
// Maximize, minimize or restore the window.
void nsWindow::SetSizeMode(nsSizeMode aMode) {
// If we are still displaying a maximized pre-XUL skeleton UI, ignore the
// noise of sizemode changes. Once we have "shown" the window for the first
// time (called nsWindow::Show(true), even though the window is already
// technically displayed), we will again accept sizemode changes.
if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
return;
}
mFrameState->EnsureSizeMode(aMode);
}
nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
nsString DoGetWorkspaceID(HWND aWnd) {
nsString ret;
RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
if (!desktopManager || !aWnd) {
return ret;
}
GUID desktop;
MOZ_LOG(gWindowsLog, LogLevel::Debug, ("calling GetWindowDesktopId"));
HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
MOZ_LOG(gWindowsLog, LogLevel::Debug,
("called GetWindowDesktopId, hr=%08lX", hr));
if (FAILED(hr)) {
return ret;
}
RPC_WSTR workspaceIDStr = nullptr;
if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
ret.Assign((wchar_t*)workspaceIDStr);
RpcStringFreeW(&workspaceIDStr);
}
return ret;
}
void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
// If we have a value cached, use that, but also make sure it is
// scheduled to be updated. If we don't yet have a value, get
// one synchronously.
AssertIsOnMainThread();
if (mDesktopId.IsEmpty()) {
MOZ_LOG(gWindowsLog, LogLevel::Debug,
("GetWorkspaceId - calling DoGetWorkspaceID() (synchronously)"));
mDesktopId = DoGetWorkspaceID(mWnd);
} else {
MOZ_LOG(gWindowsLog, LogLevel::Debug,
("GetWorkspaceId - calling AsyncUpdateWorkspaceID()"));
AsyncUpdateWorkspaceID();
}
workspaceID = mDesktopId;
}
void nsWindow::AsyncUpdateWorkspaceID() {
nsWeakPtr weakSelf = do_GetWeakReference(this);
// Wrap weak reference in nsMainThreadPtrHandle to resist attempts to
// Release() the weak reference off-main-thread (it's not thread safe)
// in case of failed task dispatch.
nsMainThreadPtrHandle<nsIWeakReference> weakSelfHandle(
new nsMainThreadPtrHolder("AsyncUpdateWorkspaceID", weakSelf.forget()));
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
"BackgroundUpdateWorkspaceID",
[hwnd = mWnd, weakSelfHandle = std::move(weakSelfHandle)]() mutable {
auto id = DoGetWorkspaceID(hwnd);
NS_DispatchToMainThread(NS_NewRunnableFunction(
"MainUpdateWorkspaceID",
[id = std::move(id),
weakSelfHandle = std::move(weakSelfHandle)]() mutable {
AssertIsOnMainThread();
nsCOMPtr<nsIWidget> widgetSelf = do_QueryReferent(weakSelfHandle);
auto* self = static_cast<nsWindow*>(widgetSelf.get());
if (self) {
self->mDesktopId = id;
}
}));
}));
}
void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
AssertIsOnMainThread();
RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
if (!desktopManager) {
return;
}
GUID desktop;
const nsString flat = PromiseFlatString(workspaceID);
RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
mDesktopId = workspaceID;
}
}
}
void nsWindow::SuppressAnimation(bool aSuppress) {
DWORD dwAttribute = aSuppress ? TRUE : FALSE;
DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
sizeof dwAttribute);
}
// Constrain a potential move to fit onscreen
// Position (aX, aY) is specified in Windows screen (logical) pixels,
// except when using per-monitor DPI, in which case it's device pixels.
void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
if (!IsTopLevelWidget()) {
// only a problem for top-level windows
return;
}
// If the window is already at (0, 0), nothing we do to it here can help.
// Leave it alone.
//
// (This also happens to cover the case where the window was Aero Snapped into
// the upper-left corner.)
if (aPoint == DesktopIntPoint{0, 0}) {
return;
}
double dpiScale = GetDesktopToDeviceScale().scale;
// We need to use the window size in the kind of pixels used for window-
// manipulation APIs.
int32_t logWidth =
std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
int32_t logHeight =
std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
/* get our playing field. use the current screen, or failing that
for any reason, use device caps for the default screen. */
DesktopIntRect screenRect;
nsCOMPtr<nsIScreenManager> screenmgr =
do_GetService(sScreenManagerContractID);
if (!screenmgr) {
return;
}
nsCOMPtr<nsIScreen> screen;
screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
getter_AddRefs(screen));
if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
// For normalized windows, use the desktop work area.
screenRect = screen->GetAvailRectDisplayPix();
} else {
// For full screen windows, use the desktop.
screenRect = screen->GetRectDisplayPix();
}
// Check for the case where the window was Aero Snapped to the right. (The
// window will extend off the right and bottom of the screen in this case by a
// small but DPI-dependent value.)
//
// We do not check WINDOWPLACEMENT for a position mismatch. That would catch
// whether the window is _currently_ Aero Snapped to the right, but we may be
// restoring the window. (We can't guarantee a restore into a snapped state:
// there is no known API to do so. Fortunately, the shell seems to detect this
// case anyway, and treats the window as snapped.)
//
// Note that this _is_ a heuristic. False positives are possible; but they
// seem unlikely (it would require manually positioning a window to extend
// just barely offscreen to the lower right), and anyway are probably
// harmless: the effect will simply be that we leave the window exactly where
// the user put it, instead of nudging it slightly.
if (aPoint.y == 0) {
auto const xMax = aPoint.x + logWidth;
auto const yMax = aPoint.y + logHeight;
auto const deltaX = xMax - screenRect.XMost();
auto const deltaY = yMax - screenRect.YMost();
if (deltaX == deltaY) {
if (8 <= deltaX && deltaX <= 16) {
// If so, don't try to fix the position; Windows will (probably) deal
// with it.
return;
}
}
}
aPoint = ConstrainPositionToBounds(aPoint, {logWidth, logHeight}, screenRect);
}
/**************************************************************
*
* SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
*
* Enabling and disabling the widget.
*
**************************************************************/
// Enable/disable this component
void nsWindow::Enable(bool aState) {
if (mWnd) {
::EnableWindow(mWnd, aState);
}
}
// Return the current enable state
bool nsWindow::IsEnabled() const {
return !mWnd || (::IsWindowEnabled(mWnd) &&
::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
}
/**************************************************************
*
* SECTION: nsIWidget::SetFocus
*
* Give the focus to this widget.
*
**************************************************************/
void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
if (!mWnd) {
return;
}
#ifdef WINSTATE_DEBUG_OUTPUT
if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
MOZ_LOG(gWindowsLog, LogLevel::Info,
("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
} else {
MOZ_LOG(gWindowsLog, LogLevel::Info,
("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
}
#endif
// Uniconify, if necessary
HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
::ShowWindow(toplevelWnd, SW_RESTORE);
}
::SetFocus(mWnd);
}
/**************************************************************
*
* SECTION: Bounds
*
* GetBounds, GetClientBounds, GetScreenBounds,
* GetRestoredBounds, GetClientOffset, SetCustomTitlebar
*
* Bound calculations.
*
**************************************************************/
// Return the window's full dimensions in screen coordinates.
// If the window has a parent, converts the origin to an offset
// of the parent's screen origin.
LayoutDeviceIntRect nsWindow::GetBounds() {
if (!mWnd) {
return mBounds;
}
RECT r;
VERIFY(::GetWindowRect(mWnd, &r));
LayoutDeviceIntRect rect;
// assign size
rect.SizeTo(r.right - r.left, r.bottom - r.top);
// popup window bounds' are in screen coordinates, not relative to parent
// window
if (mWindowType == WindowType::Popup) {
rect.MoveTo(r.left, r.top);
return rect;
}
// chrome on parent:
// ___ 5,5 (chrome start)
// | ____ 10,10 (client start)
// | | ____ 20,20 (child start)
// | | |
// 20,20 - 5,5 = 15,15 (??)
// minus GetClientOffset:
// 15,15 - 5,5 = 10,10
//
// no chrome on parent:
// ______ 10,10 (win start)
// | ____ 20,20 (child start)
// | |
// 20,20 - 10,10 = 10,10
//
// walking the chain:
// ___ 5,5 (chrome start)
// | ___ 10,10 (client start)
// | | ___ 20,20 (child start)
// | | | __ 30,30 (child start)
// | | | |
// 30,30 - 20,20 = 10,10 (offset from second child to first)
// 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
// minus GetClientOffset:
// 25,25 - 5,5 = 20,20 (offset from second child to parent client)
// convert coordinates if parent exists
HWND parent = ::GetParent(mWnd);
if (parent) {
RECT pr;
VERIFY(::GetWindowRect(parent, &pr));
r.left -= pr.left;
r.top -= pr.top;
// adjust for chrome
nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
if (pWidget && pWidget->IsTopLevelWidget()) {
LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
r.left -= clientOffset.x;
r.top -= clientOffset.y;
}
}
rect.MoveTo(r.left, r.top);
if (mCompositorSession &&
!wr::WindowSizeSanityCheck(rect.width, rect.height)) {
gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
<< mFrameState->GetSizeMode();
}
return rect;
}
LayoutDeviceIntSize nsWindow::GetSize() const {
if (!mWnd) {
return mBounds.Size();
}
RECT r;
VERIFY(::GetWindowRect(mWnd, &r));
return {r.right - r.left, r.bottom - r.top};
}
// Get this component dimension
LayoutDeviceIntRect nsWindow::GetClientBounds() {
if (!mWnd) {
return LayoutDeviceIntRect(0, 0, 0, 0);
}
RECT r;
if (!::GetClientRect(mWnd, &r)) {
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
return mBounds;
}
LayoutDeviceIntRect bounds = GetBounds();
LayoutDeviceIntRect rect;
rect.MoveTo(bounds.TopLeft() + GetClientOffset());
rect.SizeTo(r.right - r.left, r.bottom - r.top);
return rect;
}
// Like GetBounds, but don't offset by the parent
LayoutDeviceIntRect nsWindow::GetScreenBounds() {
if (!mWnd) {
return mBounds;
}
RECT r;
VERIFY(::GetWindowRect(mWnd, &r));
return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
}
nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
if (SizeMode() == nsSizeMode_Normal) {
aRect = GetScreenBounds();
return NS_OK;
}
if (!mWnd) {
return NS_ERROR_FAILURE;
}
WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
VERIFY(::GetWindowPlacement(mWnd, &pl));
const RECT& r = pl.rcNormalPosition;
HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
if (!monitor) {
return NS_ERROR_FAILURE;
}
MONITORINFO mi = {sizeof(MONITORINFO)};
VERIFY(::GetMonitorInfo(monitor, &mi));
aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
mi.rcWork.top - mi.rcMonitor.top);
return NS_OK;
}
// Return the x,y offset of the client area from the origin of the window. If
// the window is borderless returns (0,0).
LayoutDeviceIntPoint nsWindow::GetClientOffset() {
if (!mWnd) {
return LayoutDeviceIntPoint(0, 0);
}
RECT r1;
GetWindowRect(mWnd, &r1);
LayoutDeviceIntPoint pt = WidgetToScreenOffset();
return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
pt.y - LayoutDeviceIntCoord(r1.top));
}
void nsWindow::ResetLayout() {
// This will trigger a frame changed event, triggering
// nc calc size and a sizemode gecko event.
SetWindowPos(mWnd, 0, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
// If hidden, just send the frame changed event for now.
if (!mIsVisible) {
return;
}
// Send a gecko size event to trigger reflow.
RECT clientRc = {0};
GetClientRect(mWnd, &clientRc);
OnResize(WinUtils::ToIntRect(clientRc).Size());
// Invalidate and update
Invalidate();
}
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
void nsWindow::SetColorScheme(const Maybe<ColorScheme>& aScheme) {
BOOL dark =
aScheme.valueOrFrom(LookAndFeel::SystemColorScheme) == ColorScheme::Dark;
DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
sizeof dark);
DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
sizeof dark);
}
void nsWindow::SetMicaBackdrop(bool aEnabled) {
if (aEnabled == mMicaBackdrop) {
return;
}
mMicaBackdrop = aEnabled;
UpdateMicaBackdrop();
}
void nsWindow::UpdateMicaBackdrop(bool aForce) {
const bool micaEnabled =
IsPopup() ? WinUtils::MicaPopupsEnabled() : WinUtils::MicaEnabled();
if (!micaEnabled && !aForce) {
return;
}
const bool useBackdrop = mMicaBackdrop && micaEnabled;
const DWM_SYSTEMBACKDROP_TYPE backdrop = [&] {
if (!useBackdrop) {
return DWMSBT_AUTO;
}
if (IsPopup()) {
return DWMSBT_TRANSIENTWINDOW;
}
switch (StaticPrefs::widget_windows_mica_toplevel_backdrop()) {
case 1:
return DWMSBT_MAINWINDOW;
case 2:
return DWMSBT_TRANSIENTWINDOW;
case 3:
default:
return DWMSBT_TABBEDWINDOW;
}
}();
::DwmSetWindowAttribute(mWnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdrop,
sizeof backdrop);
if (IsPopup()) {
// For popups, we need a couple extra tweaks:
// * We want the native rounded corners and borders (as otherwise we can't
// clip the backdrop).
// * We want to draw as if the window was active all the time (as
// otherwise it'd draw the inactive window backdrop rather than
// acrylic). See also the WM_NCACTIVATE implementation.
const DWM_WINDOW_CORNER_PREFERENCE corner =
useBackdrop ? DWMWCP_ROUND : DWMWCP_DEFAULT;
::DwmSetWindowAttribute(mWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &corner,
sizeof corner);
::PostMessageW(mWnd, WM_NCACTIVATE, TRUE, -1);
}
}
LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
MOZ_ASSERT(mCustomNonClient);
// We're dealing with a "normal" window (not maximized, minimized, or
// fullscreen), so set `mNonClientOffset` accordingly.
//
// Setting `mNonClientOffset` to 0 has the effect of leaving the default
// frame intact. Setting it to a value greater than 0 reduces the frame
// size by that amount.
//
// When using custom titlebar, we hide the titlebar and leave the default
// frame on the other sides.
return LayoutDeviceIntMargin(mCustomNonClientMetrics.DefaultMargins().top, 0,
0, 0);
}
/**
* Called when the window layout changes: full screen mode transitions,
* theme changes, and composition changes. Calculates the new non-client
* margins and fires off a frame changed event, which triggers an nc calc
* size windows event, kicking the changes in.
*
* This function calculates and populates `mNonClientOffset`.
* In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
* as (default frame size - offset). For example, if the left frame should
* be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
* will equal 1.
*
* For maximized, fullscreen, and minimized windows special processing takes
* place.
*/
bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
if (!mCustomNonClient) {
return false;
}
const nsSizeMode sizeMode = mFrameState->GetSizeMode();
if (sizeMode == nsSizeMode_Minimized) {
return false;
}
const bool hasCaption =
bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
BorderStyle::Menu | BorderStyle::Default));
float dpi = GetDPI();
auto& metrics = mCustomNonClientMetrics;
// mHorResizeMargin is the size of the default NC areas on the
// left and right sides of our window. It is calculated as
// the sum of:
// SM_CXFRAME - The thickness of the sizing border
// SM_CXPADDEDBORDER - The amount of border padding
// for captioned windows
//
// If the window does not have a caption, mHorResizeMargin will be equal to
// `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
metrics.mHorResizeMargin =
WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
(hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
: 0);
// mVertResizeMargin is the size of the default NC area at the
// bottom of the window. It is calculated as the sum of:
// SM_CYFRAME - The thickness of the sizing border
// SM_CXPADDEDBORDER - The amount of border padding
// for captioned windows.
//
// If the window does not have a caption, mVertResizeMargin will be equal to
// `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
metrics.mVertResizeMargin =
WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
(hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
: 0);
// mCaptionHeight is the default size of the caption. You need to include
// mVertResizeMargin if you want the whole size of the default NC area at the
// top of the window.
metrics.mCaptionHeight =
hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) : 0;
metrics.mOffset = {};
if (sizeMode == nsSizeMode_Fullscreen) {
// Remove the default frame from the top of our fullscreen window. This
// makes the whole caption part of our client area, allowing us to draw
// in the whole caption area. Additionally remove the default frame from
// the left, right, and bottom.
//
// NOTE(emilio): Fullscreen windows have completely different window styles
// because of HideWindowChrome(), so we actually need to apply the offsets
// and extend into the frame. It might be worth investigating if we can
// make fullscreen work without messing with window styles (like
// maximized windows work).
metrics.mOffset = metrics.DefaultMargins();
} else if (sizeMode == nsSizeMode_Maximized) {
// We make the entire frame part of the client area. We leave the default
// frame sizes for left, right and bottom since Windows will automagically
// position the edges "offscreen" for maximized windows.
metrics.mOffset.top = metrics.mCaptionHeight;
} else if (mPIPWindow &&
!StaticPrefs::widget_windows_pip_decorations_enabled()) {
metrics.mOffset = metrics.DefaultMargins();
} else {
metrics.mOffset = NormalWindowNonClientOffset();
}
UpdateOpaqueRegionInternal();
if (aReflowWindow) {
// Force a reflow of content based on the new client
// dimensions.
ResetLayout();
}
return true;
}
void nsWindow::SetCustomTitlebar(bool aCustomTitlebar) {
if (!IsTopLevelWidget() || mBorderStyle == BorderStyle::None) {
return;
}
if (mCustomNonClient == aCustomTitlebar) {
return;
}
if (mHideChrome) {
mCustomTitlebarOnceChromeShows = Some(aCustomTitlebar);
return;
}
mCustomTitlebarOnceChromeShows.reset();
mCustomNonClient = aCustomTitlebar;
// Force a reflow of content based on the new client dimensions.
if (mCustomNonClient) {
UpdateNonClientMargins();
} else {
mCustomNonClientMetrics = {};
ResetLayout();
}
// Not needed for PiP windows, and using the Windows App SDK on
// these windows causes them to go under the taskbar (bug 1995838)
if (!mPIPWindow) {
WindowsUIUtils::SetIsTitlebarCollapsed(mWnd, mCustomNonClient);
}
}
void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
mCustomResizeMargin = aResizeMargin;
}
/**************************************************************
*
* SECTION: nsIWidget::SetCursor
*
* SetCursor and related utilities for manging cursor state.
*
**************************************************************/
// Set this component cursor
static HCURSOR CursorFor(nsCursor aCursor) {
switch (aCursor) {
case eCursor_select:
return ::LoadCursor(nullptr, IDC_IBEAM);
case eCursor_wait:
return ::LoadCursor(nullptr, IDC_WAIT);
case eCursor_hyperlink:
return ::LoadCursor(nullptr, IDC_HAND);
case eCursor_standard:
case eCursor_context_menu: // XXX See bug 258960.
return ::LoadCursor(nullptr, IDC_ARROW);
case eCursor_n_resize:
case eCursor_s_resize:
return ::LoadCursor(nullptr, IDC_SIZENS);
case eCursor_w_resize:
case eCursor_e_resize:
return ::LoadCursor(nullptr, IDC_SIZEWE);
case eCursor_nw_resize:
case eCursor_se_resize:
return ::LoadCursor(nullptr, IDC_SIZENWSE);
case eCursor_ne_resize:
case eCursor_sw_resize:
return ::LoadCursor(nullptr, IDC_SIZENESW);
case eCursor_crosshair:
return ::LoadCursor(nullptr, IDC_CROSS);
case eCursor_move:
return ::LoadCursor(nullptr, IDC_SIZEALL);
case eCursor_help:
return ::LoadCursor(nullptr, IDC_HELP);
case eCursor_copy: // CSS3
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
case eCursor_alias:
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
case eCursor_cell:
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
case eCursor_grab:
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
case eCursor_grabbing:
return ::LoadCursor(nsToolkit::mDllInstance,
MAKEINTRESOURCE(IDC_GRABBING));
case eCursor_spinning:
return ::LoadCursor(nullptr, IDC_APPSTARTING);
case eCursor_zoom_in:
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
case eCursor_zoom_out:
return ::LoadCursor(nsToolkit::mDllInstance,
MAKEINTRESOURCE(IDC_ZOOMOUT));
case eCursor_not_allowed:
case eCursor_no_drop:
return ::LoadCursor(nullptr, IDC_NO);
case eCursor_col_resize:
return ::LoadCursor(nsToolkit::mDllInstance,
MAKEINTRESOURCE(IDC_COLRESIZE));
case eCursor_row_resize:
return ::LoadCursor(nsToolkit::mDllInstance,
MAKEINTRESOURCE(IDC_ROWRESIZE));
case eCursor_vertical_text:
return ::LoadCursor(nsToolkit::mDllInstance,
MAKEINTRESOURCE(IDC_VERTICALTEXT));
case eCursor_all_scroll:
// XXX not 100% appropriate perhaps
return ::LoadCursor(nullptr, IDC_SIZEALL);
case eCursor_nesw_resize:
return ::LoadCursor(nullptr, IDC_SIZENESW);
case eCursor_nwse_resize:
return ::LoadCursor(nullptr, IDC_SIZENWSE);
case eCursor_ns_resize:
return ::LoadCursor(nullptr, IDC_SIZENS);
case eCursor_ew_resize:
return ::LoadCursor(nullptr, IDC_SIZEWE);
case eCursor_none:
return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
default:
NS_ERROR("Invalid cursor type");
return nullptr;
}
}
static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
CSSToLayoutDeviceScale aScale) {
if (!aCursor.IsCustom()) {
return nullptr;
}
nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
// Reject cursors greater than 128 pixels in either direction, to prevent
// spoofing.
// XXX ideally we should rescale. Also, we could modify the API to
// allow trusted content to set larger cursors.
if (size.width > 128 || size.height > 128) {
return nullptr;
}
LayoutDeviceIntSize layoutSize =
RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
LayoutDeviceIntPoint hotspot =
RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
HCURSOR cursor;
nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, nullptr, true,
hotspot, layoutSize, &cursor);
if (NS_FAILED(rv)) {
return nullptr;
}
return cursor;
}
void nsWindow::SetCursor(const Cursor& aCursor) {
static HCURSOR sCurrentHCursor = nullptr;
static bool sCurrentHCursorIsCustom = false;
mCursor = aCursor;
if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
// Cursors in windows are global, so even if our mUpdateCursor flag is
// false we always need to make sure the Windows cursor is up-to-date,
// since stuff like native drag and drop / resizers code can mutate it
// outside of this method.
::SetCursor(sCurrentHCursor);
return;
}
mUpdateCursor = false;
if (sCurrentHCursorIsCustom) {
::DestroyIcon(sCurrentHCursor);
}
sCurrentHCursor = nullptr;
sCurrentHCursorIsCustom = false;
sCurrentCursor = aCursor;
HCURSOR cursor = nullptr;
if (mCustomCursorAllowed) {
cursor = CursorForImage(aCursor, GetDefaultScale());
}
bool custom = false;
if (cursor) {
custom = true;
} else {
cursor = CursorFor(aCursor.mDefaultCursor);
}
if (!cursor) {
return;
}
sCurrentHCursor = cursor;
sCurrentHCursorIsCustom = custom;
::SetCursor(cursor);
}
/**************************************************************
*
* SECTION: nsIWidget::UpdateWindowDraggingRegion
*
* For setting the draggable titlebar region from CSS
* with -moz-window-dragging: drag.
*
**************************************************************/
void nsWindow::UpdateWindowDraggingRegion(
const LayoutDeviceIntRegion& aRegion) {
mDraggableRegion = aRegion;
}
/**************************************************************
*
* SECTION: nsIWidget::HideWindowChrome
*
* Show or hide window chrome.
*
**************************************************************/
void nsWindow::HideWindowChrome(bool aShouldHide) {
HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
if (!WinUtils::GetNSWindowPtr(hwnd)) {
NS_WARNING("Trying to hide window decorations in an embedded context");
return;
}
if (mHideChrome == aShouldHide) {
return;
}
// The desired style-flagset for fullscreen windows. (This happens to be all
// zeroes, but we don't need to rely on that.)
constexpr static const WindowStyles kFullscreenChromeStyles{.style = 0,
.ex = 0};
auto const [chromeless, currentChrome] =
kChromeStylesMask.split(Styles::FromHWND(hwnd));
Styles newChrome{}, oldChrome{};
mHideChrome = aShouldHide;
if (aShouldHide) {
newChrome = kFullscreenChromeStyles;
oldChrome = currentChrome;
} else {
// if there's nothing to "restore" it to, just use what's there now
oldChrome = mOldStyles.refOr(currentChrome);
newChrome = oldChrome;
if (mCustomTitlebarOnceChromeShows) {
SetCustomTitlebar(mCustomTitlebarOnceChromeShows.extract());
MOZ_ASSERT(!mCustomTitlebarOnceChromeShows);
}
}
mOldStyles = Some(oldChrome);
SetWindowStyles(hwnd, kChromeStylesMask.merge(chromeless, newChrome));
}
/**************************************************************
*
* SECTION: nsWindow::Invalidate
*
* Invalidate an area of the client for painting.
*
**************************************************************/
// Invalidate this component visible area
void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
bool aIncludeChildren) {
if (!mWnd) {
return;
}
#ifdef WIDGET_DEBUG_OUTPUT
debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
#endif // WIDGET_DEBUG_OUTPUT
DWORD flags = RDW_INVALIDATE;
if (aEraseBackground) {
flags |= RDW_ERASE;
}
if (aUpdateNCArea) {
flags |= RDW_FRAME;
}
if (aIncludeChildren) {
flags |= RDW_ALLCHILDREN;
}
VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
}
// Invalidate this component visible area
void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
if (mWnd) {
#ifdef WIDGET_DEBUG_OUTPUT
debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
#endif // WIDGET_DEBUG_OUTPUT
RECT rect;
rect.left = aRect.X();
rect.top = aRect.Y();
rect.right = aRect.XMost();
rect.bottom = aRect.YMost();
VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
}
}
static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_FULLSCREEN_TRANSITION_BEFORE:
case WM_FULLSCREEN_TRANSITION_AFTER: {
DWORD duration = (DWORD)lParam;
DWORD flags = AW_BLEND;
if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
flags |= AW_HIDE;
}
::AnimateWindow(hWnd, duration, flags);
// The message sender should have added ref for us.
NS_DispatchToMainThread(
already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
break;
}
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
return 0;
}
struct FullscreenTransitionInitData {
LayoutDeviceIntRect mBounds;
HANDLE mSemaphore;
HANDLE mThread;
HWND mWnd;
FullscreenTransitionInitData()
: mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
~FullscreenTransitionInitData() {
if (mSemaphore) {
::CloseHandle(mSemaphore);
}
if (mThread) {
::CloseHandle(mThread);
}
}
};
static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
// Initialize window class
static bool sInitialized = false;
if (!sInitialized) {
WNDCLASSW wc = {};
wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
wc.hInstance = nsToolkit::mDllInstance;
wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
wc.lpszClassName = kClassNameTransition;
::RegisterClassW(&wc);
sInitialized = true;
}
auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
nullptr, nsToolkit::mDllInstance, nullptr);
if (!wnd) {
::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
return 0;
}
// Since AnimateWindow blocks the thread of the transition window,
// we need to hide the cursor for that window, otherwise the system
// would show the busy pointer to the user.
::ShowCursor(false);
::SetWindowLongW(wnd, GWL_STYLE, 0);
::SetWindowLongW(
wnd, GWL_EXSTYLE,
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
data->mBounds.Width(), data->mBounds.Height(), 0);
data->mWnd = wnd;
::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
// The initialization data may no longer be valid
// after we release the semaphore.
data = nullptr;
MSG msg;
while (::GetMessageW(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::ShowCursor(true);
::DestroyWindow(wnd);
return 0;
}
class FullscreenTransitionData final : public nsISupports {
public:
NS_DECL_ISUPPORTS
explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
MOZ_ASSERT(NS_IsMainThread(),
"FullscreenTransitionData "
"should be constructed in the main thread");
}
const HWND mWnd;
private:
~FullscreenTransitionData() {
MOZ_ASSERT(NS_IsMainThread(),
"FullscreenTransitionData "
"should be deconstructed in the main thread");
::PostMessageW(mWnd, WM_DESTROY, 0, 0);
}
};
NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
/* virtual */
bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
FullscreenTransitionInitData initData;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
const DesktopIntRect rect = screen->GetRectDisplayPix();
initData.mBounds =
LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
// Create a semaphore for synchronizing the window handle which will
// be created by the transition thread and used by the main thread for
// posting the transition messages.
initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
if (initData.mSemaphore) {
initData.mThread = ::CreateThread(
nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
if (initData.mThread) {
::WaitForSingleObject(initData.mSemaphore, INFINITE);
}
}
if (!initData.mWnd) {
return false;
}
mTransitionWnd = initData.mWnd;
auto data = new FullscreenTransitionData(initData.mWnd);
*aData = data;
NS_ADDREF(data);
return true;
}
/* virtual */
void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback) {
auto data = static_cast<FullscreenTransitionData*>(aData);
nsCOMPtr<nsIRunnable> callback = aCallback;
UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
: WM_FULLSCREEN_TRANSITION_AFTER;
WPARAM wparam = (WPARAM)callback.forget().take();
::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
}
/* virtual */
void nsWindow::CleanupFullscreenTransition() {
MOZ_ASSERT(NS_IsMainThread(),
"CleanupFullscreenTransition "
"should only run on the main thread");
mTransitionWnd = nullptr;
}
void nsWindow::TryDwmResizeHack() {
// The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
// for DWM's occasional and not-entirely-predictable failure to update its
// internal state when the client area of a window changes without changing
// the window size. The effect of this is that DWM will clip the content of
// the window to its former client area.
//
// It is not known under what circumstances the bug will trigger. Windows 11
// is known to be required, but many Windows 11 machines do not exhibit the
// issue. Even machines that _do_ exhibit it will sometimes not do so when
// apparently-irrelevant changes are made to the configuration. (See bug
// 1763981.)
//
// The bug is triggered by Firefox when a maximized window (which has window
// decorations) becomes fullscreen (which doesn't). To work around this, if we
// think it may occur, we "flicker-resize" the relevant window -- that is, we
// reduce its height by 1px, then restore it. This causes DWM to acquire the
// new client-area metrics.
//
// Note that, in particular, this bug will not occur when using a separate
// compositor window, as our compositor windows never have any nonclient area.
//
// This is admittedly a sledgehammer where a screwdriver should suffice.
// ---------------------------------------------------------------------------
// Regardless of preferences or heuristics, only apply the hack if this is the
// first time we've entered fullscreen across the entire Firefox session.
// (Subsequent transitions to fullscreen, even with different windows, don't
// appear to induce the bug.)
{
// (main thread only; `atomic` not needed)
static bool sIsFirstFullscreenEntry = true;
bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
sIsFirstFullscreenEntry = false;
if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
return;
}
MOZ_LOG(gWindowsLog, LogLevel::Verbose,
("%s: first fullscreen entry", __PRETTY_FUNCTION__));
}
// Check whether to try to apply the DWM resize hack, based on the override
// pref and/or some internal heuristics.
{
const auto hackApplicationHeuristics = [&]() -> bool {
// The bug has only been seen under Windows 11. (At time of writing, this
// is the latest version of Windows.)
if (!IsWin11OrLater()) {
return false;
}
KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
// This should never happen...
MOZ_ASSERT(kc);
// ... so if it does, we are in uncharted territory: don't apply the hack.
if (!kc) {
return false;
}
// The bug doesn't occur when we're using a separate compositor window
// (since the compositor window always comprises exactly its client area,
// with no non-client border).
if (kc->GetUseCompositorWnd()) {
return false;
}
// Otherwise, apply the hack.
return true;
};
// Figure out whether or not we should perform the hack, and -- arguably
// more importantly -- log that decision.
bool const shouldApplyHack = [&]() {
enum Reason : bool { Pref, Heuristics };
auto const msg = [&](bool decision, Reason reason) -> bool {
MOZ_LOG(gWindowsLog, LogLevel::Verbose,
("%s %s per %s", decision ? "applying" : "skipping",
"DWM resize hack", reason == Pref ? "pref" : "heuristics"));
return decision;
};
switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
case 0:
return msg(false, Pref);
case 1:
return msg(true, Pref);
default: // treat all other values as `auto`
return msg(hackApplicationHeuristics(), Heuristics);
}
}();
if (!shouldApplyHack) {
return;
}
}
// The DWM bug is believed to involve a race condition: some users have
// reported that setting a custom theme or adding unused command-line
// parameters sometimes causes the bug to vanish.
//
// Out of an abundance of caution, we therefore apply the hack in a later
// event, rather than inline.
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
HWND const hwnd = self->GetWindowHandle();
if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
("DWM resize hack: window no longer fullscreen; aborting"));
return;
}
RECT origRect;
if (!::GetWindowRect(hwnd, &origRect)) {
MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
("DWM resize hack: could not get window size?!"));
return;
}
LONG const x = origRect.left;
LONG const y = origRect.top;
LONG const width = origRect.right - origRect.left;
LONG const height = origRect.bottom - origRect.top;
MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
auto const onExit =
MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
self->mIsPerformingDwmFlushHack = oldVal;
});
self->mIsPerformingDwmFlushHack = true;
MOZ_LOG(gWindowsLog, LogLevel::Debug,
("beginning DWM resize hack for HWND %08" PRIXPTR,
uintptr_t(hwnd)));
::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
::MoveWindow(hwnd, x, y, width, height, TRUE);
MOZ_LOG(gWindowsLog, LogLevel::Debug,
("concluded DWM resize hack for HWND %08" PRIXPTR,
uintptr_t(hwnd)));
}));
}
void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
// HACK: Potentially flicker-resize the window, to force DWM to get the right
// client-area information.
if (aFullScreen) {
TryDwmResizeHack();
}
// Hide chrome and reposition window. Note this will also cache dimensions for
// restoration, so it should only be called once per fullscreen request.
//
// Don't do this when minimized, since our bounds make no sense then, nor when
// coming back from that state.
const bool toOrFromMinimized =
mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
aOldSizeMode == nsSizeMode_Minimized;
if (!toOrFromMinimized) {
InfallibleMakeFullScreen(aFullScreen);
}
// Possibly notify the taskbar that we have changed our fullscreen mode.
TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
}
nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
mFrameState->EnsureFullscreenMode(aFullScreen);
return NS_OK;
}
/**************************************************************
*
* SECTION: Native data storage
*
* nsIWidget::GetNativeData
*
* Set or clear native data based on a constant.
*
**************************************************************/
// Return some native data according to aDataType
void* nsWindow::GetNativeData(uint32_t aDataType) {
switch (aDataType) {
case NS_NATIVE_WIDGET:
case NS_NATIVE_WINDOW:
case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
return (void*)mWnd;
case NS_NATIVE_GRAPHIC:
MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
return nullptr;
case NS_RAW_NATIVE_IME_CONTEXT: {
void* pseudoIMEContext = GetPseudoIMEContext();
if (pseudoIMEContext) {
return pseudoIMEContext;
}
return IMEHandler::GetNativeData(this, aDataType);
}
default:
break;
}
return nullptr;
}
/**************************************************************
*
* SECTION: nsIWidget::SetTitle
*
* Set the main windows title text.
*
**************************************************************/
nsresult nsWindow::SetTitle(const nsAString& aTitle) {
const nsString& strTitle = PromiseFlatString(aTitle);
AutoRestore<bool> sendingText(mSendingSetText);
mSendingSetText = true;
::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
return NS_OK;
}
/**************************************************************
*
* SECTION: nsIWidget::SetIcon
*
* Set the main windows icon.
*
**************************************************************/
void nsWindow::SetBigIcon(HICON aIcon) {
HICON icon =
(HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
if (icon) {
::DestroyIcon(icon);
}
mIconBig = aIcon;
}
void nsWindow::SetSmallIcon(HICON aIcon) {
HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
(LPARAM)aIcon);
if (icon) {
::DestroyIcon(icon);
}
mIconSmall = aIcon;
}
void nsWindow::SetIcon(const nsAString& aIconSpec) {
// Assume the given string is a local identifier for an icon file.
nsCOMPtr<nsIFile> iconFile;
ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
if (!iconFile) return;
nsAutoString iconPath;
iconFile->GetPath(iconPath);
// XXX this should use MZLU (see bug 239279)
::SetLastError(0);
HICON bigIcon =
(HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
::GetSystemMetrics(SM_CXICON),
::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
HICON smallIcon =
(HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
::GetSystemMetrics(SM_CXSMICON),
::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
if (bigIcon) {
SetBigIcon(bigIcon);
}
#ifdef DEBUG_SetIcon
else {
NS_LossyConvertUTF16toASCII cPath(iconPath);
MOZ_LOG(gWindowsLog, LogLevel::Info,
("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
::GetLastError()));
}
#endif
if (smallIcon) {
SetSmallIcon(smallIcon);
}
#ifdef DEBUG_SetIcon
else {
NS_LossyConvertUTF16toASCII cPath(iconPath);
MOZ_LOG(gWindowsLog, LogLevel::Info,
("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
::GetLastError()));
}
#endif
}
void nsWindow::SetBigIconNoData() {
HICON bigIcon =
::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
SetBigIcon(bigIcon);
}
void nsWindow::SetSmallIconNoData() {
HICON smallIcon =
::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
SetSmallIcon(smallIcon);
}
/**************************************************************
*
* SECTION: nsIWidget::WidgetToScreenOffset
*
* Return this widget's origin in screen coordinates.
*
**************************************************************/
LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
POINT point;
point.x = 0;
point.y = 0;
::ClientToScreen(mWnd, &point);
return LayoutDeviceIntPoint(point.x, point.y);
}
LayoutDeviceIntMargin nsWindow::NormalSizeModeClientToWindowMargin() {
if (mWindowType == WindowType::Popup) {
return {};
}
if (mCustomNonClient) {
return NonClientSizeMargin(NormalWindowNonClientOffset());
}
// Just use a dummy 200x200 at (200, 200) client rect as the rect.
RECT clientRect;
clientRect.left = 200;
clientRect.top = 200;
clientRect.right = 400;
clientRect.bottom = 400;
auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
return {aRect.left, aRect.top, aRect.right - aRect.left,
aRect.bottom - aRect.top};
};
RECT windowRect = clientRect;
::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
return ToRect(windowRect) - ToRect(clientRect);
}
/**************************************************************
*
* SECTION: nsIWidget::EnableDragDrop
*
* Enables/Disables drag and drop of files on this widget.
*
**************************************************************/
void nsWindow::EnableDragDrop(bool aEnable) {
if (!mWnd) {
// Return early if the window already closed
return;
}
if (aEnable) {
if (!mNativeDragTarget) {
mNativeDragTarget = new nsNativeDragTarget(this);
mNativeDragTarget->AddRef();
::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
}
} else {
if (mWnd && mNativeDragTarget) {
::RevokeDragDrop(mWnd);
mNativeDragTarget->DragCancel();
NS_RELEASE(mNativeDragTarget);
}
}
}
/**************************************************************
*
* SECTION: nsIWidget::CaptureMouse
*
* Enables/Disables system mouse capture.
*
**************************************************************/
void nsWindow::CaptureMouse(bool aCapture) {
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
if (aCapture) {
mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
::SetCapture(mWnd);
} else {
mTrack.dwFlags = TME_LEAVE;
::ReleaseCapture();
}
sIsInMouseCapture = aCapture;
TrackMouseEvent(&mTrack);
}
/**************************************************************
*
* SECTION: nsIWidget::CaptureRollupEvents
*
* Dealing with event rollup on destroy for popups. Enables &
* Disables system capture of any and all events that would
* cause a dropdown to be rolled up.
*
**************************************************************/
void nsWindow::CaptureRollupEvents(bool aDoCapture) {
if (aDoCapture) {
if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
RegisterSpecialDropdownHooks();
}
sProcessHook = true;
} else {
sProcessHook = false;
UnregisterSpecialDropdownHooks();
}
}
/**************************************************************
*
* SECTION: nsIWidget::GetAttention
*
* Bring this window to the user's attention.
*
**************************************************************/
// Draw user's attention to this window until it comes to foreground.
nsresult nsWindow::GetAttention(int32_t aCycleCount) {
// Got window?
if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
HWND fgWnd = ::GetForegroundWindow();
// Don't flash if the flash count is 0 or if the foreground window is our
// window handle or that of our owned-most window.
if (aCycleCount == 0 || flashWnd == fgWnd ||
flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
return NS_OK;
}
DWORD defaultCycleCount = 0;
::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
::FlashWindowEx(&flashInfo);
return NS_OK;
}
void nsWindow::StopFlashing() {
HWND flashWnd = mWnd;
while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
flashWnd = ownerWnd;
}
FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
::FlashWindowEx(&flashInfo);
}
/**************************************************************
*
* SECTION: nsIWidget::HasPendingInputEvent
*
* Ask whether there user input events pending. All input events are
* included, including those not targeted at this nsIwidget instance.
*
**************************************************************/
bool nsWindow::HasPendingInputEvent() {
// If there is pending input or the user is currently
// moving the window then return true.
// Note: When the user is moving the window WIN32 spins
// a separate event loop and input events are not
// reported to the application.
if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
GUITHREADINFO guiInfo;
guiInfo.cbSize = sizeof(GUITHREADINFO);
if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
}
/**************************************************************
*
* SECTION: nsIWidget::GetWindowRenderer
*
* Get the window renderer associated with this widget.
*
**************************************************************/
WindowRenderer* nsWindow::GetWindowRenderer() {
if (mWindowRenderer) {
return mWindowRenderer;
}
EnsureLocalesChangedObserver();
// Try OMTC first.
if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
CreateCompositor();
}
if (!mWindowRenderer) {
MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
MOZ_ASSERT(!mCompositorWidgetDelegate);
// Ensure we have a widget proxy even if we're not using the compositor,
// since all our transparent window handling lives there.
WinCompositorWidgetInitData initData(
reinterpret_cast<uintptr_t>(mWnd),
reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
mTransparencyMode);
// If we're not using the compositor, the options don't actually matter.
CompositorOptions options(false, false);
mBasicLayersSurface =
new InProcessWinCompositorWidget(initData, options, this);
mCompositorWidgetDelegate = mBasicLayersSurface;
mWindowRenderer = CreateFallbackRenderer();
}
NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
if (mWindowRenderer) {
// Update the size constraints now that the layer manager has been
// created.
KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
if (knowsCompositor) {
SizeConstraints c = mSizeConstraints;
mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
nsIWidget::SetSizeConstraints(c);
}
}
return mWindowRenderer;
}
/**************************************************************
*
* SECTION: nsIWidget::SetCompositorWidgetDelegate
*
* Called to connect the nsWindow to the delegate providing
* platform compositing API access.
*
**************************************************************/
void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
if (delegate) {
mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
MOZ_ASSERT(mCompositorWidgetDelegate,
"nsWindow::SetCompositorWidgetDelegate called with a "
"non-PlatformCompositorWidgetDelegate");
} else {
mCompositorWidgetDelegate = nullptr;
}
}
/**************************************************************
*
* SECTION: nsIWidget::OnDefaultButtonLoaded
*
* Called after the dialog is loaded and it has a default button.
*
**************************************************************/
nsresult nsWindow::OnDefaultButtonLoaded(
const LayoutDeviceIntRect& aButtonRect) {
if (aButtonRect.IsEmpty()) return NS_OK;
// Don't snap when we are not active.
HWND activeWnd = ::GetActiveWindow();
if (activeWnd != ::GetForegroundWindow() ||
WinUtils::GetTopLevelHWND(mWnd, true) !=
WinUtils::GetTopLevelHWND(activeWnd, true)) {
return NS_OK;
}
bool isAlwaysSnapCursor =
Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
if (!isAlwaysSnapCursor) {
BOOL snapDefaultButton;
if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
0) ||
!snapDefaultButton)
return NS_OK;
}
LayoutDeviceIntRect widgetRect = GetScreenBounds();
LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
buttonRect.Y() + buttonRect.Height() / 2);
// The center of the button can be outside of the widget.
// E.g., it could be hidden by scrolling.
if (!widgetRect.Contains(centerOfButton)) {
return NS_OK;
}
if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
NS_ERROR("SetCursorPos failed");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
uint32_t nsWindow::GetMaxTouchPoints() const {
return WinUtils::GetMaxTouchPoints();
}
void nsWindow::SetIsEarlyBlankWindow(bool aIsEarlyBlankWindow) {
if (mIsEarlyBlankWindow == aIsEarlyBlankWindow) {
return;
}
mIsEarlyBlankWindow = aIsEarlyBlankWindow;
if (!aIsEarlyBlankWindow) {
// We skip processing WM_PAINT messages while we're the blank window;
// ensure we get one to do any work we might have missed.
MaybeInvalidateTranslucentRegion();
}
}
/**************************************************************
**************************************************************
**
** BLOCK: Moz Events
**
** Moz GUI event management.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: Mozilla event initialization
*
* Helpers for initializing moz events.
*
**************************************************************/
// Event initialization
void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
if (nullptr == aPoint) { // use the point from the event
// get the message position in client coordinates
if (mWnd != nullptr) {
DWORD pos = ::GetMessagePos();
POINT cpos;
cpos.x = GET_X_LPARAM(pos);
cpos.y = GET_Y_LPARAM(pos);
::ScreenToClient(mWnd, &cpos);
event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
} else {
event.mRefPoint = LayoutDeviceIntPoint(0, 0);
}
} else {
// use the point override if provided
event.mRefPoint = *aPoint;
}
event.AssignEventTime(CurrentMessageWidgetEventTime());
}
WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
LONG messageTime = ::GetMessageTime();
return WidgetEventTime(GetMessageTimeStamp(messageTime));
}
/**************************************************************
*
* SECTION: Moz event dispatch helpers
*
* Helpers for dispatching different types of moz events.
*
**************************************************************/
// Main event dispatch. Invokes callback and ProcessEvent method on
// Event Listener object. Part of nsIWidget.
nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
nsEventStatus& aStatus) {
#ifdef WIDGET_DEBUG_OUTPUT
debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
#endif // WIDGET_DEBUG_OUTPUT
aStatus = nsEventStatus_eIgnore;
// Top level windows can have a view attached which requires events be sent
// to the underlying base window and the view. Added when we combined the
// base chrome window with the main content child for nc client area (title
// bar) rendering.
if (mAttachedWidgetListener) {
aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
} else if (mWidgetListener) {
aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
}
// the window can be destroyed during processing of seemingly innocuous events
// like, say, mousedowns due to the magic of scripting. mousedowns will return
// nsEventStatus_eIgnore, which causes problems with the deleted window.
// therefore:
if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
return NS_OK;
}
bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
WidgetGUIEvent event(true, aMsg, this);
InitEvent(event);
bool result = DispatchWindowEvent(event);
return result;
}
bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
nsEventStatus status = DispatchInputEvent(event).mContentStatus;
return ConvertStatus(status);
}
bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
nsEventStatus status;
DispatchEvent(aEvent, status);
return ConvertStatus(status);
}
bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
nsEventStatus status =
DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
return ConvertStatus(status);
}
// Recursively dispatch synchronous paints for nsIWidget
// descendants with invalidated rectangles.
BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
if (proc == (LONG_PTR)&nsWindow::WindowProc) {
// its one of our windows so check to see if it has a
// invalidated rect. If it does. Dispatch a synchronous
// paint.
if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
}
return TRUE;
}
// Check for pending paints and dispatch any pending paint
// messages for any nsIWidget which is a descendant of the
// top-level window that *this* window is embedded within.
//
// Note: We do not dispatch pending paint messages for non
// nsIWidget managed windows.
void nsWindow::DispatchPendingEvents() {
// We need to ensure that reflow events do not get starved.
// At the same time, we don't want to recurse through here
// as that would prevent us from dispatching starved paints.
static int recursionBlocker = 0;
if (recursionBlocker++ == 0) {
NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
--recursionBlocker;
}
// Quickly check to see if there are any paint events pending,
// but only dispatch them if it has been long enough since the
// last paint completed.
if (::GetQueueStatus(QS_PAINT) &&
((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
// Find the top level window.
HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
// Dispatch pending paints for topWnd and all its descendant windows.
// Note: EnumChildWindows enumerates all descendant windows not just
// the children (but not the window itself).
nsWindow::DispatchStarvedPaints(topWnd, 0);
::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
}
}
void nsWindow::DispatchCustomEvent(const nsString& eventName) {
if (Document* doc = GetDocument()) {
if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
}
}
}
bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
LayoutDeviceIntPoint aEventPoint) {
// Allow users to start dragging by double-tapping.
if (aEventMessage == eMouseDoubleClick) {
return true;
}
// In chrome UI, allow touchdownstartsdrag attributes
// to cause any touchdown event to trigger a drag.
if (aEventMessage == eMouseDown) {
WidgetMouseEvent hittest(true, eMouseHitTest, this,
WidgetMouseEvent::eReal);
hittest.mRefPoint = aEventPoint;
hittest.mIgnoreRootScrollFrame = true;
hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
DispatchInputEvent(&hittest);
if (EventTarget* target = hittest.GetDOMEventTarget()) {
if (nsIContent* content = nsIContent::FromEventTarget(target)) {
// Check if the element or any parent element has the
// attribute we're looking for.
for (Element* element = content->GetAsElementOrParentElement(); element;
element = element->GetParentElement()) {
nsAutoString startDrag;
element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
if (!startDrag.IsEmpty()) {
return true;
}
}
}
}
}
return false;
}
// Deal with all sort of mouse event
bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
LPARAM lParam, bool aIsContextMenuKey,
int16_t aButton, uint16_t aInputSource,
WinPointerInfo* aPointerInfo,
IsNonclient aIsNonclient) {
ContextMenuPreventer contextMenuPreventer(this);
bool result = false;
UserActivity();
if (!mWidgetListener) {
return result;
}
LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
// Suppress mouse moves caused by widget creation. Make sure to do this early
// so that we update sLastMouseMovePointByAnyPointer even for touch-induced
// mousemove events.
if (aEventMessage == eMouseMove) {
if (LastMouseMoveData::ShouldIgnoreMouseMoveOf(
mpScreen, aInputSource,
aPointerInfo ? aPointerInfo->pointerId : 0)) {
return result;
}
LastMouseMoveData::WillDispatchMouseMoveOf(
mpScreen, aInputSource, aPointerInfo ? aPointerInfo->pointerId : 0);
}
if (!bool(aIsNonclient) && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
if (mTouchWindow) {
// If mTouchWindow is true, then we must have APZ enabled and be
// feeding it raw touch events. In that case we only want to
// send touch-generated mouse events to content if they should
// start a touch-based drag-and-drop gesture, such as on
// double-tapping or when tapping elements marked with the
// touchdownstartsdrag attribute in chrome UI.
MOZ_ASSERT(mAPZC);
if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
aEventMessage = eMouseTouchDrag;
} else {
return result;
}
}
}
uint32_t pointerId =
aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
switch (aEventMessage) {
case eMouseDown:
// If the mouse was pressed down in the nonclient region, we do not
// capture the mouse. (Doing so would cause Windows to start sending us
// client-area mouse messages instead of nonclient-area messages.)
if (!bool(aIsNonclient)) {
CaptureMouse(true);
}
break;
// eMouseMove and eMouseExitFromWidget are here because we need to make
// sure capture flag isn't left on after a drag where we wouldn't see a
// button up message (see bug 324131).
case eMouseUp:
case eMouseMove:
case eMouseExitFromWidget:
if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
sIsInMouseCapture)
CaptureMouse(false);
break;
default:
break;
} // switch
Maybe<WidgetPointerEvent> pointerEvent;
Maybe<WidgetMouseEvent> mouseEvent;
if (IsPointerEventMessage(aEventMessage)) {
pointerEvent.emplace(true, aEventMessage, this,
aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
: WidgetMouseEvent::eNormal);
} else {
mouseEvent.emplace(true, aEventMessage, this, WidgetMouseEvent::eReal,
aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
: WidgetMouseEvent::eNormal);
}
WidgetMouseEvent& mouseOrPointerEvent =
pointerEvent.isSome() ? pointerEvent.ref() : mouseEvent.ref();
if (aEventMessage == eContextMenu && aIsContextMenuKey) {
LayoutDeviceIntPoint zero(0, 0);
InitEvent(mouseOrPointerEvent, &zero);
} else {
InitEvent(mouseOrPointerEvent, &eventPoint);
}
ModifierKeyState modifierKeyState;
modifierKeyState.InitInputEvent(mouseOrPointerEvent);
// eContextMenu with Shift state is special. It won't fire "contextmenu"
// event in the web content for blocking web content to prevent its default.
// However, Shift+F10 is a standard shortcut key on Windows. Therefore,
// this should not block web page to prevent its default. I.e., it should
// behave same as ContextMenu key without Shift key.
// XXX Should we allow to block web page to prevent its default with
// Ctrl+Shift+F10 or Alt+Shift+F10 instead?
if (aEventMessage == eContextMenu && aIsContextMenuKey &&
mouseOrPointerEvent.IsShift() &&
NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
mouseOrPointerEvent.mModifiers &= ~MODIFIER_SHIFT;
}
mouseOrPointerEvent.mButton = aButton;
mouseOrPointerEvent.mInputSource = aInputSource;
if (aPointerInfo) {
// Mouse events from Windows WM_POINTER*. Fill more information in
// WidgetMouseEvent.
mouseOrPointerEvent.AssignPointerHelperData(*aPointerInfo);
mouseOrPointerEvent.mPressure = aPointerInfo->mPressure;
mouseOrPointerEvent.mButtons = aPointerInfo->mButtons;
} else {
// If we get here the mouse events must be from non-touch sources, so
// convert it to pointer events as well
mouseOrPointerEvent.convertToPointer = true;
mouseOrPointerEvent.pointerId = pointerId;
}
if (aEventMessage == eContextMenu &&
aInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
MOZ_ASSERT(!aIsContextMenuKey);
mouseOrPointerEvent.mContextMenuTrigger = WidgetMouseEvent::eNormal;
}
// Static variables used to distinguish simple-, double- and triple-clicks.
static POINT sLastMousePoint = {0};
static LONG sLastMouseDownTime = 0L;
static LONG sLastClickCount = 0L;
static BYTE sLastMouseButton = 0;
bool insideMovementThreshold =
(DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
(short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
(DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
(short)::GetSystemMetrics(SM_CYDOUBLECLK));
BYTE eventButton;
switch (aButton) {
case MouseButton::ePrimary:
eventButton = VK_LBUTTON;
break;
case MouseButton::eMiddle:
eventButton = VK_MBUTTON;
break;
case MouseButton::eSecondary:
eventButton = VK_RBUTTON;
break;
default:
eventButton = 0;
break;
}
// Doubleclicks are used to set the click count, then changed to mousedowns
// We're going to time double-clicks from mouse *up* to next mouse *down*
LONG curMsgTime = ::GetMessageTime();
switch (aEventMessage) {
case eMouseDoubleClick:
mouseOrPointerEvent.mMessage = eMouseDown;
mouseOrPointerEvent.mButton = aButton;
sLastClickCount = 2;
sLastMouseDownTime = curMsgTime;
break;
case eMouseUp:
// remember when this happened for the next mouse down
sLastMousePoint.x = eventPoint.x;
sLastMousePoint.y = eventPoint.y;
sLastMouseButton = eventButton;
break;
case eMouseDown:
// now look to see if we want to convert this to a double- or triple-click
if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
insideMovementThreshold && eventButton == sLastMouseButton) {
sLastClickCount++;
} else {
// reset the click count, to count *this* click
sLastClickCount = 1;
}
// Set last Click time on MouseDown only
sLastMouseDownTime = curMsgTime;
break;
case eMouseMove:
if (!insideMovementThreshold) {
sLastClickCount = 0;
}
break;
case eMouseExitFromWidget:
mouseOrPointerEvent.mExitFrom =
Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
: WidgetMouseEvent::ePlatformChild);
break;
default:
break;
}
mouseOrPointerEvent.mClickCount = sLastClickCount;
#ifdef NS_DEBUG_XX
MOZ_LOG(gWindowsLog, LogLevel::Info,
("Msg Time: %d Click Count: %d\n", curMsgTime,
mouseOrPointerEvent.mClickCount));
#endif
// call the event callback
if (mWidgetListener) {
if (aEventMessage == eMouseMove) {
LayoutDeviceIntRect rect = GetBounds();
rect.MoveTo(0, 0);
if (rect.Contains(mouseOrPointerEvent.mRefPoint)) {
if (sCurrentWindow == nullptr || sCurrentWindow != this) {
if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
sCurrentWindow->DispatchMouseEvent(
eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
aInputSource, aPointerInfo);
}
sCurrentWindow = this;
if (!mInDtor) {
LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
sCurrentWindow->DispatchMouseEvent(
eMouseEnterIntoWidget, wParam, pos, false,
MouseButton::ePrimary, aInputSource, aPointerInfo);
}
}
}
} else if (aEventMessage == eMouseExitFromWidget) {
if (sCurrentWindow == this) {
sCurrentWindow = nullptr;
}
}
nsIWidget::ContentAndAPZEventStatus eventStatus =
DispatchInputEvent(&mouseOrPointerEvent);
contextMenuPreventer.Update(mouseOrPointerEvent, eventStatus);
return ConvertStatus(eventStatus.mContentStatus);
}
return result;
}
HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
// retrieve the toplevel window or dialogue
HWND toplevelWnd = nullptr;
while (aCurWnd) {
toplevelWnd = aCurWnd;
nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
if (win) {
if (win->mWindowType == WindowType::TopLevel ||
win->mWindowType == WindowType::Dialog) {
break;
}
}
aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
}
return toplevelWnd;
}
void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
if (aIsActivate && mPickerDisplayCount) {
// We disable the root window when a picker opens. See PickerOpen. When the
// picker closes (but before PickerClosed is called), our window will get
// focus, but it will still be disabled. This confuses the focus system.
// Therefore, we ignore this focus and explicitly call this function once
// we re-enable the window. Rarely, the picker seems to re-enable our root
// window before we do, but for simplicity, we always ignore focus before
// the final call to PickerClosed. See bug 1883568 for further details.
return;
}
if (aIsActivate) {
sJustGotActivate = false;
}
sJustGotDeactivate = false;
mLastKillFocusWindow = nullptr;
HWND toplevelWnd = GetTopLevelForFocus(mWnd);
if (toplevelWnd) {
nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
if (win && win->mWidgetListener) {
if (aIsActivate) {
win->mWidgetListener->WindowActivated();
} else {
win->mWidgetListener->WindowDeactivated();
}
}
}
}
HWND nsWindow::WindowAtMouse() {
DWORD pos = ::GetMessagePos();
POINT mp;
mp.x = GET_X_LPARAM(pos);
mp.y = GET_Y_LPARAM(pos);
return ::WindowFromPoint(mp);
}
bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
HWND mouseWnd = WindowAtMouse();
// WinUtils::GetTopLevelHWND() will return a HWND for the window frame
// (which includes the non-client area). If the mouse has moved into
// the non-client area, we should treat it as a top-level exit.
HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
if (mouseWnd == mouseTopLevel) return true;
return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
}
/**************************************************************
*
* SECTION: IPC
*
* IPC related helpers.
*
**************************************************************/
// static
bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
switch (aMsg) {
case WM_SETFOCUS:
case WM_KILLFOCUS:
case WM_ENABLE:
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED:
case WM_PARENTNOTIFY:
case WM_ACTIVATEAPP:
case WM_NCACTIVATE:
case WM_ACTIVATE:
case WM_CHILDACTIVATE:
case WM_IME_SETCONTEXT:
case WM_IME_NOTIFY:
case WM_SHOWWINDOW:
case WM_CANCELMODE:
case WM_MOUSEACTIVATE:
case WM_CONTEXTMENU:
aResult = 0;
return true;
case WM_SETTINGCHANGE:
case WM_SETCURSOR:
return false;
}
#ifdef DEBUG
char szBuf[200];
sprintf(szBuf,
"An unhandled ISMEX_SEND message was received during spin loop! (%X)",
aMsg);
NS_WARNING(szBuf);
#endif
return false;
}
void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
MOZ_ASSERT_IF(
msg != WM_GETOBJECT,
!mozilla::ipc::MessageChannel::IsPumpingMessages() ||
mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
// Modal UI being displayed in windowless plugins.
if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
(InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
LRESULT res;
if (IsAsyncResponseEvent(msg, res)) {
ReplyMessage(res);
}
return;
}
// Handle certain sync plugin events sent to the parent which
// trigger ipc calls that result in deadlocks.
DWORD dwResult = 0;
bool handled = false;
switch (msg) {
// Windowless flash sending WM_ACTIVATE events to the main window
// via calls to ShowWindow.
case WM_ACTIVATE:
if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
IsWindow((HWND)lParam)) {
// Check for Adobe Reader X sync activate message from their
// helper window and ignore. Fixes an annoying focus problem.
if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
ISMEX_SEND) {
wchar_t szClass[10];
HWND focusWnd = (HWND)lParam;
if (IsWindowVisible(focusWnd) &&
GetClassNameW(focusWnd, szClass,
sizeof(szClass) / sizeof(char16_t)) &&
!wcscmp(szClass, L"Edit") &&
!WinUtils::IsOurProcessWindow(focusWnd)) {
break;
}
}
handled = true;
}
break;
// Plugins taking or losing focus triggering focus app messages.
case WM_SETFOCUS:
case WM_KILLFOCUS:
// Windowed plugins that pass sys key events to defwndproc generate
// WM_SYSCOMMAND events to the main window.
case WM_SYSCOMMAND:
// Windowed plugins that fire context menu selection events to parent
// windows.
case WM_CONTEXTMENU:
// IME events fired as a result of synchronous focus changes
case WM_IME_SETCONTEXT:
handled = true;
break;
}
if (handled &&
(InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
ReplyMessage(dwResult);
}
}
/**************************************************************
**************************************************************
**
** BLOCK: Native events
**
** Main Windows message handlers and OnXXX handlers for
** Windows event handling.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: Wind proc.
*
* The main Windows event procedures and associated
* message processing methods.
*
**************************************************************/
static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
int32_t x, int32_t y) {
HMENU hMenu = GetSystemMenu(hWnd, FALSE);
if (NS_WARN_IF(!hMenu)) {
return false;
}
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE;
mii.fType = 0;
// update the options
mii.fState = MF_ENABLED;
SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
mii.fState = MF_GRAYED;
switch (sizeMode) {
case nsSizeMode_Fullscreen:
// intentional fall through
case nsSizeMode_Maximized:
SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
break;
case nsSizeMode_Minimized:
SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
break;
case nsSizeMode_Normal:
SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
break;
case nsSizeMode_Invalid:
NS_ASSERTION(false, "Did the argument come from invalid IPC?");
break;
default:
MOZ_ASSERT_UNREACHABLE("Unhandled nsSizeMode value detected");
break;
}
LPARAM cmd = TrackPopupMenu(hMenu,
TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD |
TPM_TOPALIGN |
(isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN),
x, y, 0, hWnd, nullptr);
if (!cmd) {
return false;
}
PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
return true;
}
// The WndProc procedure for all nsWindows in this toolkit. This merely catches
// SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
// and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
mozilla::ipc::CancelCPOWs();
BackgroundHangMonitor().NotifyActivity();
return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
wParam, lParam);
}
LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
// This message was sent to the FAKETRACKPOINTSCROLLABLE.
if (msg == WM_HSCROLL) {
// Route WM_HSCROLL messages to the main window.
hWnd = ::GetParent(::GetParent(hWnd));
} else {
// Handle all other messages with its original window procedure.
WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
}
}
// Get the window which caused the event and ask it to process the message
nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
NS_ASSERTION(targetWindow, "nsWindow* is null!");
if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
// Hold the window for the life of this method, in case it gets
// destroyed during processing, unless we're in the dtor already.
nsCOMPtr<nsIWidget> kungFuDeathGrip;
if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
// Create this here so that we store the last rolled up popup until after
// the event has been processed.
nsAutoRollup autoRollup;
LRESULT popupHandlingResult;
if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult)) {
return popupHandlingResult;
}
// Call ProcessMessage
LRESULT retValue;
if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
return retValue;
}
LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
wParam, lParam);
return res;
}
const char16_t* GetQuitType() {
if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
DWORD cchCmdLine = 0;
HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
&cchCmdLine, nullptr);
if (rc == S_OK) {
return u"os-restart";
}
}
return nullptr;
}
bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
LPARAM& aLParam,
MSGResult& aResult) {
if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
return true;
}
if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
return true;
}
if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
aResult)) {
return true;
}
return false;
}
// The main windows message processing method. Wraps ProcessMessageInternal so
// we can log aRetValue.
bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
LRESULT* aRetValue) {
// For some events we might change the parameter values, so log
// before and after we process them.
NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
eventLogger.SetResult(*aRetValue, result);
return result;
}
// The main windows message processing method. Called by ProcessMessage.
bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
LRESULT* aRetValue) {
MSGResult msgResult(aRetValue);
if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
return (msgResult.mConsumed || !mWnd);
}
bool result = false; // call the default nsWindow proc
*aRetValue = 0;
// The DWM resize hack (see bug 1763981) causes us to process a number of
// messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
// would ordinarily result in a whole lot of internal state being updated.
//
// Since we're supposed to end in the same state we started in (and since the
// content shouldn't know about any of this nonsense), just discard any
// messages synchronously dispatched from within the hack.
if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
return true;
}
// Glass hit testing w/custom transparent margins.
//
// FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
// now.
LRESULT dwmHitResult;
if (mCustomNonClient &&
DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
*aRetValue = dwmHitResult;
return true;
}
// The preference whether to use a different keyboard layout for each
// window is cached, and updating it will not take effect until the
// next restart. We read the preference here and not upon WM_ACTIVATE to make
// sure that this behavior is consistent. Otherwise, if the user changed the
// preference before having ever lowered the window, the preference would take
// effect immediately.
static const bool sSwitchKeyboardLayout =
Preferences::GetBool("intl.keyboard.per_window_layout", false);
AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
// (Large blocks of code should be broken out into OnEvent handlers.)
switch (msg) {
// WM_QUERYENDSESSION must be handled by all windows.
// Otherwise Windows thinks the window can just be killed at will.
case WM_QUERYENDSESSION: {
// Ask around if it's ok to quit.
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
cancelQuitWrapper->SetData(false);
const char16_t* quitType = GetQuitType();
obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
quitType);
bool shouldCancelQuit;
cancelQuitWrapper->GetData(&shouldCancelQuit);
*aRetValue = !shouldCancelQuit;
result = true;
} break;
case MOZ_WM_STARTA11Y:
#if defined(ACCESSIBILITY)
(void)GetAccessible();
result = true;
#else
result = false;
#endif
break;
case WM_ENDSESSION: {
// For WM_ENDSESSION, wParam indicates whether we need to shutdown
// (TRUE) or not (FALSE).
if (!wParam) {
result = true;
break;
}
// According to WM_ENDSESSION lParam documentation:
// 0 -> OS shutdown or restart (no way to distinguish)
// ENDSESSION_LOGOFF -> User is logging off
// ENDSESSION_CLOSEAPP -> Application must shutdown
// ENDSESSION_CRITICAL -> Application is forced to shutdown
// The difference of the last two is not very clear.
if (lParam == 0) {
shutdownReason = AppShutdownReason::OSShutdown;
} else if (lParam & ENDSESSION_LOGOFF) {
shutdownReason = AppShutdownReason::OSSessionEnd;
} else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
shutdownReason = AppShutdownReason::OSForceClose;
} else {
MOZ_DIAGNOSTIC_CRASH("Received WM_ENDSESSION with unknown flags.");
shutdownReason = AppShutdownReason::OSForceClose;
}
// Let's fake a shutdown sequence without actually closing windows etc.
// to avoid Windows killing us in the middle. A proper shutdown would
// require having a chance to pump some messages. Unfortunately
// Windows won't let us do that. Bug 212316.
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
const char16_t* syncShutdown = u"syncShutdown";
const char16_t* quitType = GetQuitType();
AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
obsServ->NotifyObservers(nullptr, "quit-application-granted",
syncShutdown);
obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
AppShutdown::OnShutdownConfirmed();
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
quitType);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
nullptr);
AppShutdown::DoImmediateExit();
MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
} break;
case WM_THEMECHANGED: {
// Update non-client margin offsets
UpdateNonClientMargins();
// Invalidate the window so that the repaint will pick up the new theme.
Invalidate(true, true, true);
} break;
case WM_WTSSESSION_CHANGE: {
switch (wParam) {
case WTS_CONSOLE_CONNECT:
case WTS_REMOTE_CONNECT:
case WTS_SESSION_UNLOCK:
// When a session becomes visible, we should invalidate.
Invalidate(true, true, true);
break;
default:
break;
}
} break;
case WM_NCCALCSIZE: {
// NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
// will need to be kept in sync.
if (mCustomNonClient) {
// If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
// the proposed window rectangle for our window. During our
// processing of the `WM_NCCALCSIZE` message, we are expected to
// modify the `RECT` that `lParam` points to, so that its value upon
// our return is the new client area. We must return 0 if `wParam`
// is `FALSE`.
//
// If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
// struct. This struct contains an array of 3 `RECT`s, the first of
// which has the exact same meaning as the `RECT` that is pointed to
// by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
// conjunction with our return value, can
// be used to specify portions of the source and destination window
// rectangles that are valid and should be preserved. We opt not to
// implement an elaborate client-area preservation technique, and
// simply return 0, which means "preserve the entire old client area
// and align it with the upper-left corner of our new client area".
RECT* clientRect =
wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
: (reinterpret_cast<RECT*>(lParam));
auto margin = NonClientSizeMargin();
clientRect->top += margin.top;
clientRect->left += margin.left;
clientRect->right -= margin.right;
clientRect->bottom -= margin.bottom;
// Make client rect's width and height more than 0 to
// avoid problems of webrender and angle.
clientRect->right = std::max(clientRect->right, clientRect->left + 1);
clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
result = true;
*aRetValue = 0;
}
break;
}
case WM_GETTITLEBARINFOEX: {
if (!mCustomNonClient) {
break;
}
auto* info = reinterpret_cast<TITLEBARINFOEX*>(lParam);
const LayoutDeviceIntPoint origin = WidgetToScreenOffset();
auto GeckoClientToWinScreenRect =
[&origin](LayoutDeviceIntRect aRect) -> RECT {
aRect.MoveBy(origin);
return WinUtils::ToWinRect(aRect);
};
auto SetButton = [&](size_t aIndex, WindowButtonType aType) {
info->rgrect[aIndex] =
GeckoClientToWinScreenRect(mWindowBtnRect[aType]);
DWORD& state = info->rgstate[aIndex];
if (mWindowBtnRect[aType].IsEmpty()) {
state = STATE_SYSTEM_INVISIBLE;
} else {
state = STATE_SYSTEM_FOCUSABLE;
}
};
info->rgrect[0] = info->rcTitleBar =
GeckoClientToWinScreenRect(mDraggableRegion.GetBounds());
info->rgstate[0] = 0;
SetButton(2, WindowButtonType::Minimize);
SetButton(3, WindowButtonType::Maximize);
SetButton(5, WindowButtonType::Close);
// We don't have a help button.
info->rgstate[4] = STATE_SYSTEM_INVISIBLE;
info->rgrect[4] = {0, 0, 0, 0};
result = true;
} break;
case WM_NCHITTEST: {
if (mInputRegion.mFullyTransparent) {
// Treat this window as transparent.
*aRetValue = HTTRANSPARENT;
result = true;
break;
}
if (mInputRegion.mMargin) {
const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
LayoutDeviceIntRect screenRect = GetScreenBounds();
screenRect.Deflate(mInputRegion.mMargin);
if (!screenRect.Contains(screenPoint)) {
*aRetValue = HTTRANSPARENT;
result = true;
break;
}
}
/*
* If an nc client area margin has been moved, we are responsible
* for calculating where the resize margins are and returning the
* appropriate set of hit test constants. DwmDefWindowProc (above)
* will handle hit testing on it's command buttons if we are on a
* composited desktop.
*/
if (!mCustomNonClient) {
break;
}
*aRetValue =
ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
result = true;
break;
}
case WM_SETTEXT:
/*
* WM_SETTEXT paints the titlebar area. Avoid this if we have a
* custom titlebar we paint ourselves, or if we're the ones
* sending the message with an updated title
*/
if (mSendingSetText || !mCustomNonClient) {
break;
}
{
// From msdn, the way around this is to disable the visible state
// temporarily. We need the text to be set but we don't want the
// redraw to occur. However, we need to make sure that we don't
// do this at the same time that a Present is happening.
//
// To do this we take mPresentLock in nsWindow::PreRender and
// if that lock is taken we wait before doing WM_SETTEXT
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->EnterPresentLock();
}
DWORD style = GetWindowLong(mWnd, GWL_STYLE);
SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
*aRetValue =
CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
SetWindowLong(mWnd, GWL_STYLE, style);
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->LeavePresentLock();
}
return true;
}
case WM_NCACTIVATE: {
if (!mCustomNonClient) {
break;
}
// There is a case that rendered result is not kept. Bug 1237617
if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
NS_DispatchToMainThread(NewRunnableMethod(
"nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
}
// ::DefWindowProc would paint nc areas. Avoid this, since we just want
// dwm to take care of re-displaying the glass effect if any. Quoting the
// docs[1]:
//
// If this parameter is set to -1, DefWindowProc does not repaint the
// nonclient area to reflect the state change.
//
// [1]:
// https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
lParam = -1;
} break;
case WM_NCPAINT: {
// ClearType changes often don't send a WM_SETTINGCHANGE message. But
// they do seem to always send a WM_NCPAINT message, so let's update on
// that.
gfxDWriteFont::UpdateSystemTextVars();
} break;
case WM_POWERBROADCAST:
switch (wParam) {
case PBT_APMSUSPEND:
PostSleepWakeNotification(true);
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMECRITICAL:
case PBT_APMRESUMESUSPEND:
PostSleepWakeNotification(false);
break;
}
break;
case WM_CLOSE: // close request
if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
result = true; // abort window closure
break;
case WM_DESTROY:
// clean up.
DestroyLayerManager();
OnDestroy();
result = true;
break;
case WM_PAINT:
*aRetValue = (int)OnPaint();
result = true;
break;
case WM_HOTKEY:
result = OnHotKey(wParam, lParam);
break;
case WM_SYSCHAR:
case WM_CHAR: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = ProcessCharMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
case WM_SYSKEYUP:
case WM_KEYUP: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
nativeMsg.time = ::GetMessageTime();
result = ProcessKeyUpMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
case WM_SYSKEYDOWN:
case WM_KEYDOWN: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = ProcessKeyDownMessage(nativeMsg, nullptr);
DispatchPendingEvents();
} break;
// Say we've dealt with erasing the background. (This is actually handled in
// WM_PAINT or at window-creation time, as necessary.)
case WM_ERASEBKGND: {
*aRetValue = 1;
result = true;
} break;
case WM_MOUSEMOVE: {
LPARAM lParamScreen = lParamToScreen(lParam);
mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
GET_Y_LPARAM(lParamScreen));
if (!mMousePresent && !sIsInMouseCapture) {
// First MOUSEMOVE over the client area. Ask for MOUSELEAVE
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
}
mMousePresent = true;
// Suppress dispatch of pending events
// when mouse moves are generated by widget
// creation instead of user input.
POINT mp;
mp.x = GET_X_LPARAM(lParamScreen);
mp.y = GET_Y_LPARAM(lParamScreen);
const uint16_t inputSource = MOUSE_INPUT_SOURCE();
WinPointerInfo* const pointerInfo =
mPointerEvents.GetCachedPointerInfo(msg, wParam);
if (!LastMouseMoveData::ShouldIgnoreMouseMoveOf(
mp, inputSource, pointerInfo ? pointerInfo->pointerId : 0)) {
result =
DispatchMouseEvent(eMouseMove, wParam, lParam, false,
MouseButton::ePrimary, inputSource, pointerInfo);
DispatchPendingEvents();
}
} break;
case WM_NCMOUSEMOVE: {
LPARAM lParamClient = lParamToClient(lParam);
if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
if (!sIsInMouseCapture) {
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
}
// If we noticed the mouse moving in our draggable region, forward the
// message as a normal WM_MOUSEMOVE.
SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
} else {
// We've transitioned from a draggable area to somewhere else within
// the non-client area - perhaps one of the edges of the window for
// resizing.
mSimulatedClientArea = false;
}
if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
}
} break;
case WM_LBUTTONDOWN: {
result =
DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
} break;
case WM_LBUTTONUP: {
result =
DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
} break;
case WM_NCMOUSELEAVE: {
mSimulatedClientArea = false;
if (EventIsInsideWindow(this)) {
// If we're handling WM_NCMOUSELEAVE and the mouse is still over the
// window, then by process of elimination, the mouse has moved from the
// non-client to client area, so no need to fall-through to the
// WM_MOUSELEAVE handler. We also need to re-register for the
// WM_MOUSELEAVE message, since according to the documentation at [1],
// all tracking requested via TrackMouseEvent is cleared once
// WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
// [1]:
// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
TRACKMOUSEEVENT mTrack;
mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
mTrack.dwFlags = TME_LEAVE;
mTrack.dwHoverTime = 0;
mTrack.hwndTrack = mWnd;
TrackMouseEvent(&mTrack);
break;
}
// We've transitioned from non-client to outside of the window, so
// fall-through to the WM_MOUSELEAVE handler.
[[fallthrough]];
}
case WM_MOUSELEAVE: {
if (!mMousePresent) break;
if (mSimulatedClientArea) break;
mMousePresent = false;
// Check if the mouse is over the fullscreen transition window, if so
// clear LastMouseMoveData. This way the WM_MOUSEMOVE we get after the
// transition window disappears will not be ignored, even if the mouse
// hasn't moved.
if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
LastMouseMoveData::Clear();
}
// We need to check mouse button states and put them in for
// wParam.
WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
(GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
(GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
// Synthesize an event position because we don't get one from
// WM_MOUSELEAVE.
LPARAM pos = lParamToClient(::GetMessagePos());
DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
} break;
case WM_CONTEXTMENU: {
// If the context menu is brought up by a touch long-press, then
// the APZ code is responsible for dealing with this, so we don't
// need to do anything.
if (mTouchWindow &&
MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
result = true;
break;
}
// If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
// event in overscroll gutter, we shouldn't open context menu.
if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
mNeedsToPreventContextMenu) {
result = true;
break;
}
// if the context menu is brought up from the keyboard, |lParam|
// will be -1.
LPARAM pos;
bool contextMenukey = false;
if (lParam == -1) {
contextMenukey = true;
pos = lParamToClient(GetMessagePos());
} else {
pos = lParamToClient(lParam);
}
uint16_t inputSource = MOUSE_INPUT_SOURCE();
int16_t button =
(contextMenukey ||
inputSource == dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH)
? MouseButton::ePrimary
: MouseButton::eSecondary;
result = DispatchMouseEvent(eContextMenu, wParam, pos, contextMenukey,
button, inputSource);
if (lParam != -1 && !result && mCustomNonClient &&
mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
// Blank area hit, throw up the system menu.
DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
result = true;
}
} break;
case WM_POINTERLEAVE:
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERUPDATE:
result = OnPointerEvents(msg, wParam, lParam);
if (result) {
DispatchPendingEvents();
}
break;
case DM_POINTERHITTEST:
if (mDmOwner) {
UINT contactId = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE pointerType;
if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
pointerType == PT_TOUCHPAD) {
mDmOwner->SetContact(contactId);
}
}
break;
case WM_LBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONDOWN:
result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONUP:
result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_MBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONDOWN:
result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONUP:
result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCMBUTTONDBLCLK:
result =
DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_RBUTTONDOWN:
result =
DispatchMouseEvent(eMouseDown, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
break;
case WM_RBUTTONUP:
result =
DispatchMouseEvent(eMouseUp, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
DispatchPendingEvents();
break;
case WM_RBUTTONDBLCLK:
result =
DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONDOWN:
result =
DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONUP:
result =
DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCRBUTTONDBLCLK:
result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
false, MouseButton::eSecondary,
MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
// Windows doesn't provide to customize the behavior of 4th nor 5th button
// of mouse. If 5-button mouse works with standard mouse deriver of
// Windows, users cannot disable 4th button (browser back) nor 5th button
// (browser forward). We should allow to do it with our prefs since we can
// prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
// messages are not sent to DefWindowProc.
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
*aRetValue = TRUE;
switch (GET_XBUTTON_WPARAM(wParam)) {
case XBUTTON1:
result = !Preferences::GetBool("mousebutton.4th.enabled", true);
break;
case XBUTTON2:
result = !Preferences::GetBool("mousebutton.5th.enabled", true);
break;
default:
break;
}
break;
case WM_SIZING: {
if (mAspectRatio > 0) {
LPRECT rect = (LPRECT)lParam;
int32_t newWidth, newHeight;
// The following conditions and switch statement borrow heavily from the
// Chromium source code from
// https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
newWidth = rect->right - rect->left;
newHeight = newWidth / mAspectRatio;
if (newHeight < mSizeConstraints.mMinSize.height) {
newHeight = mSizeConstraints.mMinSize.height;
newWidth = newHeight * mAspectRatio;
} else if (newHeight > mSizeConstraints.mMaxSize.height) {
newHeight = mSizeConstraints.mMaxSize.height;
newWidth = newHeight * mAspectRatio;
}
} else {
newHeight = rect->bottom - rect->top;
newWidth = newHeight * mAspectRatio;
if (newWidth < mSizeConstraints.mMinSize.width) {
newWidth = mSizeConstraints.mMinSize.width;
newHeight = newWidth / mAspectRatio;
} else if (newWidth > mSizeConstraints.mMaxSize.width) {
newWidth = mSizeConstraints.mMaxSize.width;
newHeight = newWidth / mAspectRatio;
}
}
switch (wParam) {
case WMSZ_RIGHT:
case WMSZ_BOTTOM:
rect->right = newWidth + rect->left;
rect->bottom = rect->top + newHeight;
break;
case WMSZ_TOP:
rect->right = newWidth + rect->left;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_LEFT:
case WMSZ_TOPLEFT:
rect->left = rect->right - newWidth;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_TOPRIGHT:
rect->right = rect->left + newWidth;
rect->top = rect->bottom - newHeight;
break;
case WMSZ_BOTTOMLEFT:
rect->left = rect->right - newWidth;
rect->bottom = rect->top + newHeight;
break;
case WMSZ_BOTTOMRIGHT:
rect->right = rect->left + newWidth;
rect->bottom = rect->top + newHeight;
break;
}
}
// When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
// resize or move event. Instead we wait for first VM_SIZING message
// within a ENTERSIZEMOVE to consider this a live resize event.
if (mResizeState == IN_SIZEMOVE) {
mResizeState = RESIZING;
NotifyLiveResizeStarted();
}
break;
}
case WM_MOVING:
FinishLiveResizing(MOVING);
// Sometimes, we appear to miss a WM_DPICHANGED message while moving
// a window around. Therefore, call ChangedDPI and ResetLayout here
// if it appears that the window's scaling is not what we expect.
// This causes the prescontext and appshell window management code to
// check the appUnitsPerDevPixel value and current widget size, and
// refresh them if necessary. If nothing has changed, these calls will
// return without actually triggering any extra reflow or painting.
if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
ChangedDPI();
ResetLayout();
}
break;
case WM_ENTERSIZEMOVE: {
if (mResizeState == NOT_RESIZING) {
mResizeState = IN_SIZEMOVE;
}
break;
}
case WM_EXITSIZEMOVE: {
FinishLiveResizing(NOT_RESIZING);
if (!sIsInMouseCapture) {
NotifySizeMoveDone();
}
// Windows spins a separate hidden event loop when moving a window so we
// don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
// when the hidden event loop exits. We set mDraggingWindowWithMouse to
// true in WM_NCLBUTTONDOWN when we started moving the window with the
// mouse so we know that if mDraggingWindowWithMouse is true, we can send
// a mouse up event.
if (mDraggingWindowWithMouse) {
mDraggingWindowWithMouse = false;
result = DispatchMouseEvent(
eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
MOUSE_INPUT_SOURCE(),
mPointerEvents.GetCachedPointerInfo(msg, wParam));
}
break;
}
case WM_NCLBUTTONDBLCLK:
DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
DispatchPendingEvents();
break;
case WM_NCLBUTTONDOWN: {
// TODO(rkraesig): do we really need this? It should be the same as
// wParam, and when it's not we probably don't want it here.
auto const hitTest =
ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
// Dispatch a custom event when this happens in the draggable region, so
// that non-popup-based panels can react to it. This doesn't send an
// actual mousedown event because that would break dragging or interfere
// with other mousedown handling in the caption area.
if (hitTest == HTCAPTION) {
DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
mDraggingWindowWithMouse = true;
}
if (IsWindowButton(int32_t(wParam)) && mCustomNonClient) {
DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
lParamToClient(lParam), false, MouseButton::ePrimary,
MOUSE_INPUT_SOURCE(), nullptr, IsNonclient::Yes);
DispatchPendingEvents();
result = true;
}
break;
}
case WM_NCLBUTTONUP: {
if (mCustomNonClient) {
result = DispatchMouseEvent(eMouseUp, wParamFromGlobalMouseState(),
lParamToClient(lParam), false,
MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
nullptr, IsNonclient::Yes);
DispatchPendingEvents();
} else {
result = false;
}
break;
}
case WM_APPCOMMAND: {
MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
result = HandleAppCommandMsg(nativeMsg, aRetValue);
break;
}
// The WM_ACTIVATE event is fired when a window is raised or lowered,
// and the loword of wParam specifies which. But we don't want to tell
// the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
// events are fired. Instead, set either the sJustGotActivate or
// gJustGotDeactivate flags and activate/deactivate once the focus
// events arrive.
case WM_ACTIVATE: {
int32_t fActive = LOWORD(wParam);
if (!mWidgetListener) {
break;
}
if (WA_INACTIVE == fActive) {
// when minimizing a window, the deactivation and focus events will
// be fired in the reverse order. Instead, just deactivate right away.
// This can also happen when a modal system dialog is opened, so check
// if the last window to receive the WM_KILLFOCUS message was this one
// or a child of this one.
if (HIWORD(wParam) ||
(mLastKillFocusWindow &&
(GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
DispatchFocusToTopLevelWindow(false);
} else {
sJustGotDeactivate = true;
}
if (IsTopLevelWidget()) {
mLastKeyboardLayout = KeyboardLayout::GetLayout();
}
} else {
StopFlashing();
sJustGotActivate = true;
WidgetMouseEvent event(true, eMouseActivate, this,
WidgetMouseEvent::eReal);
InitEvent(event);
ModifierKeyState modifierKeyState;
modifierKeyState.InitInputEvent(event);
DispatchInputEvent(&event);
if (sSwitchKeyboardLayout && mLastKeyboardLayout) {
ActivateKeyboardLayout(mLastKeyboardLayout, 0);
}
#ifdef ACCESSIBILITY
a11y::LazyInstantiator::ResetUiaDetectionCache();
#endif
}
} break;
case WM_ACTIVATEAPP: {
// Bug 1851991: Sometimes this can be called before gfxPlatform::Init
// when a window is created very early. In that case we just forego
// setting this and accept the GPU process might briefly run at a lower
// priority.
if (GPUProcessManager::Get()) {
GPUProcessManager::Get()->SetAppInForeground(wParam);
}
} break;
case WM_MOUSEACTIVATE:
// A popup with a parent owner should not be activated when clicked but
// should still allow the mouse event to be fired, so the return value
// is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
// just use default processing so that the window is activated.
if (IsPopup() && IsOwnerForegroundWindow()) {
*aRetValue = MA_NOACTIVATE;
result = true;
}
break;
case WM_WINDOWPOSCHANGING: {
LPWINDOWPOS info = (LPWINDOWPOS)lParam;
OnWindowPosChanging(info);
result = true;
} break;
// Workaround for race condition in explorer.exe.
case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
result = true;
} break;
case WM_GETMINMAXINFO: {
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
// Set the constraints. The minimum size should also be constrained to the
// default window maximum size so that it fits on screen.
mmi->ptMinTrackSize.x =
std::min((int32_t)mmi->ptMaxTrackSize.x,
std::max((int32_t)mmi->ptMinTrackSize.x,
mSizeConstraints.mMinSize.width));
mmi->ptMinTrackSize.y =
std::min((int32_t)mmi->ptMaxTrackSize.y,
std::max((int32_t)mmi->ptMinTrackSize.y,
mSizeConstraints.mMinSize.height));
mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
mSizeConstraints.mMaxSize.width);
mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
mSizeConstraints.mMaxSize.height);
} break;
case WM_SETFOCUS: {
WndProcUrgentInvocation::Marker _marker;
// If previous focused window isn't ours, it must have received the
// redirected message. So, we should forget it.
if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
RedirectedKeyDownMessageManager::Forget();
}
if (sJustGotActivate) {
DispatchFocusToTopLevelWindow(true);
}
TaskbarConcealer::OnFocusAcquired(this);
} break;
case WM_KILLFOCUS:
if (sJustGotDeactivate) {
DispatchFocusToTopLevelWindow(false);
} else {
mLastKillFocusWindow = mWnd;
}
break;
case WM_WINDOWPOSCHANGED: {
WINDOWPOS* wp = (LPWINDOWPOS)lParam;
OnWindowPosChanged(wp);
TaskbarConcealer::OnWindowPosChanged(this);
result = true;
} break;
case WM_INPUTLANGCHANGEREQUEST:
*aRetValue = TRUE;
result = false;
break;
case WM_INPUTLANGCHANGE:
KeyboardLayout::GetInstance()->OnLayoutChange(
reinterpret_cast<HKL>(lParam));
nsBidiKeyboard::OnLayoutChange();
result = false; // always pass to child window
break;
case WM_DESTROYCLIPBOARD: {
nsIClipboard* clipboard;
nsresult rv = CallGetService(kCClipboardCID, &clipboard);
if (NS_SUCCEEDED(rv)) {
clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
NS_RELEASE(clipboard);
}
} break;
#ifdef ACCESSIBILITY
case WM_GETOBJECT: {
*aRetValue = 0;
// Do explicit casting to make it working on 64bit systems (see bug 649236
// for details).
int32_t objId = static_cast<DWORD>(lParam);
if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
RefPtr<IAccessible> root(
a11y::LazyInstantiator::GetRootAccessible(mWnd));
if (root) {
*aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
result = true;
}
} else if (objId == UiaRootObjectId) {
if (RefPtr<IRawElementProviderSimple> root =
a11y::LazyInstantiator::GetRootUia(mWnd)) {
*aRetValue = UiaReturnRawElementProvider(mWnd, wParam, lParam, root);
a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
result = true;
}
}
} break;
#endif
case WM_SYSCOMMAND: {
WPARAM const filteredWParam = (wParam & 0xFFF0);
// SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
// middle of something important, put off responding to it.
if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
::PostMessageW(mWnd, msg, wParam, lParam);
result = true;
break;
}
if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
filteredWParam == SC_RESTORE &&
GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
mFrameState->EnsureFullscreenMode(false);
result = true;
}
if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE) {
const auto sizeMode = mFrameState->GetSizeMode();
// Handle the system menu manually when we're in full screen mode
// so we can set the appropriate options.
if (sizeMode == nsSizeMode_Fullscreen) {
// Historically on fullscreen windows we've used this offset from the
// top left as our context menu position. Note that if the point we
// supply is offscreen, Windows will still try to put our menu in the
// right place.
constexpr LayoutDeviceIntPoint offset(20, 20);
auto pos = GetScreenBounds().TopLeft() + offset;
DisplaySystemMenu(mWnd, sizeMode, mIsRTL, pos.x, pos.y);
result = true;
}
}
} break;
case WM_DPICHANGED: {
LPRECT rect = (LPRECT)lParam;
OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
rect->bottom - rect->top);
break;
}
/* Gesture support events */
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
// According to MS samples, this must be handled to enable
// rotational support in multi-touch drivers.
result = true;
*aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
break;
case WM_TOUCH:
result = OnTouch(wParam, lParam);
if (result) {
*aRetValue = 0;
}
break;
case WM_GESTURE:
result = OnGesture(wParam, lParam);
break;
case WM_GESTURENOTIFY: {
// A GestureNotify event is dispatched to decide which single-finger
// panning direction should be active (including none) and if pan
// feedback should be displayed. Java and plugin windows can make their
// own calls.
GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
nsPointWin touchPoint;
touchPoint = gestureinfo->ptsLocation;
touchPoint.ScreenToClient(mWnd);
WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
gestureNotifyEvent.mRefPoint =
LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
nsEventStatus status;
DispatchEvent(&gestureNotifyEvent, status);
mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
if (!mTouchWindow) {
mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
}
result = false; // should always bubble to DefWindowProc
} break;
case WM_CLEAR: {
WidgetContentCommandEvent command(true, eContentCommandDelete, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_CUT: {
WidgetContentCommandEvent command(true, eContentCommandCut, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_COPY: {
WidgetContentCommandEvent command(true, eContentCommandCopy, this);
DispatchWindowEvent(command);
result = true;
} break;
case WM_PASTE: {
WidgetContentCommandEvent command(true, eContentCommandPaste, this);
DispatchWindowEvent(command);
result = true;
} break;
case EM_UNDO: {
WidgetContentCommandEvent command(true, eContentCommandUndo, this);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_REDO: {
WidgetContentCommandEvent command(true, eContentCommandRedo, this);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_CANPASTE: {
// Support EM_CANPASTE message only when wParam isn't specified or
// is plain text format.
if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
WidgetContentCommandEvent command(true, eContentCommandPaste, this,
true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
}
} break;
case EM_CANUNDO: {
WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case EM_CANREDO: {
WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
DispatchWindowEvent(command);
*aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
result = true;
} break;
case MOZ_WM_SKEWFIX: {
TimeStamp skewStamp;
if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
&skewStamp)) {
TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
skewStamp);
}
} break;
default: {
if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
SetHasTaskbarIconBeenCreated();
}
} break;
}
//*aRetValue = result;
if (mWnd) {
return result;
} else {
// Events which caused mWnd destruction and aren't consumed
// will crash during the Windows default processing.
return true;
}
}
void nsWindow::FinishLiveResizing(ResizeState aNewState) {
if (mResizeState == RESIZING) {
NotifyLiveResizeStopped();
}
mResizeState = aNewState;
ForcePresent();
}
/**************************************************************
*
* SECTION: Event processing helpers
*
* Special processing for certain event types and
* synthesized events.
*
**************************************************************/
LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
const LayoutDeviceIntMargin& aNonClientOffset) const {
return mCustomNonClientMetrics.DefaultMargins() - aNonClientOffset;
}
int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
const nsSizeMode sizeMode = mFrameState->GetSizeMode();
if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
return HTCLIENT;
}
// Calculations are done in screen coords
const LayoutDeviceIntRect winRect = GetScreenBounds();
const LayoutDeviceIntPoint point(aX, aY);
// hit return constants:
// HTBORDER - non-resizable border
// HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
// HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
// HTTOPLEFT, HTTOPRIGHT - resizable corner
// HTCAPTION - general title bar area
// HTCLIENT - area considered the client
// HTCLOSE - hovering over the close button
// HTMAXBUTTON - maximize button
// HTMINBUTTON - minimize button
int32_t testResult = HTCLIENT;
const bool isResizable =
sizeMode != nsSizeMode_Maximized &&
(mBorderStyle &
(BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
// Ensure being accessible to borders of window. Even if contents are in
// this area, the area must behave as border.
nonClientSizeMargin.EnsureAtLeast(
LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
kResizableBorderMinSize, kResizableBorderMinSize));
LayoutDeviceIntRect clientRect = winRect;
clientRect.Deflate(nonClientSizeMargin);
const bool allowContentOverride =
sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
// The border size. If there is no content under mouse cursor, the border
// size should be larger than the values in system settings. Otherwise,
// contents under the mouse cursor should be able to override the behavior.
// E.g., user must expect that Firefox button always opens the popup menu
// even when the user clicks on the above edge of it.
LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
borderSize.EnsureAtLeast(mCustomNonClientMetrics.ResizeMargins());
// If we have a custom resize margin, check for it too.
if (mCustomResizeMargin) {
borderSize.EnsureAtLeast(
LayoutDeviceIntMargin(mCustomResizeMargin, mCustomResizeMargin,
mCustomResizeMargin, mCustomResizeMargin));
}
bool top = false;
bool bottom = false;
bool left = false;
bool right = false;
if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
top = true;
} else if (point.y <= winRect.YMost() &&
point.y > winRect.YMost() - borderSize.bottom) {
bottom = true;
}
// (the 2x case here doubles the resize area for corners)
int multiplier = (top || bottom) ? 2 : 1;
if (point.x >= winRect.x &&
point.x < winRect.x + (multiplier * borderSize.left)) {
left = true;
} else if (point.x <= winRect.XMost() &&
point.x > winRect.XMost() - (multiplier * borderSize.right)) {
right = true;
}
bool inResizeRegion = false;
if (isResizable) {
if (top) {
testResult = HTTOP;
if (left) {
testResult = HTTOPLEFT;
} else if (right) {
testResult = HTTOPRIGHT;
}
} else if (bottom) {
testResult = HTBOTTOM;
if (left) {
testResult = HTBOTTOMLEFT;
} else if (right) {
testResult = HTBOTTOMRIGHT;
}
} else {
if (left) {
testResult = HTLEFT;
}
if (right) {
testResult = HTRIGHT;
}
}
inResizeRegion = (testResult != HTCLIENT);
} else {
if (top) {
testResult = HTCAPTION;
} else if (bottom || left || right) {
testResult = HTBORDER;
}
}
if (sIsInMouseCapture || !allowContentOverride) {
return testResult;
}
{
POINT pt = {aX, aY};
::ScreenToClient(mWnd, &pt);
if (pt.x == mCachedHitTestPoint.x.value &&
pt.y == mCachedHitTestPoint.y.value &&
TimeStamp::Now() - mCachedHitTestTime <
TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
return mCachedHitTestResult;
}
mCachedHitTestPoint = {pt.x, pt.y};
mCachedHitTestTime = TimeStamp::Now();
}
auto pt = mCachedHitTestPoint;
if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
testResult = HTMINBUTTON;
} else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
#ifdef ACCESSIBILITY
a11y::Compatibility::SuppressA11yForSnapLayouts();
#endif
testResult = HTMAXBUTTON;
} else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
testResult = HTCLOSE;
} else if (!inResizeRegion) {
// If we're in the resize region, avoid overriding that with either a
// drag or a client result; resize takes priority over either (but not
// over the window controls, which is why we check this after those).
if (mDraggableRegion.Contains(pt)) {
testResult = HTCAPTION;
} else {
testResult = HTCLIENT;
}
}
mCachedHitTestResult = testResult;
return testResult;
}
bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
return testResult == HTCAPTION || IsWindowButton(testResult);
}
bool nsWindow::IsWindowButton(int32_t hitTestResult) {
return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
hitTestResult == HTCLOSE;
}
TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
CurrentWindowsTimeGetter getCurrentTime(mWnd);
return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
}
void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
// Retain the previous mode that was notified to observers
static bool sWasSleepMode = false;
// Only notify observers if mode changed
if (aIsSleepMode == sWasSleepMode) return;
sWasSleepMode = aIsSleepMode;
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService)
observerService->NotifyObservers(nullptr,
aIsSleepMode
? NS_WIDGET_SLEEP_OBSERVER_TOPIC
: NS_WIDGET_WAKE_OBSERVER_TOPIC,
nullptr);
}
LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
if (IMEHandler::IsComposingOn(this)) {
IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
}
// These must be checked here too as a lone WM_CHAR could be received
// if a child window didn't handle it (for example Alt+Space in a content
// window)
ModifierKeyState modKeyState;
NativeKey nativeKey(this, aMsg, modKeyState);
return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
}
LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
ModifierKeyState modKeyState;
NativeKey nativeKey(this, aMsg, modKeyState);
bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
if (aMsg.wParam == VK_F10) {
// Bug 1382199: Windows default behavior will trigger the System menu bar
// when F10 is released. Among other things, this causes the System menu bar
// to appear when a web page overrides the contextmenu event. We *never*
// want this default behavior, so eat this key (never pass it to Windows).
return true;
}
return result;
}
LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
bool* aEventDispatched) {
// If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
// must clean up the redirected message information itself. For more
// information, see above comment of
// RedirectedKeyDownMessageManager::AutoFlusher class definition in
// KeyboardLayout.h.
RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
ModifierKeyState modKeyState;
NativeKey nativeKey(this, aMsg, modKeyState);
LRESULT result =
static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
// HandleKeyDownMessage cleaned up the redirected message information
// itself, so, we should do nothing.
redirectedMsgFlusher.Cancel();
if (aMsg.wParam == VK_MENU ||
(aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
// We need to let Windows handle this keypress,
// by returning false, if there's a native menu
// bar somewhere in our containing window hierarchy.
// Otherwise we handle the keypress and don't pass
// it on to Windows, by returning true.
bool hasNativeMenu = false;
HWND hWnd = mWnd;
while (hWnd) {
if (::GetMenu(hWnd)) {
hasNativeMenu = true;
break;
}
hWnd = ::GetParent(hWnd);
}
result = !hasNativeMenu;
}
return result;
}
nsresult nsWindow::SynthesizeNativeKeyEvent(
int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
uint32_t aModifierFlags, const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters,
nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
return keyboardLayout->SynthesizeNativeKeyEvent(
this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
aUnmodifiedCharacters);
}
nsresult nsWindow::SynthesizeNativeMouseEvent(
LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
INPUT input;
memset(&input, 0, sizeof(input));
// TODO (bug 1693240):
// Now, we synthesize native mouse events asynchronously since we want to
// synthesize the event on the front window at the point. However, Windows
// does not provide a way to set modifier only while a mouse message is
// being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
// need a trick for handling it.
switch (aNativeMessage) {
case NativeMouseMessage::Move:
input.mi.dwFlags = MOUSEEVENTF_MOVE;
// Reset LastMouseMoveData so that even if we're moving the mouse to the
// position it's already at, we still dispatch a mousemove event, because
// the callers of this function expect that.
LastMouseMoveData::Clear();
break;
case NativeMouseMessage::ButtonDown:
case NativeMouseMessage::ButtonUp: {
const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
switch (aButton) {
case MouseButton::ePrimary:
input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
break;
case MouseButton::eMiddle:
input.mi.dwFlags =
isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
break;
case MouseButton::eSecondary:
input.mi.dwFlags =
isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
break;
case MouseButton::eX1:
input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
input.mi.mouseData = XBUTTON1;
break;
case MouseButton::eX2:
input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
input.mi.mouseData = XBUTTON2;
break;
default:
return NS_ERROR_INVALID_ARG;
}
break;
}
case NativeMouseMessage::EnterWindow:
case NativeMouseMessage::LeaveWindow:
MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
return NS_ERROR_INVALID_ARG;
}
input.type = INPUT_MOUSE;
::SetCursorPos(aPoint.x, aPoint.y);
::SendInput(1, &input, sizeof(INPUT));
return NS_OK;
}
nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
uint32_t aAdditionalFlags, nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
this, aPoint, aNativeMessage,
(aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
? static_cast<int32_t>(aDeltaY)
: static_cast<int32_t>(aDeltaX),
aModifierFlags, aAdditionalFlags);
}
nsresult nsWindow::SynthesizeNativeTouchpadPan(
TouchpadGesturePhase aEventPhase, LayoutDeviceIntPoint aPoint,
double aDeltaX, double aDeltaY, int32_t aModifierFlags,
nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
DirectManipulationOwner::SynthesizeNativeTouchpadPan(
this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
return NS_OK;
}
static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
#ifdef WINSTATE_DEBUG_OUTPUT
if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
} else {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
}
MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
if (wp->flags & SWP_FRAMECHANGED) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
}
if (wp->flags & SWP_SHOWWINDOW) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
}
if (wp->flags & SWP_NOSIZE) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
}
if (wp->flags & SWP_HIDEWINDOW) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
}
if (wp->flags & SWP_NOZORDER) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
}
if (wp->flags & SWP_NOACTIVATE) {
MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
}
MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
#endif
}
/**************************************************************
*
* SECTION: OnXXX message handlers
*
* For message handlers that need to be broken out or
* implemented in specific platform code.
*
**************************************************************/
void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
if (!wp) {
return;
}
MaybeLogPosChanged(mWnd, wp);
// Handle window size mode changes
if (wp->flags & SWP_FRAMECHANGED) {
// Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
// windows when fullscreen games disable desktop composition. If we're
// minimized and not being activated, ignore the event and let windows
// handle it.
if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
(wp->flags & SWP_NOACTIVATE)) {
return;
}
mFrameState->OnFrameChanged();
if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
// Skip window size change events below on minimization.
return;
}
}
// Notify visibility change when window is activated.
if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
}
// Handle window position changes
if (!(wp->flags & SWP_NOMOVE)) {
mBounds.MoveTo(wp->x, wp->y);
NotifyWindowMoved(wp->x, wp->y);
}
// Handle window size changes
if (!(wp->flags & SWP_NOSIZE)) {
RECT r;
int32_t newWidth, newHeight;
::GetWindowRect(mWnd, &r);
newWidth = r.right - r.left;
newHeight = r.bottom - r.top;
if (newWidth > mLastSize.width) {
RECT drect;
// getting wider
drect.left = wp->x + mLastSize.width;
drect.top = wp->y;
drect.right = drect.left + (newWidth - mLastSize.width);
drect.bottom = drect.top + newHeight;
::RedrawWindow(mWnd, &drect, nullptr,
RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
RDW_ERASENOW | RDW_ALLCHILDREN);
}
if (newHeight > mLastSize.height) {
RECT drect;
// getting taller
drect.left = wp->x;
drect.top = wp->y + mLastSize.height;
drect.right = drect.left + newWidth;
drect.bottom = drect.top + (newHeight - mLastSize.height);
::RedrawWindow(mWnd, &drect, nullptr,
RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
RDW_ERASENOW | RDW_ALLCHILDREN);
}
mBounds.SizeTo(newWidth, newHeight);
mLastSize.width = newWidth;
mLastSize.height = newHeight;
#ifdef WINSTATE_DEBUG_OUTPUT
MOZ_LOG(gWindowsLog, LogLevel::Info,
("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
newHeight));
#endif
if (mAspectRatio > 0) {
// It's possible (via Windows Aero Snap) that the size of the window
// has changed such that it violates the aspect ratio constraint. If so,
// queue up an event to enforce the aspect ratio constraint and repaint.
// When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
float newAspectRatio = (float)newWidth / newHeight;
if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
// Hold a reference to self alive and pass it into the lambda to make
// sure this nsIWidget stays alive long enough to run this function.
nsCOMPtr<nsIWidget> self(this);
NS_DispatchToMainThread(NS_NewRunnableFunction(
"EnforceAspectRatio", [self, this, newWidth]() -> void {
if (mWnd) {
Resize(LayoutDeviceSize(newWidth, newWidth / mAspectRatio) /
GetDesktopToDeviceScale(),
true);
}
}));
}
}
// If a maximized window is resized, recalculate the non-client margins.
if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
if (UpdateNonClientMargins(true)) {
// gecko resize event already sent by UpdateNonClientMargins.
return;
}
}
}
// Notify the widget listener for size change of client area for gecko
// events. This needs to be done when either window size is changed,
// or window frame is changed. They may not happen together.
// However, we don't invoke that for popup when window frame changes,
// because popups may trigger frame change before size change via
// {Set,Clear}ThemeRegion they invoke in Resize. That would make the
// code below call OnResize with a wrong client size first, which can
// lead to flickerling for some popups.
if (!(wp->flags & SWP_NOSIZE) ||
((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
RECT r;
LayoutDeviceIntSize clientSize;
if (::GetClientRect(mWnd, &r)) {
clientSize = WinUtils::ToIntRect(r).Size();
} else {
clientSize = mBounds.Size();
}
// Send a gecko resize event
OnResize(clientSize);
}
}
void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
// Update non-client margins if the frame size is changing, and let the
// browser know we are changing size modes, so alternative css can kick in.
// If we're going into fullscreen mode, ignore this, since it'll reset
// margins to normal mode.
if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
mFrameState->OnFrameChanging();
}
// Force fullscreen. This works around a bug in Windows 10 1809 where
// using fullscreen when a window is "snapped" causes a spurious resize
// smaller than the full screen, see bug 1482920.
if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
!(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
nsCOMPtr<nsIScreenManager> screenmgr =
do_GetService(sScreenManagerContractID);
if (screenmgr) {
LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
DesktopIntRect deskBounds =
RoundedToInt(bounds / GetDesktopToDeviceScale());
nsCOMPtr<nsIScreen> screen;
screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
deskBounds.Width(), deskBounds.Height(),
getter_AddRefs(screen));
if (screen) {
auto rect = screen->GetRect();
info->x = rect.x;
info->y = rect.y;
info->cx = rect.width;
info->cy = rect.height;
}
}
}
// When waking from sleep or switching out of tablet mode, Windows 10
// Version 1809 will reopen popup windows that should be hidden. Detect
// this case and refuse to show the window.
static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
mWindowType == WindowType::Popup && mWidgetListener &&
mWidgetListener->ShouldNotBeVisible()) {
info->flags &= ~SWP_SHOWWINDOW;
}
}
void nsWindow::UserActivity() {
// Check if we have the idle service, if not we try to get it.
if (!mIdleService) {
mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
}
// Check that we now have the idle service.
if (mIdleService) {
mIdleService->ResetIdleTimeOut(0);
}
}
// Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
// uint32_t).
static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
std::string deviceName;
UINT dataSize = 0;
// The first call just queries how long the name string will be.
GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
if (!dataSize || dataSize > 0x10000) {
return false;
}
deviceName.resize(dataSize);
// The second call actually populates the string.
UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
&dataSize);
if (result == UINT_MAX) {
return false;
}
// The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
// needs to be escaped with another one.
std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
// For some reason, the dataSize returned by the first call is double the
// actual length of the device name (as if it were returning the size of a
// wide-character string in bytes) even though we are using the narrow
// version of the API. For the comparison against the expected device name
// to pass, we truncate the buffer to be no longer tha the expected device
// name.
if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
return false;
}
RID_DEVICE_INFO deviceInfo;
deviceInfo.cbSize = sizeof(deviceInfo);
dataSize = sizeof(deviceInfo);
result =
GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
if (result == UINT_MAX) {
return false;
}
// The device identifiers that we check for here come from bug 1355162
// comment 1 (see also bug 1511901 comment 35).
return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
deviceInfo.hid.dwProductId == 0 &&
deviceInfo.hid.dwVersionNumber == 1 &&
deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
}
// Determine if the touch device that originated |aOSEvent| needs to have
// touch events representing a two-finger gesture converted to pan
// gesture events.
// We only do this for touch devices with a specific name and identifiers.
static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
uint32_t aTouchCount) {
if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
return false;
}
if (aTouchCount == 0) {
return false;
}
HANDLE source = aOSEvent[0].hSource;
// Cache the result of this computation for each touch device.
// Touch devices are identified by the HANDLE stored in the hSource
// field of TOUCHINPUT.
static std::map<HANDLE, bool> sResultCache;
auto [iter, inserted] = sResultCache.emplace(source, false);
if (inserted) {
iter->second = TouchDeviceNeedsPanGestureConversion(source);
}
return iter->second;
}
Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
// Checks if the touch device that originated the touch event is one
// for which we want to convert the touch events to pang gesture events.
bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
aOSEvent, aTouchInput.mTouches.Length());
if (!shouldConvert) {
return Nothing();
}
// Only two-finger gestures need conversion.
if (aTouchInput.mTouches.Length() != 2) {
return Nothing();
}
PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
eventType = PanGestureInput::PANGESTURE_START;
} else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
eventType = PanGestureInput::PANGESTURE_END;
} else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
eventType = PanGestureInput::PANGESTURE_CANCELLED;
}
// Use the midpoint of the two touches as the start point of the pan gesture.
ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
aTouchInput.mTouches[1].mScreenPoint) /
2;
// To compute the displacement of the pan gesture, we keep track of the
// location of the previous event.
ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
? ScreenPoint(0, 0)
: (focusPoint - mLastPanGestureFocus);
mLastPanGestureFocus = focusPoint;
// We need to negate the displacement because for a touch event, moving the
// fingers down results in scrolling up, but for a touchpad gesture, we want
// moving the fingers down to result in scrolling down.
PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
-displacement, aTouchInput.modifiers);
result.mSimulateMomentum = true;
return Some(result);
}
// Dispatch an event that originated as an OS touch event.
// Usually, we want to dispatch it as a touch event, but some touchpads
// produce touch events for two-finger scrolling, which need to be converted
// to pan gesture events for correct behaviour.
void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
PTOUCHINPUT aOSEvent) {
if (Maybe<PanGestureInput> panInput =
ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
DispatchPanGestureInput(*panInput);
return;
}
DispatchTouchInput(aTouchInput);
}
bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
uint32_t cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
sizeof(TOUCHINPUT))) {
MultiTouchInput touchInput, touchEndInput;
// Walk across the touch point array processing each contact point.
for (uint32_t i = 0; i < cInputs; i++) {
bool addToEvent = false, addToEndEvent = false;
// N.B.: According with MS documentation
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
// TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
// TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
// TOUCHEVENTF_UP can be combined together.
if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
if (touchInput.mTimeStamp.IsNull()) {
// Initialize a touch event to send.
touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
ModifierKeyState modifierKeyState;
touchInput.modifiers = modifierKeyState.GetModifiers();
}
// Pres shell expects this event to be a eTouchStart
// if any new contact points have been added since the last event sent.
if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
touchInput.mType = MultiTouchInput::MULTITOUCH_START;
}
addToEvent = true;
}
if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
// Pres shell expects removed contacts points to be delivered in a
// separate eTouchEnd event containing only the contact points that were
// removed.
if (touchEndInput.mTimeStamp.IsNull()) {
// Initialize a touch event to send.
touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
ModifierKeyState modifierKeyState;
touchEndInput.modifiers = modifierKeyState.GetModifiers();
}
addToEndEvent = true;
}
if (!addToEvent && !addToEndEvent) {
// Filter out spurious Windows events we don't understand, like palm
// contact.
continue;
}
// Setup the touch point we'll append to the touch event array.
nsPointWin touchPoint;
touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
touchPoint.ScreenToClient(mWnd);
// Initialize the touch data.
SingleTouchData touchData(
pInputs[i].dwID, // aIdentifier
ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
// The contact area info cannot be trusted even when
// TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
// which somehow violates the API docs. (bug 1710509) Ultimately the
// dwFlags check will become redundant since we want to migrate to
// WM_POINTER for pens. (bug 1707075)
(pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
!(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
: ScreenSize(1, 1), // aRadius
0.0f, // aRotationAngle
0.0f); // aForce
// Append touch data to the appropriate event.
if (addToEvent) {
touchInput.mTouches.AppendElement(touchData);
}
if (addToEndEvent) {
touchEndInput.mTouches.AppendElement(touchData);
}
}
// Dispatch touch start and touch move event if we have one.
if (!touchInput.mTimeStamp.IsNull()) {
DispatchTouchOrPanGestureInput(touchInput, pInputs);
}
// Dispatch touch end event if we have one.
if (!touchEndInput.mTimeStamp.IsNull()) {
DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
}
}
delete[] pInputs;
CloseTouchInputHandle((HTOUCHINPUT)lParam);
return true;
}
// Gesture event processing. Handles WM_GESTURE events.
bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
// Treatment for pan events which translate into scroll events:
if (mGesture.IsPanEvent(lParam)) {
if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
return false; // ignore
nsEventStatus status;
WidgetWheelEvent wheelEvent(true, eWheel, this);
ModifierKeyState modifierKeyState;
modifierKeyState.InitInputEvent(wheelEvent);
wheelEvent.mButton = 0;
wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
bool endFeedback = true;
if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
DispatchEvent(&wheelEvent, status);
}
if (mDisplayPanFeedback) {
mGesture.UpdatePanFeedbackX(
mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
endFeedback);
mGesture.UpdatePanFeedbackY(
mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
endFeedback);
mGesture.PanFeedbackFinalize(mWnd, endFeedback);
}
CloseGestureInfoHandle((HGESTUREINFO)lParam);
return true;
}
// Other gestures translate into simple gesture events:
WidgetSimpleGestureEvent event(true, eVoidEvent, this);
if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
return false; // fall through to DefWndProc
}
// Polish up and send off the new event
ModifierKeyState modifierKeyState;
modifierKeyState.InitInputEvent(event);
event.mButton = 0;
event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
nsEventStatus status;
DispatchEvent(&event, status);
if (status == nsEventStatus_eIgnore) {
return false; // Ignored, fall through
}
// Only close this if we process and return true.
CloseGestureInfoHandle((HGESTUREINFO)lParam);
return true; // Handled
}
// WM_DESTROY event handler
void nsWindow::OnDestroy() {
mOnDestroyCalled = true;
// If this is a toplevel window, notify the taskbar concealer to clean up any
// relevant state.
if (!mParent) {
TaskbarConcealer::OnWindowDestroyed(mWnd);
}
// Make sure we don't get destroyed in the process of tearing down.
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
// Dispatch the destroy notification.
if (!mInDtor) NotifyWindowDestroyed();
// Prevent the widget from sending additional events.
mWidgetListener = nullptr;
mAttachedWidgetListener = nullptr;
DestroyDirectManipulation();
if (mWnd == mLastKillFocusWindow) {
mLastKillFocusWindow = nullptr;
}
// Unregister notifications from terminal services
::WTSUnRegisterSessionNotification(mWnd);
// We will stop receiving native events after dissociating from our native
// window. We will also disappear from the output of WinUtils::GetNSWindowPtr
// for that window.
DissociateFromNativeWindow();
// Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
// can be cleared. (It's used in tracking windows for mouse events.)
if (sCurrentWindow == this) sCurrentWindow = nullptr;
// Disconnects us from our parent, will call our GetParent().
nsIWidget::Destroy();
// Release references to children, device context, toolkit, and app shell.
nsIWidget::OnDestroy();
// We have to destroy the native drag target before we null out our window
// pointer.
EnableDragDrop(false);
// If we're going away and for some reason we're still the rollup widget,
// rollup and turn off capture.
nsIRollupListener* rollupListener = nsIWidget::GetActiveRollupListener();
nsCOMPtr<nsIWidget> rollupWidget;
if (rollupListener) {
rollupWidget = rollupListener->GetRollupWidget();
}
if (this == rollupWidget) {
rollupListener->Rollup({});
CaptureRollupEvents(false);
}
IMEHandler::OnDestroyWindow(this);
// Destroy any custom cursor resources.
if (mCursor.IsCustom()) {
SetCursor(Cursor{eCursor_standard});
}
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->OnDestroyWindow();
}
mBasicLayersSurface = nullptr;
// Finalize panning feedback to possibly restore window displacement
mGesture.PanFeedbackFinalize(mWnd, true);
// Clear the main HWND.
mWnd = nullptr;
}
// Send a resize message to the listener
bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
if (mCompositorWidgetDelegate &&
!mCompositorWidgetDelegate->OnWindowResize(aSize)) {
return false;
}
bool result = false;
if (mWidgetListener) {
result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
}
// If there is an attached view, inform it as well as the normal widget
// listener.
if (mAttachedWidgetListener) {
return mAttachedWidgetListener->WindowResized(this, aSize.width,
aSize.height);
}
return result;
}
void nsWindow::OnSizeModeChange() {
const nsSizeMode mode = mFrameState->GetSizeMode();
MOZ_LOG(gWindowsLog, LogLevel::Info,
("nsWindow::OnSizeModeChange() sizeMode %d", mode));
if (NeedsToTrackWindowOcclusionState()) {
WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
this, mode != nsSizeMode_Minimized);
}
if (mWidgetListener) {
mWidgetListener->SizeModeChanged(mode);
}
}
bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
bool nsWindow::ShouldUseOffMainThreadCompositing() {
if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
return false;
}
return nsIWidget::ShouldUseOffMainThreadCompositing();
}
void nsWindow::WindowUsesOMTC() {
ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
if (!style) {
NS_WARNING("Could not get window class style");
return;
}
style |= CS_HREDRAW | CS_VREDRAW;
DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
NS_WARNING_ASSERTION(result, "Could not reset window class style");
}
void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
int32_t height) {
// Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
// they remain tied to their original parent's resolution.
if (mWindowType == WindowType::Popup) {
return;
}
if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
return;
}
mDefaultScale = -1.0; // force recomputation of scale factor
if (mResizeState != RESIZING &&
mFrameState->GetSizeMode() == nsSizeMode_Normal) {
if (nsCOMPtr<nsIScreenManager> sm =
do_GetService(sScreenManagerContractID)) {
// Before getting the screen which will contain this window, we need to
// refresh the screens because WM_DPICHANGED is sent before
// WM_DISPLAYCHANGE.
ScreenHelperWin::RefreshScreens();
// Limit the position (if not in the middle of a drag-move) & size,
// if it would overflow the destination screen
nsCOMPtr<nsIScreen> screen;
sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
if (screen) {
int32_t availLeft, availTop, availWidth, availHeight;
screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
if (mResizeState != MOVING) {
x = std::max(x, availLeft);
y = std::max(y, availTop);
}
width = std::min(width, availWidth);
height = std::min(height, availHeight);
}
}
Resize(LayoutDeviceIntRect(x, y, width, height) / GetDesktopToDeviceScale(),
true);
}
UpdateNonClientMargins();
ChangedDPI();
ResetLayout();
}
// Callback to generate OnCloakChanged pseudo-events.
/* static */
void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
MOZ_ASSERT(NS_IsMainThread());
const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
if (!pWin) {
MOZ_LOG(
sCloakingLog, LogLevel::Debug,
("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
return;
}
const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
MOZ_LOG(sCloakingLog, LogLevel::Debug,
("Received redundant %s event for %s HWND %p; discarding",
kEventName, kWasCloakedStr, aWnd));
return;
}
MOZ_LOG(
sCloakingLog, LogLevel::Info,
("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
// Cloaking events like the one we've just received are sent asynchronously.
// Rather than process them one-by-one, we jump the gun a bit and perform
// updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
// batch operations that consider more than one window's state.
struct Item {
nsWindow* win;
bool nowCloaked;
};
nsTArray<Item> changedWindows;
mozilla::EnumerateThreadWindows([&](HWND hwnd) {
nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
if (!pWin) {
return;
}
const bool isCloaked = mozilla::IsCloaked(hwnd);
if (isCloaked != pWin->mIsCloaked) {
changedWindows.AppendElement(Item{pWin, isCloaked});
}
});
if (changedWindows.IsEmpty()) {
return;
}
for (const Item& item : changedWindows) {
item.win->OnCloakChanged(item.nowCloaked);
}
nsWindow::TaskbarConcealer::OnCloakChanged();
}
void nsWindow::OnCloakChanged(bool aCloaked) {
MOZ_LOG(sCloakingLog, LogLevel::Info,
("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
aCloaked ? "true" : "false"));
mIsCloaked = aCloaked;
}
/**************************************************************
**************************************************************
**
** BLOCK: IME management and accessibility
**
** Handles managing IME input and accessibility.
**
**************************************************************
**************************************************************/
void nsWindow::SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) {
InputContext newInputContext = aContext;
IMEHandler::SetInputContext(this, newInputContext, aAction);
mInputContext = newInputContext;
}
InputContext nsWindow::GetInputContext() {
mInputContext.mIMEState.mOpen = IMEState::CLOSED;
if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
mInputContext.mIMEState.mOpen = IMEState::OPEN;
} else {
mInputContext.mIMEState.mOpen = IMEState::CLOSED;
}
return mInputContext;
}
TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
return IMEHandler::GetNativeTextEventDispatcherListener();
}
#ifdef ACCESSIBILITY
# ifdef DEBUG
# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
printf( \
"Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
"%p,\n", \
aHwnd, ::GetParent(aHwnd), aWnd); \
printf(" acc: %p", aAcc); \
if (aAcc) { \
nsAutoString name; \
aAcc->Name(name); \
printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
} \
printf("\n }\n"); \
}
# else
# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
# endif
a11y::LocalAccessible* nsWindow::GetAccessible() {
// If the pref was ePlatformIsDisabled, return null here, disabling a11y.
if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
return nullptr;
if (mInDtor || mOnDestroyCalled) {
return nullptr;
}
// In case of popup window return a popup accessible.
if (auto* frame = GetPopupFrame()) {
if (nsAccessibilityService* accService = GetOrCreateAccService()) {
a11y::DocAccessible* docAcc =
accService->GetDocAccessible(frame->PresShell());
if (docAcc) {
NS_LOG_WMGETOBJECT(
this, mWnd, docAcc->GetAccessibleOrDescendant(frame->GetContent()));
return docAcc->GetAccessibleOrDescendant(frame->GetContent());
}
}
}
// otherwise root document accessible.
NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
return GetRootAccessible();
}
#endif
void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
if (aMode == mTransparencyMode || DestroyCalled()) {
return;
}
MOZ_ASSERT(WinUtils::GetTopLevelHWND(mWnd, true) == mWnd);
MOZ_ASSERT(GetTopLevelWindow(true) == this);
mTransparencyMode = aMode;
UpdateOpaqueRegionInternal();
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->UpdateTransparency(aMode);
}
}
void nsWindow::UpdateOpaqueRegion(const LayoutDeviceIntRegion& aRegion) {
if (aRegion == mOpaqueRegion || IsPopup()) {
// Popups don't track opaque region changes since our opaque region
// tracking is, let's say, suboptimal (see bug 1933952).
return;
}
mOpaqueRegion = aRegion;
UpdateOpaqueRegionInternal();
}
LayoutDeviceIntRegion nsWindow::GetTranslucentRegion() {
if (mTransparencyMode != TransparencyMode::Transparent) {
return {};
}
const auto clientRect =
LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetClientSize());
LayoutDeviceIntRegion translucentRegion{clientRect};
translucentRegion.SubOut(mOpaqueRegion);
return translucentRegion;
}
void nsWindow::MaybeInvalidateTranslucentRegion() {
if (mTransparencyMode != TransparencyMode::Transparent) {
return;
}
const auto translucent = GetTranslucentRegion();
if (translucent.IsEmpty() || mClearedRegion.Contains(translucent)) {
return;
}
// We need to clear some part of the window that isn't cleared already, make
// sure we trigger a WM_PAINT message.
//
// NOTE(emilio): we could provide a finer grained region here (i.e., only
// invalidate the translucent region, or even only the bits that are not yet
// cleared), but we don't do much with that region in OnPaint message so this
// seems fine for now.
::RedrawWindow(mWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_INTERNALPAINT);
}
void nsWindow::UpdateOpaqueRegionInternal() {
MARGINS margins{0};
if (mTransparencyMode == TransparencyMode::Transparent) {
// If there is no opaque region, set margins to support a full sheet of
// glass. Comments in MSDN indicate all values must be set to -1 to get a
// full sheet of glass.
margins = {-1, -1, -1, -1};
if (!mOpaqueRegion.IsEmpty()) {
LayoutDeviceIntRect clientBounds = GetClientBounds();
// Find the largest rectangle and use that to calculate the inset.
LayoutDeviceIntRect largest = mOpaqueRegion.GetLargestRectangle();
margins.cxLeftWidth = largest.X();
margins.cxRightWidth = clientBounds.Width() - largest.XMost();
margins.cyBottomHeight = clientBounds.Height() - largest.YMost();
margins.cyTopHeight = largest.Y();
auto ncmargin = NonClientSizeMargin();
margins.cxLeftWidth += ncmargin.left;
margins.cyTopHeight += ncmargin.top;
margins.cxRightWidth += ncmargin.right;
margins.cyBottomHeight += ncmargin.bottom;
}
}
DwmExtendFrameIntoClientArea(mWnd, &margins);
MaybeInvalidateTranslucentRegion();
}
/**************************************************************
**************************************************************
**
** BLOCK: Popup rollup hooks
**
** Deals with CaptureRollup on popup windows.
**
**************************************************************
**************************************************************/
// Schedules a timer for a window, so we can rollup after processing the hook
// event
void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
// In some cases multiple hooks may be scheduled
// so ignore any other requests once one timer is scheduled
if (sHookTimerId == 0) {
// Remember the window handle and the message ID to be used later
sRollupMsgId = aMsgId;
sRollupMsgWnd = aWnd;
// Schedule native timer for doing the rollup after
// this event is done being processed
sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
}
}
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
int gLastMsgCode = 0;
extern MSGFEventMsgInfo gMSGFEvents[];
#endif
// Process Menu messages, rollup when popup is clicked.
LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
LPARAM lParam) {
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (sProcessHook) {
MSG* pMsg = (MSG*)lParam;
int inx = 0;
while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
inx++;
}
if (code != gLastMsgCode) {
if (gMSGFEvents[inx].mId == code) {
# ifdef DEBUG
MOZ_LOG(gWindowsLog, LogLevel::Info,
("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
gMSGFEvents[inx].mStr, pMsg->hwnd));
# endif
} else {
# ifdef DEBUG
MOZ_LOG(gWindowsLog, LogLevel::Info,
("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
gMSGFEvents[inx].mId, pMsg->hwnd));
# endif
}
gLastMsgCode = code;
}
PrintEvent(pMsg->message, FALSE, FALSE);
}
#endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (sProcessHook && code == MSGF_MENU) {
MSG* pMsg = (MSG*)lParam;
ScheduleHookTimer(pMsg->hwnd, pMsg->message);
}
return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
}
// Process all mouse messages. Roll up when a click is in a native window
// that doesn't have an nsIWidget.
LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
LPARAM lParam) {
if (sProcessHook) {
switch (wParam) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL: {
MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
if (!mozWin) {
ScheduleHookTimer(ms->hwnd, (UINT)wParam);
}
break;
}
}
}
return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
}
// Process all messages. Roll up when the window is moving, or
// is resizing or when maximized or mininized.
LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
LPARAM lParam) {
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (sProcessHook) {
CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
PrintEvent(cwpt->message, FALSE, FALSE);
}
#endif
if (sProcessHook) {
CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
cwpt->message == WM_GETMINMAXINFO) {
ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
}
}
return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
}
// Register the special "hooks" for dropdown processing.
void nsWindow::RegisterSpecialDropdownHooks() {
NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
// Install msg hook for moving the window and resizing
if (!sMsgFilterHook) {
DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
nullptr, GetCurrentThreadId());
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (!sMsgFilterHook) {
MOZ_LOG(gWindowsLog, LogLevel::Info,
("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
}
#endif
}
// Install msg hook for menus
if (!sCallProcHook) {
DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
GetCurrentThreadId());
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (!sCallProcHook) {
MOZ_LOG(
gWindowsLog, LogLevel::Info,
("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
}
#endif
}
// Install msg hook for the mouse
if (!sCallMouseHook) {
DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
GetCurrentThreadId());
#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
if (!sCallMouseHook) {
MOZ_LOG(gWindowsLog, LogLevel::Info,
("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
}
#endif
}
}
// Unhook special message hooks for dropdowns.
void nsWindow::UnregisterSpecialDropdownHooks() {
DISPLAY_NMM_PRT(
"***************** De-installing Msg Hooks ***************\n");
if (sCallProcHook) {
DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
if (!::UnhookWindowsHookEx(sCallProcHook)) {
DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
}
sCallProcHook = nullptr;
}
if (sMsgFilterHook) {
DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
}
sMsgFilterHook = nullptr;
}
if (sCallMouseHook) {
DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
if (!::UnhookWindowsHookEx(sCallMouseHook)) {
DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
}
sCallMouseHook = nullptr;
}
}
// This timer is designed to only fire one time at most each time a "hook"
// function is used to rollup the dropdown. In some cases, the timer may be
// scheduled from the hook, but that hook event or a subsequent event may roll
// up the dropdown before this timer function is executed.
//
// For example, if an MFC control takes focus, the combobox will lose focus and
// rollup before this function fires.
VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
DWORD dwTime) {
if (sHookTimerId != 0) {
// if the window is nullptr then we need to use the ID to kill the timer
DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
NS_ASSERTION(status, "Hook Timer was not killed.");
sHookTimerId = 0;
}
if (sRollupMsgId != 0) {
// Note: DealWithPopups does the check to make sure that the rollup widget
// is set.
LRESULT popupHandlingResult;
nsAutoRollup autoRollup;
DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
sRollupMsgId = 0;
sRollupMsgWnd = nullptr;
}
}
static bool IsDifferentThreadWindow(HWND aWnd) {
return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
}
// static
bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
Maybe<POINT> aEventPoint) {
RECT r;
::GetWindowRect(aWindow->mWnd, &r);
POINT mp;
if (aEventPoint) {
mp = *aEventPoint;
} else {
DWORD pos = ::GetMessagePos();
mp.x = GET_X_LPARAM(pos);
mp.y = GET_Y_LPARAM(pos);
}
auto margin = aWindow->mInputRegion.mMargin;
if (margin > 0) {
r.top += margin;
r.bottom -= margin;
r.left += margin;
r.right -= margin;
}
// was the event inside this window?
return static_cast<bool>(::PtInRect(&r, mp));
}
// static
bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
uint32_t* aPopupsToRollup,
Maybe<POINT> aEventPoint) {
// If we're dealing with menus, we probably have submenus and we don't want
// to rollup some of them if the click is in a parent menu of the current
// submenu.
*aPopupsToRollup = UINT32_MAX;
AutoTArray<nsIWidget*, 5> widgetChain;
uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
nsIWidget* widget = widgetChain[i];
if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
// Don't roll up if the mouse event occurred within a menu of the
// same type. If the mouse event occurred in a menu higher than that,
// roll up, but pass the number of popups to Rollup so that only those
// of the same type close up.
if (i < sameTypeCount) {
return false;
}
*aPopupsToRollup = sameTypeCount;
break;
}
}
return true;
}
static bool IsTouchSupportEnabled(HWND aWnd) {
nsWindow* topWindow =
WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
return topWindow ? topWindow->IsTouchWindow() : false;
}
static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
Maybe<POINT> ret;
uint32_t cInputs = LOWORD(wParam);
if (cInputs != 1) {
return ret;
}
TOUCHINPUT input;
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
sizeof(TOUCHINPUT))) {
ret.emplace();
ret->x = TOUCH_COORD_TO_PIXEL(input.x);
ret->y = TOUCH_COORD_TO_PIXEL(input.y);
}
// Note that we don't call CloseTouchInputHandle here because we need
// to read the touch input info again in OnTouch later.
return ret;
}
// static
bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
LPARAM aLParam, LRESULT* aResult) {
NS_ASSERTION(aResult, "Bad outResult");
// XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
*aResult = MA_NOACTIVATE;
if (!::IsWindowVisible(aWnd)) {
return false;
}
if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
// NOTE: We deal with this here rather than on the switch below because we
// want to do this even if there are no menus to rollup (tooltips don't set
// the rollup listener etc).
if (RefPtr pm = nsXULPopupManager::GetInstance()) {
pm->RollupTooltips();
}
}
nsIRollupListener* rollupListener = nsIWidget::GetActiveRollupListener();
NS_ENSURE_TRUE(rollupListener, false);
nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
if (!popup) {
return false;
}
uint32_t popupsToRollup = UINT32_MAX;
bool consumeRollupEvent = false;
Maybe<POINT> touchPoint; // In screen coords.
// If we rollup with animations but get occluded right away, we might not
// advance the refresh driver enough for the animation to finish.
auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
switch (aMessage) {
case WM_TOUCH:
if (!IsTouchSupportEnabled(aWnd)) {
// If APZ is disabled, don't allow touch inputs to dismiss popups. The
// compatibility mouse events will do it instead.
return false;
}
touchPoint = GetSingleTouch(aWParam, aLParam);
if (!touchPoint) {
return false;
}
[[fallthrough]];
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_NCLBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCMBUTTONDOWN:
if (aMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
// If any of these mouse events are really compatibility events that
// Windows is sending for touch inputs, then don't allow them to dismiss
// popups when APZ is enabled (instead we do the dismissing as part of
// WM_TOUCH handling which is more correct).
// If we don't do this, then when the user lifts their finger after a
// long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
// us will dismiss the contextmenu popup that we displayed as part of
// handling the long-tap-up.
return false;
}
if (!EventIsInsideWindow(popupWindow, touchPoint) &&
GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
break;
}
return false;
case WM_POINTERDOWN: {
WinPointerEvents pointerEvents;
if (!pointerEvents.ShouldRollupOnPointerEvent(aMessage, aWParam)) {
return false;
}
POINT pt;
pt.x = GET_X_LPARAM(aLParam);
pt.y = GET_Y_LPARAM(aLParam);
if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
return false;
}
if (EventIsInsideWindow(popupWindow, Some(pt))) {
// Don't roll up if the event is inside the popup window.
return false;
}
} break;
case MOZ_WM_DMANIP: {
POINT pt;
::GetCursorPos(&pt);
if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
return false;
}
if (EventIsInsideWindow(popupWindow, Some(pt))) {
// Don't roll up if the event is inside the popup window
return false;
}
} break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
// We need to check if the popup thinks that it should cause closing
// itself when mouse wheel events are fired outside the rollup widget.
if (!EventIsInsideWindow(popupWindow)) {
// Check if we should consume this event even if we don't roll-up:
consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
*aResult = MA_ACTIVATE;
if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
GetPopupsToRollup(rollupListener, &popupsToRollup)) {
break;
}
}
return consumeRollupEvent;
case WM_ACTIVATEAPP:
allowAnimations = nsIRollupListener::AllowAnimations::No;
break;
case WM_ACTIVATE: {
// This marker should be useless nowadays, but kept just for safety, see
// the discussion in D210302. See also bug 1842170.
WndProcUrgentInvocation::Marker _marker;
nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
nsWindow* prevWindow =
WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
// Don't rollup popups for WM_ACTIVATE from/to a popup.
// When we click on a popup (WA_CLICKACTIVE) we don't want to do it.
// WA_ACTIVE/WA_INACTIVE shouldn't really happen, but some old
// pre-windows-10 drivers used to do this, see bug 953146.
// It might be the case that is no longer needed tho, and we can move
// this to the WA_CLICKACTIVE condition.
if ((window && window->IsPopup()) ||
(prevWindow && prevWindow->IsPopup())) {
return false;
}
if (LOWORD(aWParam) == WA_CLICKACTIVE &&
!GetPopupsToRollup(rollupListener, &popupsToRollup)) {
return false;
}
allowAnimations = nsIRollupListener::AllowAnimations::No;
} break;
case WM_MOUSEACTIVATE:
if (!EventIsInsideWindow(popupWindow) &&
GetPopupsToRollup(rollupListener, &popupsToRollup)) {
// WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
// of TweakUI is enabled. Then, check if the popup should be rolled up
// with rollup listener. If not, just consume the message.
if (HIWORD(aLParam) == WM_MOUSEMOVE &&
!rollupListener->ShouldRollupOnMouseActivate()) {
return true;
}
// Otherwise, it should be handled by wndproc.
return false;
}
// Prevent the click inside the popup from causing a change in window
// activation. Since the popup is shown non-activated, we need to eat any
// requests to activate the window while it is displayed. Windows will
// automatically activate the popup on the mousedown otherwise.
return true;
case WM_SHOWWINDOW:
// If the window is being minimized, close popups.
if (aLParam == SW_PARENTCLOSING) {
allowAnimations = nsIRollupListener::AllowAnimations::No;
break;
}
return false;
case WM_KILLFOCUS:
// If focus moves to other window created in different process/thread,
// e.g., a plugin window, popups should be rolled up.
if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
allowAnimations = nsIRollupListener::AllowAnimations::No;
break;
}
return false;
case WM_MOVING:
case WM_MENUSELECT:
break;
default:
return false;
}
// Only need to deal with the last rollup for left mouse down events.
NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
nsIRollupListener::RollupOptions rollupOptions{
popupsToRollup,
nsIRollupListener::FlushViews::Yes,
/* mPoint = */ nullptr,
allowAnimations,
};
if (aMessage == WM_TOUCH || aMessage == WM_LBUTTONDOWN ||
aMessage == WM_POINTERDOWN) {
LayoutDeviceIntPoint pos;
if (aMessage == WM_TOUCH) {
pos.x = touchPoint->x;
pos.y = touchPoint->y;
} else {
POINT pt;
pt.x = GET_X_LPARAM(aLParam);
pt.y = GET_Y_LPARAM(aLParam);
// POINTERDOWN is already in screen coords.
if (aMessage == WM_LBUTTONDOWN) {
::ClientToScreen(aWnd, &pt);
}
pos = LayoutDeviceIntPoint(pt.x, pt.y);
}
rollupOptions.mPoint = &pos;
nsIContent* lastRollup = nullptr;
consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
nsAutoRollup::SetLastRollup(lastRollup);
} else {
consumeRollupEvent = rollupListener->Rollup(rollupOptions);
}
// Tell hook to stop processing messages
sProcessHook = false;
sRollupMsgId = 0;
sRollupMsgWnd = nullptr;
// If we are NOT supposed to be consuming events, let it go through
if (consumeRollupEvent && aMessage != WM_RBUTTONDOWN) {
*aResult = MA_ACTIVATE;
return true;
}
return false;
}
/**************************************************************
**************************************************************
**
** BLOCK: Misc. utility methods and functions.
**
** General use.
**
**************************************************************
**************************************************************/
// Note that the result of GetTopLevelWindow method can be different from the
// result of WinUtils::GetTopLevelHWND(). The result can be non-floating
// window. Because our top level window may be contained in another window
// which is not managed by us.
nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
nsWindow* curWindow = this;
while (true) {
if (aStopOnDialogOrPopup) {
switch (curWindow->mWindowType) {
case WindowType::Dialog:
case WindowType::Popup:
return curWindow;
default:
break;
}
}
// Retrieve the top level parent or owner window
nsWindow* parentWindow = curWindow->GetParentWindow(true);
if (!parentWindow) return curWindow;
curWindow = parentWindow;
}
}
// Set a flag if hwnd is a (non-popup) visible window from this process,
// and bail out of the enumeration. Otherwise leave the flag unmodified
// and continue the enumeration.
// lParam must be a bool* pointing at the flag to be set.
static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
DWORD pid;
::GetWindowThreadProcessId(hwnd, &pid);
if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
// Don't count popups as visible windows, since they don't take focus,
// in case we only have a popup visible (see bug 1554490 where the gfx
// test window is an offscreen popup).
nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
if (!window || !window->IsPopup()) {
bool* windowsVisible = reinterpret_cast<bool*>(lParam);
*windowsVisible = true;
return FALSE;
}
}
return TRUE;
}
// Determine if it would be ok to activate a window, taking focus.
// We want to avoid stealing focus from another app (bug 225305).
bool nsWindow::CanTakeFocus() {
HWND fgWnd = ::GetForegroundWindow();
if (!fgWnd) {
// There is no foreground window, so don't worry about stealing focus.
return true;
}
// We can take focus if the current foreground window is already from
// this process.
DWORD pid;
::GetWindowThreadProcessId(fgWnd, &pid);
if (pid == ::GetCurrentProcessId()) {
return true;
}
bool windowsVisible = false;
::EnumWindows(EnumVisibleWindowsProc,
reinterpret_cast<LPARAM>(&windowsVisible));
if (!windowsVisible) {
// We're probably creating our first visible window, allow that to
// take focus.
return true;
}
return false;
}
static const wchar_t* GetMainWindowClass() {
static const wchar_t* sMainWindowClass = nullptr;
if (!sMainWindowClass) {
nsAutoString className;
Preferences::GetString("ui.window_class_override", className);
if (!className.IsEmpty()) {
sMainWindowClass = wcsdup(className.get());
} else {
sMainWindowClass = kClassNameGeneral;
}
}
return sMainWindowClass;
}
LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
::ClientToScreen(mWnd, &pt);
return MAKELPARAM(pt.x, pt.y);
}
LPARAM nsWindow::lParamToClient(LPARAM lParam) {
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
::ScreenToClient(mWnd, &pt);
return MAKELPARAM(pt.x, pt.y);
}
WPARAM nsWindow::wParamFromGlobalMouseState() {
WPARAM result = 0;
if (!!::GetKeyState(VK_CONTROL)) {
result |= MK_CONTROL;
}
if (!!::GetKeyState(VK_SHIFT)) {
result |= MK_SHIFT;
}
if (!!::GetKeyState(VK_LBUTTON)) {
result |= MK_LBUTTON;
}
if (!!::GetKeyState(VK_MBUTTON)) {
result |= MK_MBUTTON;
}
if (!!::GetKeyState(VK_RBUTTON)) {
result |= MK_RBUTTON;
}
if (!!::GetKeyState(VK_XBUTTON1)) {
result |= MK_XBUTTON1;
}
if (!!::GetKeyState(VK_XBUTTON2)) {
result |= MK_XBUTTON2;
}
return result;
}
// WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
// top-level ancestor of its provided owner-window. If the modal window's
// container process crashes, it will never get a chance to undo that.
//
// For simplicity's sake we simply unconditionally perform both the disabling
// and reenabling here, synchronously, on the main thread, rather than leaving
// it to happen in our asynchronously-operated IFileDialog.
void nsWindow::PickerOpen() {
AssertIsOnMainThread();
// Disable the root-level window synchronously before any file-dialogs get a
// chance to fight over doing it asynchronously.
if (!mPickerDisplayCount) {
::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), FALSE);
}
mPickerDisplayCount++;
}
void nsWindow::PickerClosed() {
AssertIsOnMainThread();
NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
if (!mPickerDisplayCount) return;
mPickerDisplayCount--;
// Once all the file-dialogs are gone, reenable the root-level window.
if (!mPickerDisplayCount) {
::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), TRUE);
DispatchFocusToTopLevelWindow(true);
}
if (!mPickerDisplayCount && mDestroyCalled) {
Destroy();
}
}
bool nsWindow::WidgetTypeSupportsAcceleration() { return true; }
bool nsWindow::DispatchTouchEventFromWMPointer(
UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
mozilla::MouseButton aButton) {
MultiTouchInput::MultiTouchType touchType;
switch (msg) {
case WM_POINTERDOWN:
touchType = MultiTouchInput::MULTITOUCH_START;
break;
case WM_POINTERUPDATE:
if (aPointerInfo.mPressure == 0) {
return false; // hover
}
touchType = MultiTouchInput::MULTITOUCH_MOVE;
break;
case WM_POINTERUP:
touchType = MultiTouchInput::MULTITOUCH_END;
break;
default:
return false;
}
nsPointWin touchPoint;
touchPoint.x = GET_X_LPARAM(aLParam);
touchPoint.y = GET_Y_LPARAM(aLParam);
touchPoint.ScreenToClient(mWnd);
SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
ScreenIntPoint::FromUnknownPoint(touchPoint),
ScreenSize(1, 1), // pixel size radius for pen
0.0f, // no radius rotation
aPointerInfo.mPressure);
touchData.mTiltX = aPointerInfo.tiltX;
touchData.mTiltY = aPointerInfo.tiltY;
touchData.mTwist = aPointerInfo.twist;
MultiTouchInput touchInput;
touchInput.mType = touchType;
touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
touchInput.mTouches.AppendElement(touchData);
touchInput.mButton = aButton;
touchInput.mButtons = aPointerInfo.mButtons;
touchInput.mInputSource = MouseEvent_Binding::MOZ_SOURCE_PEN;
// POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
ModifierKeyState modifierKeyState;
touchInput.modifiers = modifierKeyState.GetModifiers();
DispatchTouchInput(touchInput);
return true;
}
static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
// Theoretically flags can be set together but they do not
if (aPenFlags & PEN_FLAG_BARREL) {
return MouseButton::eSecondary;
}
if (aPenFlags & PEN_FLAG_ERASER) {
return MouseButton::eEraser;
}
return MouseButton::ePrimary;
}
bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
if (!mAPZC) {
// APZ is not available on context menu. Follow the behavior of touch input
// which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
return false;
}
uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
POINTER_INPUT_TYPE pointerType = PT_POINTER;
if (!GetPointerType(pointerId, &pointerType)) {
MOZ_ASSERT(false, "cannot find PointerType");
return false;
}
if (pointerType == PT_TOUCH) {
if (!StaticPrefs::
dom_w3c_pointer_events_dispatch_by_pointer_messages_touch()) {
return false;
}
return OnTouchPointerEvents(pointerId, msg, aWParam, aLParam);
}
if (pointerType == PT_PEN) {
return OnPenPointerEvents(pointerId, msg, aWParam, aLParam);
}
return false;
}
bool nsWindow::OnPenPointerEvents(uint32_t aPointerId, UINT aMsg,
WPARAM aWParam, LPARAM aLParam) {
if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
// We have to handle WM_POINTER* to fetch and cache pen related information
// and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
// handler. This is because Windows doesn't support ::DoDragDrop in the
// touch or pen message handlers.
mPointerEvents.ConvertAndCachePointerInfo(aMsg, aWParam);
// Don't consume the Windows WM_POINTER* messages
return false;
}
POINTER_PEN_INFO penInfo{};
if (!mPointerEvents.GetPointerPenInfo(aPointerId, &penInfo)) {
return false;
}
// The tiltX, tiltY and twist may require the high-end modes of pen tables.
// Allowing testers check whether the given these valures are exposed to the
// web, here allows the prefs override the values.
if (StaticPrefs::widget_windows_pen_tilt_override_enabled() ||
StaticPrefs::widget_windows_pen_twist_override_enabled()) {
static uint32_t sPendingToUpdate =
StaticPrefs::widget_windows_pen_override_number_of_preserver_value();
if (StaticPrefs::widget_windows_pen_tilt_override_enabled()) {
static int32_t sOverrideTiltX = 30;
static int32_t sOverrideTiltY = 0;
if (sPendingToUpdate) {
penInfo.tiltX = sOverrideTiltX;
penInfo.tiltY = sOverrideTiltY;
} else {
const auto GetCurrentOverrideValueWithUpdatingNextValue =
[](int32_t& aOverrideTilt) {
const int32_t oldValue = aOverrideTilt;
aOverrideTilt = aOverrideTilt >= 45 ? -45 : aOverrideTilt + 5;
return oldValue;
};
penInfo.tiltX =
GetCurrentOverrideValueWithUpdatingNextValue(sOverrideTiltX);
penInfo.tiltY =
GetCurrentOverrideValueWithUpdatingNextValue(sOverrideTiltY);
}
}
if (StaticPrefs::widget_windows_pen_twist_override_enabled()) {
static uint32_t sOverrideTwist = 0;
if (sPendingToUpdate) {
penInfo.rotation = sOverrideTwist;
} else {
const auto GetCurrentOverrideValueWithUpdatingNextValue =
[](uint32_t& aOverrideTwist) {
const uint32_t oldValue = aOverrideTwist;
aOverrideTwist = aOverrideTwist >= 350 ? 0 : aOverrideTwist + 10;
return oldValue;
};
penInfo.rotation =
GetCurrentOverrideValueWithUpdatingNextValue(sOverrideTwist);
}
}
if (sPendingToUpdate) {
sPendingToUpdate--;
} else {
sPendingToUpdate =
StaticPrefs::widget_windows_pen_override_number_of_preserver_value();
}
}
// When dispatching mouse events with pen, there may be some
// WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
// small movements. Those events will reset sLastMousePoint and reset
// sLastClickCount. To prevent that, we keep the last pen down position
// and compare it with the subsequent WM_POINTERUPDATE. If the movement is
// smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
// eMouseMove for WM_POINTERUPDATE.
static POINT sLastPointerDownPoint = {0};
// We don't support chorded buttons for pen. Keep the button at
// WM_POINTERDOWN.
static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
static bool sPointerDown = false;
EventMessage message;
mozilla::MouseButton button = MouseButton::ePrimary;
switch (aMsg) {
case WM_POINTERDOWN: {
LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
GET_Y_LPARAM(aLParam));
sLastPointerDownPoint.x = eventPoint.x;
sLastPointerDownPoint.y = eventPoint.y;
message = eMouseDown;
button = PenFlagsToMouseButton(penInfo.penFlags);
sLastPenDownButton = button;
sPointerDown = true;
} break;
case WM_POINTERUP:
message = eMouseUp;
MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
sPointerDown = false;
break;
case WM_POINTERUPDATE:
message = eMouseMove;
if (sPointerDown) {
LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
GET_Y_LPARAM(aLParam));
int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
? sLastPointerDownPoint.x - eventPoint.x.value
: eventPoint.x.value - sLastPointerDownPoint.x;
int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
? sLastPointerDownPoint.y - eventPoint.y.value
: eventPoint.y.value - sLastPointerDownPoint.y;
bool insideMovementThreshold =
movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
if (insideMovementThreshold) {
// Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
// from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
return false;
}
button = sLastPenDownButton;
}
break;
case WM_POINTERLEAVE:
message = eMouseExitFromWidget;
break;
default:
return false;
}
// Windows defines the pen pressure is normalized to a range between 0 and
// 1024. Convert it to float.
float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
int16_t buttons = sPointerDown
? nsContentUtils::GetButtonsFlagForButton(button)
: static_cast<int16_t>(MouseButtonsFlag::eNoButtons);
WinPointerInfo pointerInfo(aPointerId, penInfo.tiltX, penInfo.tiltY, pressure,
buttons);
// Per
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
// the rotation is normalized in a range of 0 to 359.
MOZ_ASSERT(penInfo.rotation <= 359);
pointerInfo.twist = (int32_t)penInfo.rotation;
// Fire touch events but not when the barrel button is pressed.
if (button != MouseButton::eSecondary &&
StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
DispatchTouchEventFromWMPointer(aMsg, aLParam, pointerInfo, button)) {
return true;
}
// The aLParam of WM_POINTER* is the screen location. Convert it to client
// location
LPARAM newLParam = lParamToClient(aLParam);
DispatchMouseEvent(message, aWParam, newLParam, false, button,
MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
if (button == MouseButton::eSecondary && message == eMouseUp) {
// Fire eContextMenu manually since consuming WM_POINTER* blocks
// WM_CONTEXTMENU
DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
}
// Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
// WM_MOUSEMOVE.
return true;
}
bool nsWindow::OnTouchPointerEvents(uint32_t aPointerId, UINT aMsg,
WPARAM aWParam, LPARAM aLParam) {
MultiTouchInput::MultiTouchType touchType;
switch (aMsg) {
case WM_POINTERDOWN:
touchType = MultiTouchInput::MULTITOUCH_START;
break;
case WM_POINTERUPDATE:
touchType = MultiTouchInput::MULTITOUCH_MOVE;
break;
case WM_POINTERUP:
touchType = MultiTouchInput::MULTITOUCH_END;
break;
default:
return false;
}
nsTArray<POINTER_TOUCH_INFO> touchInfoArray{};
mPointerEvents.GetPointerFrameTouchInfo(aPointerId, touchInfoArray);
if (touchInfoArray.IsEmpty()) {
return false;
}
MultiTouchInput inputToDispatch;
inputToDispatch.mInputType = MULTITOUCH_INPUT;
inputToDispatch.mType = touchType;
inputToDispatch.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
for (const POINTER_TOUCH_INFO& touchInfo : touchInfoArray) {
ScreenSize size(static_cast<float>(touchInfo.rcContact.right -
touchInfo.rcContact.left),
static_cast<float>(touchInfo.rcContact.bottom -
touchInfo.rcContact.top));
nsPointWin touchPoint;
touchPoint.x = touchInfo.pointerInfo.ptPixelLocation.x;
touchPoint.y = touchInfo.pointerInfo.ptPixelLocation.y;
touchPoint.ScreenToClient(mWnd);
// Windows provides orientation info, but the behavior differs from
// TouchEvent because TouchEvent's angle rotates the elliptic contact region
// while the Windows provided orientation is independent from touch point
// rect.
//
// e.g. For a vertically long touch pointer, Windows would give vertically
// long rect and also give a 90 degree orientation, and passing both would
// incorrectly represent a horizontal ellipse.
//
// See also: https://w3c.github.io/touch-events/#dom-touch-rotationangle
// "The angle (in degrees) that the ellipse described by radiusX and radiusY
// is rotated clockwise about its center; 0 if no value is known."
float angle = 0.0f;
bool hasPressure = !!(touchInfo.touchMask & TOUCH_MASK_PRESSURE);
float pressure = hasPressure ? (float)touchInfo.pressure / 1024 : 0;
inputToDispatch.mTouches.AppendElement(
SingleTouchData(static_cast<int32_t>(touchInfo.pointerInfo.pointerId),
ScreenIntPoint::FromUnknownPoint(touchPoint), size / 2,
angle, pressure));
}
DispatchTouchInput(inputToDispatch);
return true;
}
void nsWindow::GetCompositorWidgetInitData(
mozilla::widget::CompositorWidgetInitData* aInitData) {
*aInitData = WinCompositorWidgetInitData(
reinterpret_cast<uintptr_t>(mWnd),
reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
mTransparencyMode);
}
bool nsWindow::SynchronouslyRepaintOnResize() { return false; }
void nsWindow::MaybeDispatchInitialFocusEvent() {
if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
DispatchFocusToTopLevelWindow(true);
}
}
already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
nsCOMPtr<nsIWidget> window = new nsWindow();
return window.forget();
}
already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
nsCOMPtr<nsIWidget> window = new nsWindow();
return window.forget();
}
// static
bool nsWindow::InitTouchInjection() {
if (!sTouchInjectInitialized) {
// Initialize touch injection on the first call
HMODULE hMod = LoadLibraryW(kUser32LibName);
if (!hMod) {
return false;
}
InitializeTouchInjectionPtr func =
(InitializeTouchInjectionPtr)GetProcAddress(hMod,
"InitializeTouchInjection");
if (!func) {
WinUtils::Log("InitializeTouchInjection not available.");
return false;
}
if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
GetLastError());
return false;
}
sInjectTouchFuncPtr =
(InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
if (!sInjectTouchFuncPtr) {
WinUtils::Log("InjectTouchInput not available.");
return false;
}
sTouchInjectInitialized = true;
}
return true;
}
bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
POINTER_FLAGS aFlags, uint32_t aPressure,
uint32_t aOrientation) {
if (aId > TOUCH_INJECT_MAX_POINTS) {
WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
return false;
}
POINTER_TOUCH_INFO info{};
info.touchFlags = TOUCH_FLAG_NONE;
info.touchMask =
TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
info.pressure = aPressure;
info.orientation = aOrientation;
info.pointerInfo.pointerFlags = aFlags;
info.pointerInfo.pointerType = PT_TOUCH;
info.pointerInfo.pointerId = aId;
info.pointerInfo.ptPixelLocation.x = aPoint.x;
info.pointerInfo.ptPixelLocation.y = aPoint.y;
info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
for (int i = 0; i < 3; i++) {
if (sInjectTouchFuncPtr(1, &info)) {
break;
}
DWORD error = GetLastError();
if (error == ERROR_NOT_READY && i < 2) {
// We sent it too quickly after the previous injection (see bug 1535140
// comment 10). On the first loop iteration we just yield (via Sleep(0))
// and try again. If it happens again on the second loop iteration we
// explicitly Sleep(1) and try again. If that doesn't work either we just
// error out.
::Sleep(i);
continue;
}
WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
return false;
}
return true;
}
void nsWindow::ChangedDPI() {
if (mWidgetListener) {
if (PresShell* presShell = mWidgetListener->GetPresShell()) {
presShell->BackingScaleFactorChanged();
}
}
NotifyAPZOfDPIChange();
}
static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
TouchPointerState aPointerState, bool isUpdate) {
bool hover = aPointerState & TOUCH_HOVER;
bool contact = aPointerState & TOUCH_CONTACT;
bool remove = aPointerState & TOUCH_REMOVE;
bool cancel = aPointerState & TOUCH_CANCEL;
POINTER_FLAGS flags;
if (isUpdate) {
// We know about this pointer, send an update
flags = POINTER_FLAG_UPDATE;
if (hover) {
flags |= POINTER_FLAG_INRANGE;
} else if (contact) {
flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
} else if (remove) {
flags = POINTER_FLAG_UP;
}
if (cancel) {
flags |= POINTER_FLAG_CANCELED;
}
} else {
// Missing init state, error out
if (remove || cancel) {
return Err(NS_ERROR_INVALID_ARG);
}
// Create a new pointer
flags = POINTER_FLAG_INRANGE;
if (contact) {
flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
}
}
return flags;
}
nsresult nsWindow::SynthesizeNativeTouchPoint(
uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
LayoutDeviceIntPoint aPoint, double aPointerPressure,
uint32_t aPointerOrientation, nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
if (StaticPrefs::apz_test_fails_with_native_injection() ||
!InitTouchInjection()) {
// If we don't have touch injection from the OS, or if we are running a test
// that cannot properly inject events to satisfy the OS requirements (see
// bug 1313170) we can just fake it and synthesize the events from here.
MOZ_ASSERT(NS_IsMainThread());
if (aPointerState == TOUCH_HOVER) {
return NS_ERROR_UNEXPECTED;
}
if (!mSynthesizedTouchInput) {
mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
}
WidgetEventTime time = CurrentMessageWidgetEventTime();
LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
DispatchTouchInput(inputToDispatch);
return NS_OK;
}
// win api expects a value from 0 to 1024. aPointerPressure is a value
// from 0.0 to 1.0.
uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
// If we already know about this pointer id get it's record
return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
POINTER_FLAGS flags;
// Can't use MOZ_TRY because it confuses WithEntryHandle
auto result = PointerStateToFlag(aPointerState, !!entry);
if (result.isOk()) {
flags = result.unwrap();
} else {
return result.unwrapErr();
}
if (!entry) {
entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
PointerInfo::PointerType::TOUCH));
} else {
if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
return NS_ERROR_UNEXPECTED;
}
if (aPointerState & TOUCH_REMOVE) {
// Remove the pointer from our tracking list. This is UniquePtr wrapped,
// so shouldn't leak.
entry.Remove();
}
}
return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
aPointerOrientation)
? NS_ERROR_UNEXPECTED
: NS_OK;
});
}
#if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
#endif
static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
static bool InitPenInjection() {
if (sSyntheticPenDevice) {
return true;
}
#if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
HMODULE hMod = LoadLibraryW(kUser32LibName);
if (!hMod) {
return false;
}
CreateSyntheticPointerDevice =
(CreateSyntheticPointerDevicePtr)GetProcAddress(
hMod, "CreateSyntheticPointerDevice");
if (!CreateSyntheticPointerDevice) {
WinUtils::Log("CreateSyntheticPointerDevice not available.");
return false;
}
DestroySyntheticPointerDevice =
(DestroySyntheticPointerDevicePtr)GetProcAddress(
hMod, "DestroySyntheticPointerDevice");
if (!DestroySyntheticPointerDevice) {
WinUtils::Log("DestroySyntheticPointerDevice not available.");
return false;
}
InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
hMod, "InjectSyntheticPointerInput");
if (!InjectSyntheticPointerInput) {
WinUtils::Log("InjectSyntheticPointerInput not available.");
return false;
}
#endif
sSyntheticPenDevice =
CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
return !!sSyntheticPenDevice;
}
nsresult nsWindow::SynthesizeNativePenInput(
uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
int32_t aTiltX, int32_t aTiltY, int32_t aButton,
nsISynthesizedEventCallback* aCallback) {
AutoSynthesizedEventCallbackNotifier notifier(aCallback);
if (!InitPenInjection()) {
return NS_ERROR_UNEXPECTED;
}
// win api expects a value from 0 to 1024. aPointerPressure is a value
// from 0.0 to 1.0.
uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
// If we already know about this pointer id get it's record
return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
POINTER_FLAGS flags;
// Can't use MOZ_TRY because it confuses WithEntryHandle
auto result = PointerStateToFlag(aPointerState, !!entry);
if (result.isOk()) {
flags = result.unwrap();
} else {
return result.unwrapErr();
}
if (!entry) {
entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
PointerInfo::PointerType::PEN));
} else {
if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
return NS_ERROR_UNEXPECTED;
}
if (aPointerState & TOUCH_REMOVE) {
// Remove the pointer from our tracking list. This is UniquePtr wrapped,
// so shouldn't leak.
entry.Remove();
}
}
POINTER_TYPE_INFO info{};
info.type = PT_PEN;
info.penInfo.pointerInfo.pointerType = PT_PEN;
info.penInfo.pointerInfo.pointerFlags = flags;
info.penInfo.pointerInfo.pointerId = aPointerId;
info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
info.penInfo.penFlags = PEN_FLAG_NONE;
// PEN_FLAG_ERASER is not supported this way, unfortunately.
if (aButton == 2) {
info.penInfo.penFlags |= PEN_FLAG_BARREL;
}
info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
info.penInfo.pressure = pressure;
info.penInfo.rotation = aRotation;
info.penInfo.tiltX = aTiltX;
info.penInfo.tiltY = aTiltY;
return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
? NS_OK
: NS_ERROR_UNEXPECTED;
});
};
bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
LRESULT* aRetValue) {
ModifierKeyState modKeyState;
NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
bool consumed = nativeKey.HandleAppCommandMessage();
*aRetValue = consumed ? 1 : 0;
return consumed;
}
#ifdef DEBUG
nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
return WinUtils::SetHiDPIMode(aHiDPI);
}
nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
#endif
mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
// Check all four sides of our monitor for an appbar. Skip any that aren't
// the system taskbar.
MONITORINFO mi;
mi.cbSize = sizeof(MONITORINFO);
::GetMonitorInfo(windowMonitor, &mi);
APPBARDATA appBarData;
appBarData.cbSize = sizeof(appBarData);
appBarData.rc = mi.rcMonitor;
const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
for (auto edge : kEdges) {
appBarData.uEdge = edge;
HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
if (appBarHwnd) {
nsAutoString className;
if (WinUtils::GetClassName(appBarHwnd, className)) {
if (className.Equals(L"Shell_TrayWnd") ||
className.Equals(L"Shell_SecondaryTrayWnd")) {
return Some(edge);
}
}
}
}
return Nothing();
}
static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
WINDOWPLACEMENT pl;
pl.length = sizeof(pl);
::GetWindowPlacement(aWnd, &pl);
if (pl.showCmd == SW_SHOWMINIMIZED) {
return nsSizeMode_Minimized;
} else if (aFullscreenMode) {
return nsSizeMode_Fullscreen;
} else if (pl.showCmd == SW_SHOWMAXIMIZED) {
return nsSizeMode_Maximized;
} else {
return nsSizeMode_Normal;
}
}
static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
// This will likely cause a callback to
// nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
switch (aMode) {
case nsSizeMode_Fullscreen:
::ShowWindow(aWnd, SW_SHOW);
break;
case nsSizeMode_Maximized:
::ShowWindow(aWnd, SW_MAXIMIZE);
break;
case nsSizeMode_Minimized:
::ShowWindow(aWnd, SW_MINIMIZE);
break;
default:
// Don't call ::ShowWindow if we're trying to "restore" a window that is
// already in a normal state. Prevents a bug where snapping to one side
// of the screen and then minimizing would cause Windows to forget our
// window's correct restored position/size.
if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
::ShowWindow(aWnd, SW_RESTORE);
}
}
}
nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
void nsWindow::FrameState::CheckInvariant() const {
MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
mPreFullscreenSizeMode < nsSizeMode_Invalid);
MOZ_ASSERT(mWindow);
// We should never observe fullscreen sizemode unless fullscreen is enabled
MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
// Something went wrong if we somehow saved fullscreen mode when we are
// changing into fullscreen mode
MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
}
void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
}
void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
DoShowWindow aDoShowWindow) {
if (mSizeMode == aMode) {
return;
}
if (StaticPrefs::widget_windows_fullscreen_remind_taskbar()) {
// If we're unminimizing a window, asynchronously notify the taskbar after
// the message has been processed. This redundant notification works around
// a race condition in explorer.exe. (See bug 1835851, or comments in
// TaskbarConcealer.)
//
// Note that we notify regardless of `aMode`: unminimizing a non-fullscreen
// window can also affect the correct taskbar state, yet fail to affect the
// current taskbar state.
if (mSizeMode == nsSizeMode_Minimized) {
::PostMessage(mWindow->mWnd, MOZ_WM_FULLSCREEN_STATE_UPDATE, 0, 0);
}
}
if (aMode == nsSizeMode_Fullscreen) {
EnsureFullscreenMode(true, aDoShowWindow);
MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
} else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
// If we are in fullscreen mode, minimize should work like normal and
// return us to fullscreen mode when unminimized. Maximize isn't really
// available and won't do anything. "Restore" should do the same thing as
// requesting to end fullscreen.
EnsureFullscreenMode(false, aDoShowWindow);
} else {
SetSizeModeInternal(aMode, aDoShowWindow);
}
}
void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
DoShowWindow aDoShowWindow) {
const bool changed = aFullScreen != mFullscreenMode;
if (changed && aFullScreen) {
// Save the size mode from before fullscreen.
mPreFullscreenSizeMode = mSizeMode;
}
mFullscreenMode = aFullScreen;
if (changed || aFullScreen) {
// NOTE(emilio): When minimizing a fullscreen window we remain with
// mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
// make sure to call SetSizeModeInternal even if mFullscreenMode didn't
// change, to ensure we actually end up with a fullscreen sizemode when
// restoring a window from that state.
SetSizeModeInternal(
aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
aDoShowWindow);
}
}
void nsWindow::FrameState::OnFrameChanging() {
const nsSizeMode newSizeMode =
GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
EnsureSizeMode(newSizeMode);
mWindow->UpdateNonClientMargins(false);
}
void nsWindow::FrameState::OnFrameChanged() {
// We don't want to perform the ShowWindow ourselves if we're on the frame
// changed message. Windows has done the frame change for us, and we take care
// of activating as needed. We also don't want to potentially trigger
// more focus / restore. Among other things, this addresses a bug on Win7
// related to window docking. (bug 489258)
const auto oldSizeMode = mSizeMode;
const auto newSizeMode =
GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
EnsureSizeMode(newSizeMode, DoShowWindow::No);
// If window was restored, activate the window now to get correct attributes.
if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
oldSizeMode == nsSizeMode_Minimized &&
mSizeMode != nsSizeMode_Minimized) {
mWindow->DispatchFocusToTopLevelWindow(true);
}
}
static void MaybeLogSizeMode(nsSizeMode aMode) {
#ifdef WINSTATE_DEBUG_OUTPUT
MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
#endif
}
void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
DoShowWindow aDoShowWindow) {
if (mSizeMode == aMode) {
return;
}
const auto oldSizeMode = mSizeMode;
const bool fullscreenChange =
mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
const bool maximized = aMode == nsSizeMode_Maximized;
const bool fullscreen = aMode == nsSizeMode_Fullscreen;
mSizeMode = aMode;
MaybeLogSizeMode(mSizeMode);
if (bool(aDoShowWindow) && mWindow->mIsVisible) {
ShowWindowWithMode(mWindow->mWnd, aMode);
}
mWindow->UpdateNonClientMargins(false);
if (fullscreenChange) {
mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
} else if (maximized) {
TaskbarConcealer::OnWindowMaximized(mWindow);
}
mWindow->OnSizeModeChange();
}
void nsWindow::ContextMenuPreventer::Update(
const WidgetMouseEvent& aEvent,
const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
mNeedsToPreventContextMenu =
aEvent.mMessage == eMouseUp &&
aEvent.mButton == MouseButton::eSecondary &&
aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;
}
|