Compare commits
648
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f54df3762 | ||
|
|
446b22790a | ||
|
|
ffb0cb8ae1 | ||
|
|
d14fbe3f68 | ||
|
|
c05685f43f | ||
|
|
078a09428d | ||
|
|
468941678e | ||
|
|
2f7898f067 | ||
|
|
f4fefca0d2 | ||
|
|
247e10f4ba | ||
|
|
bda3141209 | ||
|
|
63c20e39de | ||
|
|
ec8f0ed7e3 | ||
|
|
2212143d4f | ||
|
|
c79d16f87b | ||
|
|
7a872e8f08 | ||
|
|
baaefa5889 | ||
|
|
dba5c890ab | ||
|
|
aea4e86a4b | ||
|
|
66180dfea4 | ||
|
|
a563423cac | ||
|
|
8e117bab07 | ||
|
|
6ea0ae9f54 | ||
|
|
a3b614056c | ||
|
|
2cc21a9a5e | ||
|
|
40c3319eae | ||
|
|
53e50a5fe0 | ||
|
|
fe33f7f6ae | ||
|
|
e49cd4cc72 | ||
|
|
72060b08ba | ||
|
|
aa267e212c | ||
|
|
94577ddad7 | ||
|
|
e0c04228c9 | ||
|
|
fa8cf84275 | ||
|
|
706d4782ba | ||
|
|
75d346ab0d | ||
|
|
9b2556a56c | ||
|
|
c449e942b3 | ||
|
|
7dbbdd65ac | ||
|
|
cf7f9ede29 | ||
|
|
29ce1a45ce | ||
|
|
b708aa8f14 | ||
|
|
41a5c06ed2 | ||
|
|
7c4c383e41 | ||
|
|
b55fad54f9 | ||
|
|
c24580c0af | ||
|
|
9545d4bf1b | ||
|
|
d561a4ce2e | ||
|
|
fc3d98651d | ||
|
|
de345f9ce8 | ||
|
|
af703129f0 | ||
|
|
924c474b07 | ||
|
|
c378bf4ddc | ||
|
|
bf56c41bf2 | ||
|
|
7725af0346 | ||
|
|
05d50a1f5a | ||
|
|
7768282869 | ||
|
|
a93ef42dce | ||
|
|
7169e40ddf | ||
|
|
956f449055 | ||
|
|
9083b311f2 | ||
|
|
4222d1281e | ||
|
|
c79165e512 | ||
|
|
539fe249de | ||
|
|
c0ae14fe34 | ||
|
|
1a20e65e4e | ||
|
|
fcd8fe2598 | ||
|
|
ee1adb7fb1 | ||
|
|
f7ea599ff4 | ||
|
|
a01fdc5f31 | ||
|
|
2cd29aa972 | ||
|
|
24595cf058 | ||
|
|
7ae460b17a | ||
|
|
5f290d1097 | ||
|
|
a0b10f4375 | ||
|
|
1d7ba07673 | ||
|
|
f51bc72a14 | ||
|
|
e8644b494f | ||
|
|
0acf5cb1a1 | ||
|
|
f2d027343a | ||
|
|
eaf2a4ac4b | ||
|
|
aa6a599aa4 | ||
|
|
f385a5e06c | ||
|
|
559e0dbdc5 | ||
|
|
e59e88bfd5 | ||
|
|
7e3d96909f | ||
|
|
d294c31da1 | ||
|
|
d699633e82 | ||
|
|
7079db6476 | ||
|
|
e938cb015b | ||
|
|
15893d9ebc | ||
|
|
0f45bf39af | ||
|
|
23398d176b | ||
|
|
7809ecf653 | ||
|
|
43d19989d4 | ||
|
|
81fce269f5 | ||
|
|
03bdba053e | ||
|
|
5088774087 | ||
|
|
c4d42aaeff | ||
|
|
5b5d7841a6 | ||
|
|
b5e48c5458 | ||
|
|
714c06d255 | ||
|
|
42c59e0781 | ||
|
|
a877a69089 | ||
|
|
98a7463135 | ||
|
|
6cdcc60623 | ||
|
|
c4e2909bdc | ||
|
|
8f1b4387b4 | ||
|
|
a7bc4e7c97 | ||
|
|
62e29a58b0 | ||
|
|
3ede95de79 | ||
|
|
fce465d3a6 | ||
|
|
71edc1fbf5 | ||
|
|
53e4ec00ef | ||
|
|
aa066876a0 | ||
|
|
ae54d6c3d2 | ||
|
|
f69237e99c | ||
|
|
a8fdbd677e | ||
|
|
5316be7d85 | ||
|
|
ea9d3699b0 | ||
|
|
d0c059a3fd | ||
|
|
3a285b8da5 | ||
|
|
d823df1bbb | ||
|
|
a45c04623e | ||
|
|
0f0cbed455 | ||
|
|
a7a133659a | ||
|
|
3a5a5501b9 | ||
|
|
6b6b1828b9 | ||
|
|
c42c56f40e | ||
|
|
bada47c3e0 | ||
|
|
896bdd4e87 | ||
|
|
5faff628ca | ||
|
|
331f1d1716 | ||
|
|
4c19b0c79b | ||
|
|
bb777d838d | ||
|
|
cfd6c45250 | ||
|
|
04cc0dbd19 | ||
|
|
8e1b937e5f | ||
|
|
51e966f1c1 | ||
|
|
f69570b516 | ||
|
|
fb39488301 | ||
|
|
5a3c14e2d3 | ||
|
|
a203222136 | ||
|
|
f77b3dad07 | ||
|
|
7d622afa70 | ||
|
|
c22826727d | ||
|
|
8beee33f4c | ||
|
|
0f0aeca2d5 | ||
|
|
7e68ab76c8 | ||
|
|
3984aba71e | ||
|
|
81b9be16b3 | ||
|
|
cc636f5606 | ||
|
|
67716d3e77 | ||
|
|
47671c9622 | ||
|
|
5f19acf3dc | ||
|
|
271789ceff | ||
|
|
124c914f35 | ||
|
|
8499ba651d | ||
|
|
4a825adf7d | ||
|
|
e6ff828ac7 | ||
|
|
90031251c1 | ||
|
|
059e6407d3 | ||
|
|
46d37a2d26 | ||
|
|
3b5f764740 | ||
|
|
934d61ba05 | ||
|
|
d609be5e04 | ||
|
|
a9b7af2c28 | ||
|
|
501212e324 | ||
|
|
7158edf5c8 | ||
|
|
fcc96573dd | ||
|
|
79c57f2dab | ||
|
|
ed63bc8a89 | ||
|
|
1de3a27f36 | ||
|
|
e9d47c2c07 | ||
|
|
1ba68a43cd | ||
|
|
e60dbff022 | ||
|
|
b3aad357ab | ||
|
|
4c346cc711 | ||
|
|
ca36b1f452 | ||
|
|
27195b1e7a | ||
|
|
948559c22c | ||
|
|
8e2a07c696 | ||
|
|
9aa9d182c7 | ||
|
|
b4bb6051e9 | ||
|
|
5d7a1ca5a9 | ||
|
|
613443c5eb | ||
|
|
d7884bb96c | ||
|
|
fc25f50e5f | ||
|
|
b70b3a5aa1 | ||
|
|
88ae39ed04 | ||
|
|
d893526f82 | ||
|
|
53bf5b5a21 | ||
|
|
07b2881965 | ||
|
|
ca1b7c2595 | ||
|
|
a0d8eeb768 | ||
|
|
278ce9a272 | ||
|
|
9367b64f2a | ||
|
|
080247cf2f | ||
|
|
95bf9478c7 | ||
|
|
4ae3149d2e | ||
|
|
9bf01636f1 | ||
|
|
e48fe8e307 | ||
|
|
27c12ad698 | ||
|
|
612ddb64e5 | ||
|
|
c6c8421a2a | ||
|
|
f9859d226c | ||
|
|
7c0e60c4f1 | ||
|
|
e06ef010c4 | ||
|
|
3935cd9ed3 | ||
|
|
f781824676 | ||
|
|
5a231d5bf9 | ||
|
|
02302031dd | ||
|
|
b59b350424 | ||
|
|
99b8669d0a | ||
|
|
919fbff2ec | ||
|
|
4977713834 | ||
|
|
1f8bd8cf45 | ||
|
|
8f6bacf33e | ||
|
|
a79a1b55a1 | ||
|
|
fa4d389b91 | ||
|
|
36b884ff7f | ||
|
|
f3a7e60608 | ||
|
|
a8405c5667 | ||
|
|
829354fb65 | ||
|
|
4be7883908 | ||
|
|
2f77b246b3 | ||
|
|
ddf950f5ed | ||
|
|
7341809f67 | ||
|
|
ad98fe5130 | ||
|
|
659c690d5b | ||
|
|
f1dc542b0b | ||
|
|
0c5437c80b | ||
|
|
db9881b854 | ||
|
|
c5a37cad2e | ||
|
|
dd97244289 | ||
|
|
5372fed15c | ||
|
|
37a8909357 | ||
|
|
18f4069d8d | ||
|
|
78c01f1b3f | ||
|
|
f946c969a2 | ||
|
|
2dfa2b7c19 | ||
|
|
8b88dd736a | ||
|
|
b9fd964fec | ||
|
|
9c74a819e4 | ||
|
|
f700453cb1 | ||
|
|
4ea0375575 | ||
|
|
7071894a52 | ||
|
|
0f471cfde7 | ||
|
|
a8cc198e79 | ||
|
|
477b1010fe | ||
|
|
8722bc3693 | ||
|
|
e31e3802a0 | ||
|
|
723d95295e | ||
|
|
91b3b6fdc8 | ||
|
|
34a901430d | ||
|
|
21743e0b7c | ||
|
|
83826f1f34 | ||
|
|
3562ba1235 | ||
|
|
1cc002018f | ||
|
|
352863cab4 | ||
|
|
d69570b62b | ||
|
|
26c44d4869 | ||
|
|
f10fc4489c | ||
|
|
3450ed2282 | ||
|
|
967e985c5f | ||
|
|
d6915b9014 | ||
|
|
304e6d045d | ||
|
|
4dea7f04dc | ||
|
|
7cc35f1195 | ||
|
|
afd0648631 | ||
|
|
6524bfee21 | ||
|
|
de8c2d1762 | ||
|
|
5309640d6c | ||
|
|
ddd016a6f8 | ||
|
|
e76fc5637a | ||
|
|
62110dcf7f | ||
|
|
c597f4de77 | ||
|
|
fe4b55154b | ||
|
|
d9f4319bfd | ||
|
|
ec7baced6c | ||
|
|
5ed591c3d7 | ||
|
|
32859868d6 | ||
|
|
4ab80fceb4 | ||
|
|
4e25cc130f | ||
|
|
f1b56f6512 | ||
|
|
b5d6c427ad | ||
|
|
9a900503ea | ||
|
|
f6296891bf | ||
|
|
bbd16ed644 | ||
|
|
8f6cd1cfcd | ||
|
|
70a3532f49 | ||
|
|
c160edb49b | ||
|
|
32f0e6d1e6 | ||
|
|
8c6098ed9e | ||
|
|
5f70302bfc | ||
|
|
f1cc25409a | ||
|
|
1bb67f87d1 | ||
|
|
6f89be191d | ||
|
|
f453aac0eb | ||
|
|
abc2e56e6a | ||
|
|
2f2c86599f | ||
|
|
a52e68f04d | ||
|
|
ee256eda09 | ||
|
|
adbedd87a3 | ||
|
|
4f94981ad9 | ||
|
|
fb15e11ef3 | ||
|
|
b0e1570535 | ||
|
|
b370042040 | ||
|
|
60daf77274 | ||
|
|
4f9ed77789 | ||
|
|
c819a0b3c8 | ||
|
|
104f493044 | ||
|
|
ccfb3fc5db | ||
|
|
9fa543c9f4 | ||
|
|
1d18178eda | ||
|
|
8a6b1f309b | ||
|
|
a60ab0def7 | ||
|
|
6276177e20 | ||
|
|
6c4e8f1466 | ||
|
|
19a4ae248b | ||
|
|
744212dbf3 | ||
|
|
a46fbd6ead | ||
|
|
bb39100caf | ||
|
|
a27a4e4b60 | ||
|
|
490cf03fba | ||
|
|
e93191409e | ||
|
|
5cfdaf4ad4 | ||
|
|
7664a83451 | ||
|
|
f449e1fd7d | ||
|
|
bbd4a99be2 | ||
|
|
4ae471baf9 | ||
|
|
4d41b5df11 | ||
|
|
01188c1465 | ||
|
|
7eac3090a5 | ||
|
|
e9bb602131 | ||
|
|
ddc8b21705 | ||
|
|
6209d33f4e | ||
|
|
cbaace24c0 | ||
|
|
051951ef00 | ||
|
|
a9062b8710 | ||
|
|
18c656ebba | ||
|
|
9220d05de2 | ||
|
|
fdd286d190 | ||
|
|
4fa8a8ce1d | ||
|
|
d5e853a25b | ||
|
|
6ea3635847 | ||
|
|
0ea3d42a38 | ||
|
|
a024df6124 | ||
|
|
11a80021f4 | ||
|
|
301873c751 | ||
|
|
90986caee8 | ||
|
|
778d93ac49 | ||
|
|
f9ba08fa11 | ||
|
|
95911aa2ce | ||
|
|
ccbea05142 | ||
|
|
867c957ceb | ||
|
|
0d2ff3ee65 | ||
|
|
14122475be | ||
|
|
d881e7ed56 | ||
|
|
9b0054c55f | ||
|
|
baeae97784 | ||
|
|
0324bfa219 | ||
|
|
b279fd025b | ||
|
|
f31546817a | ||
|
|
f272382a9c | ||
|
|
aa8af9d7f8 | ||
|
|
e1f03ae019 | ||
|
|
f316bc6535 | ||
|
|
82e4c66520 | ||
|
|
3c923e0467 | ||
|
|
866985ac88 | ||
|
|
ba772b40ad | ||
|
|
7a86f0e1b7 | ||
|
|
7e9286f2d8 | ||
|
|
b0d1e365ec | ||
|
|
dbd5a2cbf4 | ||
|
|
d60fb1d5c0 | ||
|
|
29c91fcdba | ||
|
|
fe65674f3f | ||
|
|
a3a6b5ed7e | ||
|
|
d2de8658a7 | ||
|
|
be785b4783 | ||
|
|
70e34941c4 | ||
|
|
545d52572d | ||
|
|
dfa0736b49 | ||
|
|
c7bb5664d9 | ||
|
|
6bbc6f3417 | ||
|
|
631169a312 | ||
|
|
427b3312a6 | ||
|
|
2287b0b57a | ||
|
|
56aaba09b1 | ||
|
|
332938fef3 | ||
|
|
13710f7a06 | ||
|
|
530693099b | ||
|
|
f68aa7ea88 | ||
|
|
5bdaee5e40 | ||
|
|
1b465bc2e2 | ||
|
|
e455021bea | ||
|
|
4fe1246bcc | ||
|
|
59b4ac4d5f | ||
|
|
de641fedfe | ||
|
|
cb99fabcc0 | ||
|
|
f996ea12c2 | ||
|
|
4e7fb3e0ea | ||
|
|
e7cbbbef0f | ||
|
|
a181078981 | ||
|
|
987a662b51 | ||
|
|
77e3e14e55 | ||
|
|
98ddf30155 | ||
|
|
83da5c58a2 | ||
|
|
7136ae45a7 | ||
|
|
97d3d61749 | ||
|
|
9c8bf1fe5d | ||
|
|
d1cc6ff3cb | ||
|
|
db76d74820 | ||
|
|
8fdd9d52f9 | ||
|
|
d42797f587 | ||
|
|
6b9c1f6ab8 | ||
|
|
4aedc96db2 | ||
|
|
234830da1e | ||
|
|
244b95968f | ||
|
|
810cd553b5 | ||
|
|
e52eaa961c | ||
|
|
b7827091b8 | ||
|
|
2e344377b5 | ||
|
|
15335ce39d | ||
|
|
14b03e1f8c | ||
|
|
0968b94e5c | ||
|
|
ba6b2258aa | ||
|
|
1e1715ff11 | ||
|
|
c93381fa79 | ||
|
|
65b0a59757 | ||
|
|
c785639043 | ||
|
|
512c259822 | ||
|
|
90fea6f0d4 | ||
|
|
d892468fd0 | ||
|
|
81ab98b329 | ||
|
|
3f1cacd3e6 | ||
|
|
6e41e01a29 | ||
|
|
9ce81488d4 | ||
|
|
25bc2630d9 | ||
|
|
e5b69418a0 | ||
|
|
229a69be5f | ||
|
|
7f2561312a | ||
|
|
93d11a07f9 | ||
|
|
8e5e24c4c6 | ||
|
|
517ee6754c | ||
|
|
47690c287c | ||
|
|
364b95170e | ||
|
|
407006d673 | ||
|
|
2d60605cc6 | ||
|
|
2d1129eb7e | ||
|
|
495b4980e7 | ||
|
|
ad480be112 | ||
|
|
a06001c022 | ||
|
|
6cbda74aeb | ||
|
|
576fbdf490 | ||
|
|
52b00c38ea | ||
|
|
8ec56f459b | ||
|
|
5cbe423461 | ||
|
|
369fbba750 | ||
|
|
8e3c35efc6 | ||
|
|
b3b852b877 | ||
|
|
d5c6e20e9e | ||
|
|
89b36775fb | ||
|
|
df16df5693 | ||
|
|
aa1fd576ac | ||
|
|
2512188ca7 | ||
|
|
89e88ca6a0 | ||
|
|
2697dd162f | ||
|
|
53a4179c4a | ||
|
|
a120731ddd | ||
|
|
bda611a41a | ||
|
|
5a02a4a1a4 | ||
|
|
e59f358b45 | ||
|
|
90c9535b39 | ||
|
|
ad778bbfdb | ||
|
|
8868552b24 | ||
|
|
66826eb487 | ||
|
|
39127b5ee9 | ||
|
|
80cff816a8 | ||
|
|
e9dda8dafa | ||
|
|
11fc818dfb | ||
|
|
84ed844e3f | ||
|
|
50af804f1c | ||
|
|
82ce93bce9 | ||
|
|
228abc8259 | ||
|
|
1692fa5291 | ||
|
|
bb58c6ae38 | ||
|
|
4f58dc27f9 | ||
|
|
fc5324b9f7 | ||
|
|
561f65aabf | ||
|
|
9acfe36b95 | ||
|
|
ce495cbd85 | ||
|
|
c44020f188 | ||
|
|
35e7a695af | ||
|
|
7a55055d22 | ||
|
|
061900b87f | ||
|
|
abe32f7309 | ||
|
|
7bfd4c7fe7 | ||
|
|
859d6a3e2c | ||
|
|
c2f05f99af | ||
|
|
55b33fb02b | ||
|
|
c247e40d41 | ||
|
|
05c58f159d | ||
|
|
67d5fefcf2 | ||
|
|
aad017be9e | ||
|
|
4543dbf3ff | ||
|
|
b1cb7e0a8f | ||
|
|
1c6d21c97d | ||
|
|
fc42fb38c9 | ||
|
|
618068c15b | ||
|
|
808516a0a8 | ||
|
|
7d6b806040 | ||
|
|
6b4e2c9d73 | ||
|
|
787883f6a4 | ||
|
|
7f02753c04 | ||
|
|
8fa38b3445 | ||
|
|
541c5f9f45 | ||
|
|
4b6bd4976a | ||
|
|
a0843d52b4 | ||
|
|
977a8cbed8 | ||
|
|
1e85085305 | ||
|
|
188f61505f | ||
|
|
a514d7f6a4 | ||
|
|
47c2c75ceb | ||
|
|
7082ec8a9a | ||
|
|
2907a84362 | ||
|
|
b8d1ee05c3 | ||
|
|
0b625e624a | ||
|
|
a4da712f91 | ||
|
|
185f124e87 | ||
|
|
8633077691 | ||
|
|
39a1899045 | ||
|
|
4dc011a187 | ||
|
|
ccb12e1dda | ||
|
|
63f0d3e295 | ||
|
|
09d598c496 | ||
|
|
7e60482d80 | ||
|
|
10b3237e52 | ||
|
|
99af7cc028 | ||
|
|
76b360b5d1 | ||
|
|
4001b58a3e | ||
|
|
134a8aac87 | ||
|
|
3e2e48872a | ||
|
|
622b43cbc5 | ||
|
|
75b44ef620 | ||
|
|
00084ea49c | ||
|
|
b6fae24296 | ||
|
|
13c6b7422d | ||
|
|
24f2e663cd | ||
|
|
f96b155988 | ||
|
|
d5cc24e916 | ||
|
|
8eba271c5f | ||
|
|
19955c82b3 | ||
|
|
80ae8aec7c | ||
|
|
d8441d7c81 | ||
|
|
c2446f2478 | ||
|
|
6efb7a1904 | ||
|
|
fa0b03a10f | ||
|
|
3ae2972950 | ||
|
|
2d48de9998 | ||
|
|
3b029cdfe4 | ||
|
|
ea9077a8a5 | ||
|
|
b4af6b6265 | ||
|
|
ae1d53ceb1 | ||
|
|
c3f4709d5e | ||
|
|
a7bb3fc175 | ||
|
|
6a0203f25f | ||
|
|
b0dfcb147f | ||
|
|
06fd4c9357 | ||
|
|
b9ea7fd9ee | ||
|
|
58308a5c7a | ||
|
|
88c7d54975 | ||
|
|
eadb5678b3 | ||
|
|
54e3e60796 | ||
|
|
2a05494abe | ||
|
|
bc89e58def | ||
|
|
3a009b97f6 | ||
|
|
a105f6d5e9 | ||
|
|
5db241c0c0 | ||
|
|
d38dca10e5 | ||
|
|
54fc723126 | ||
|
|
9b8ca17ee4 | ||
|
|
cddc95b39b | ||
|
|
5676529793 | ||
|
|
ce68156cf4 | ||
|
|
5032ad9593 | ||
|
|
b55f765f2e | ||
|
|
856078d435 | ||
|
|
c74c262d2f | ||
|
|
787e29e374 | ||
|
|
e439896c9b | ||
|
|
15b508f1fb | ||
|
|
5651ebfc76 | ||
|
|
593bfc1ce8 | ||
|
|
28953cda7a | ||
|
|
516f3f545d | ||
|
|
83aff65a42 | ||
|
|
5bd97df957 | ||
|
|
8772711181 | ||
|
|
dc9526f4c6 | ||
|
|
9ab36e7ae0 | ||
|
|
e8d30841e7 | ||
|
|
9faf29374e | ||
|
|
75e4818d36 | ||
|
|
a65144e9e0 | ||
|
|
4f29548526 | ||
|
|
528db2cbd8 | ||
|
|
84e6bcc1e6 | ||
|
|
0dc9ff518d | ||
|
|
a2e3bd44aa | ||
|
|
163a0e7caf | ||
|
|
49a86db5f3 | ||
|
|
2640953d8b | ||
|
|
7eb7d67f7a | ||
|
|
e28860ba7f | ||
|
|
49a96b1d91 | ||
|
|
3130812693 | ||
|
|
71ae69aea8 | ||
|
|
9e34d697c7 | ||
|
|
8fa9125d02 | ||
|
|
4b4488c3e4 | ||
|
|
248db7d301 | ||
|
|
e199f2949b | ||
|
|
456b5450a4 | ||
|
|
d8e629c4a3 | ||
|
|
4898c4f297 | ||
|
|
7cd578bbc6 | ||
|
|
1e91df628a | ||
|
|
b110549cbd | ||
|
|
971047835a | ||
|
|
dd4d9eb032 | ||
|
|
8b94f1e480 | ||
|
|
c89610a5c7 | ||
|
|
217963cbed | ||
|
|
c4f4e18340 | ||
|
|
8a7c0e7345 | ||
|
|
52e304d450 | ||
|
|
cfc0eeca53 | ||
|
|
ebd3dd9228 | ||
|
|
cb40e4e7ef | ||
|
|
662dba55fd | ||
|
|
8760ce023c | ||
|
|
b86e148bb7 | ||
|
|
9e83721e5e | ||
|
|
dc2d6c2e60 | ||
|
|
4ada6f6762 |
No files matched your search
@@ -276,6 +276,7 @@ NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
|
||||
MOZ_ASSERT(hideEvent);
|
||||
|
||||
if (hideEvent->NeedsShutdown()) {
|
||||
mDocument->UncacheChildrenInSubtree(aEvent->GetAccessible());
|
||||
mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1307,6 +1307,8 @@ DocAccessible::UnbindFromDocument(Accessible* aAccessible)
|
||||
{
|
||||
NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
|
||||
"Unbinding the unbound accessible!");
|
||||
MOZ_ASSERT(!mARIAOwnsHash.Contains(aAccessible),
|
||||
"Container still lingering in mARIAOwnsHash");
|
||||
|
||||
// Fire focus event on accessible having DOM focus if active item was removed
|
||||
// from the tree.
|
||||
|
||||
@@ -23,7 +23,6 @@ SOURCES += [
|
||||
'DocAccessibleWrap.cpp',
|
||||
'EnumVariant.cpp',
|
||||
'HTMLTableAccessibleWrap.cpp',
|
||||
'HTMLWin32ObjectAccessible.cpp',
|
||||
'HyperTextAccessibleWrap.cpp',
|
||||
'ImageAccessibleWrap.cpp',
|
||||
'IUnknownImpl.cpp',
|
||||
|
||||
@@ -88,6 +88,11 @@ check_and_add_gcc_warning('-Wno-error=deprecated-declarations')
|
||||
# false positives depending on optimization
|
||||
check_and_add_gcc_warning('-Wno-error=array-bounds')
|
||||
|
||||
# GCC 9+ value-range analysis trips -Wformat-overflow on null %s args in debug
|
||||
# spew (e.g. js/src/jit/x64/BaseAssembler-x64.h:595); harmless for our build,
|
||||
# and js/src forces -Werror=format so this must be downgraded here to reach it.
|
||||
check_and_add_gcc_warning('-Wno-error=format-overflow')
|
||||
|
||||
# can't get rid of those PGO warnings
|
||||
check_and_add_gcc_warning('-Wno-error=coverage-mismatch', when='MOZ_PGO')
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -26,6 +26,6 @@
|
||||
global DATA_SYMBOL
|
||||
%endif
|
||||
|
||||
SECTION .rodata align=16
|
||||
SECTION .rodata data readable align=16
|
||||
DATA_SYMBOL:
|
||||
incbin ICU_DATA_FILE
|
||||
+6
@@ -6,7 +6,13 @@
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
#endif
|
||||
.global ICU_DATA_SYMBOL
|
||||
#if defined(__APPLE__)
|
||||
.const
|
||||
#elif defined(__GNUC__) && !defined(XP_SOLARIS)
|
||||
.section .rodata
|
||||
#else
|
||||
.data
|
||||
#endif
|
||||
.balign 16
|
||||
ICU_DATA_SYMBOL:
|
||||
.incbin ICU_DATA_FILE
|
||||
Vendored
+2
@@ -17,6 +17,8 @@ DEFINES.update(
|
||||
UCONFIG_NO_LEGACY_CONVERSION = True,
|
||||
UCONFIG_NO_TRANSLITERATION = True,
|
||||
UCONFIG_NO_REGULAR_EXPRESSIONS = True,
|
||||
UCONFIG_NO_BREAK_ITERATION = True,
|
||||
UCONFIG_NO_MF2 = True,
|
||||
|
||||
# We don't need to pass data to and from legacy char* APIs.
|
||||
U_CHARSET_IS_UTF8 = True,
|
||||
|
||||
@@ -81,7 +81,7 @@ $(addprefix install-,$(INSTALL_MANIFESTS)): install-%: $(addprefix $(TOPOBJDIR)/
|
||||
--track install_$(subst /,_,$*).track \
|
||||
$(TOPOBJDIR)/$* \
|
||||
-DAB_CD=en-US \
|
||||
-DBOOKMARKS_INCLUDE_DIR=$(TOPSRCDIR)/browser/locales/en-US/profile \
|
||||
-DBOOKMARKS_INCLUDE_DIR=$(TOPSRCDIR)/$(MOZ_BUILD_APP)/locales/en-US/profile \
|
||||
$(ACDEFINES) \
|
||||
install_$(subst /,_,$*)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ static RedirEntry kRedirMap[] = {
|
||||
{ "config", "chrome://global/content/config.xul", 0 },
|
||||
{ "console", "chrome://global/content/console.xul", 0 },
|
||||
{
|
||||
"credits", "http://www.palemoon.org/Contributors.shtml",
|
||||
"credits", "https://net.riverstonerelay.jihad-browser/credits",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::MAKE_LINKABLE
|
||||
},
|
||||
|
||||
@@ -199,6 +199,7 @@
|
||||
#include "nsICommandManager.h"
|
||||
#include "nsIDOMNode.h"
|
||||
#include "nsIDocShellTreeOwner.h"
|
||||
#include "nsIBadCertListener2.h" // JIHAD: route cert errors to the embedder chrome
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIIDNService.h"
|
||||
#include "nsIInputStreamChannel.h"
|
||||
@@ -1074,6 +1075,33 @@ nsDocShell::GetInterface(const nsIID& aIID, void** aSink)
|
||||
} else if (aIID.Equals(NS_GET_IID(nsITabChild))) {
|
||||
*aSink = GetTabChild().take();
|
||||
return *aSink ? NS_OK : NS_ERROR_FAILURE;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsIBadCertListener2))) {
|
||||
// JIHAD: hand the embedder's chrome the certificate error.
|
||||
//
|
||||
// NSS asks the channel's notification callbacks for nsIBadCertListener2
|
||||
// (SSLServerCertVerification.cpp, CertErrorRunnable::CheckCertOverrides) and passes
|
||||
// it the nsISSLStatus carrying the SERVER CERTIFICATE. That call is the one and only
|
||||
// place an embedder is handed the cert while it still exists — the same shape the
|
||||
// Atlas browser gets for free from WebKit's load-failed-with-tls-errors signal, which
|
||||
// delivers the GTlsCertificate as a signal argument.
|
||||
//
|
||||
// Without this hop nothing answers: the docShell's fallthrough is
|
||||
// nsDocLoader::GetInterface, which Queries the docloader ITSELF, so the request never
|
||||
// reaches nsIWebBrowserChrome. The embedder is then left trying to recover the cert
|
||||
// afterwards, and there is no way to: the failed channel's securityInfo has a null
|
||||
// SSLStatus, and UXP has no nsIRecentBadCerts service. Measured 2026-08-04 — the
|
||||
// SSL-confirm dialog worked, the user accepted, and the accept could do nothing
|
||||
// because RememberValidityOverride had no nsIX509Cert to take.
|
||||
//
|
||||
// Routed through the tree owner, exactly as nsIWebBrowserChromeFocus and nsIPrompt
|
||||
// already are (nsDocShellTreeOwner::GetInterface).
|
||||
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
|
||||
GetTreeOwner(getter_AddRefs(treeOwner));
|
||||
nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(treeOwner));
|
||||
if (req) {
|
||||
return req->GetInterface(aIID, aSink);
|
||||
}
|
||||
return NS_NOINTERFACE;
|
||||
} else if (aIID.Equals(NS_GET_IID(nsIContentFrameMessageManager))) {
|
||||
nsCOMPtr<nsITabChild> tabChild =
|
||||
do_GetInterface(static_cast<nsIDocShell*>(this));
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
void
|
||||
Unfollow();
|
||||
|
||||
/* Should this be WeakRef? */
|
||||
RefPtr<AbortSignal> mFollowingSignal;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,19 @@
|
||||
|
||||
#include "DocumentOrShadowRoot.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/CSSStyleSheet.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/CSSStyleSheetBinding.h"
|
||||
#include "mozilla/dom/StyleSheetList.h"
|
||||
#include "mozilla/dom/ToJSValue.h"
|
||||
#include "nsDocument.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "ShadowRoot.h"
|
||||
#include "XULDocument.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsSVGUtils.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
class nsINode;
|
||||
class nsIDocument;
|
||||
@@ -43,6 +49,99 @@ DocumentOrShadowRoot::EnsureDOMStyleSheets()
|
||||
return *mDOMStyleSheets;
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::GetAdoptedStyleSheets(
|
||||
JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aResult,
|
||||
ErrorResult& aRv) const
|
||||
{
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
if (!ToJSValue(aCx, mAdoptedStyleSheets, &value)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(value.isObject());
|
||||
aResult.set(&value.toObject());
|
||||
}
|
||||
|
||||
void
|
||||
DocumentOrShadowRoot::SetAdoptedStyleSheets(
|
||||
JSContext* aCx,
|
||||
JS::Handle<JSObject*> aSheets,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
bool isArray = false;
|
||||
if (!JS_IsArrayObject(aCx, aSheets, &isArray)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isArray) {
|
||||
aRv.ThrowTypeError<MSG_NOT_SEQUENCE>(
|
||||
NS_LITERAL_STRING("DocumentOrShadowRoot.adoptedStyleSheets"));
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t length = 0;
|
||||
if (!JS_GetArrayLength(aCx, aSheets, &length)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* document = AsNode().OwnerDoc();
|
||||
nsTArray<RefPtr<CSSStyleSheet>> newSheets;
|
||||
if (!newSheets.SetCapacity(length, fallible)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(aCx);
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
if (!JS_GetElement(aCx, aSheets, i, &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value.isObject()) {
|
||||
aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(
|
||||
NS_LITERAL_STRING("Element of DocumentOrShadowRoot.adoptedStyleSheets"),
|
||||
NS_LITERAL_STRING("CSSStyleSheet"));
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<CSSStyleSheet> sheet;
|
||||
nsresult rv = UnwrapObject<prototypes::id::CSSStyleSheet,
|
||||
CSSStyleSheet>(&value.toObject(), sheet);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(
|
||||
NS_LITERAL_STRING("Element of DocumentOrShadowRoot.adoptedStyleSheets"),
|
||||
NS_LITERAL_STRING("CSSStyleSheet"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sheet->IsConstructed() || sheet->ConstructorDocument() != document) {
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
newSheets.AppendElement(sheet);
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<CSSStyleSheet>> oldSheets;
|
||||
oldSheets.SwapElements(mAdoptedStyleSheets);
|
||||
mAdoptedStyleSheets.SwapElements(newSheets);
|
||||
|
||||
if (mKind == Kind::Document) {
|
||||
static_cast<nsDocument&>(AsNode()).AdoptedStyleSheetsChanged(oldSheets,
|
||||
mAdoptedStyleSheets);
|
||||
return;
|
||||
}
|
||||
|
||||
static_cast<ShadowRoot&>(AsNode()).AdoptedStyleSheetsChanged(oldSheets,
|
||||
mAdoptedStyleSheets);
|
||||
}
|
||||
|
||||
Element*
|
||||
DocumentOrShadowRoot::GetElementById(const nsAString& aElementId)
|
||||
{
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
#include "nsContentListDeclarations.h"
|
||||
#include "nsNameSpaceManager.h"
|
||||
#include "mozilla/dom/NameSpaceConstants.h"
|
||||
#include "js/RootingAPI.h"
|
||||
|
||||
class nsContentList;
|
||||
class nsINode;
|
||||
|
||||
namespace mozilla {
|
||||
class CSSStyleSheet;
|
||||
class ErrorResult;
|
||||
class StyleSheet;
|
||||
|
||||
namespace dom {
|
||||
@@ -83,6 +86,18 @@ public:
|
||||
|
||||
StyleSheetList& EnsureDOMStyleSheets();
|
||||
|
||||
void GetAdoptedStyleSheets(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aResult,
|
||||
mozilla::ErrorResult& aRv) const;
|
||||
void SetAdoptedStyleSheets(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aSheets,
|
||||
mozilla::ErrorResult& aRv);
|
||||
|
||||
const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& AdoptedStyleSheets() const
|
||||
{
|
||||
return mAdoptedStyleSheets;
|
||||
}
|
||||
|
||||
Element* GetElementById(const nsAString& aElementId);
|
||||
|
||||
/**
|
||||
@@ -151,6 +166,7 @@ protected:
|
||||
Element* GetRetargetedFocusedElement();
|
||||
|
||||
nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheets;
|
||||
nsTArray<RefPtr<mozilla::CSSStyleSheet>> mAdoptedStyleSheets;
|
||||
RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
|
||||
|
||||
/*
|
||||
|
||||
+53
-4
@@ -18,6 +18,7 @@
|
||||
#include "mozilla/dom/StyleSheetList.h"
|
||||
#include "nsXBLPrototypeBinding.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/CSSStyleSheet.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/StyleSheet.h"
|
||||
#include "mozilla/StyleSheetInlines.h"
|
||||
@@ -30,6 +31,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot,
|
||||
DocumentFragment)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAssociatedBinding)
|
||||
for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done();
|
||||
iter.Next()) {
|
||||
@@ -42,6 +44,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
|
||||
tmp->GetHost()->RemoveMutationObserver(tmp);
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
|
||||
for (CSSStyleSheet* sheet : tmp->mAdoptedStyleSheets) {
|
||||
sheet->RemoveAdopter(tmp);
|
||||
}
|
||||
tmp->mAdoptedStyleSheets.Clear();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAssociatedBinding)
|
||||
tmp->mIdentifierMap.Clear();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)
|
||||
@@ -90,6 +96,11 @@ ShadowRoot::~ShadowRoot()
|
||||
host->RemoveMutationObserver(this);
|
||||
}
|
||||
|
||||
for (CSSStyleSheet* sheet : mAdoptedStyleSheets) {
|
||||
sheet->RemoveAdopter(this);
|
||||
}
|
||||
mAdoptedStyleSheets.Clear();
|
||||
|
||||
UnsetFlags(NODE_IS_IN_SHADOW_TREE);
|
||||
|
||||
// nsINode destructor expects mSubtreeRoot == this.
|
||||
@@ -270,15 +281,20 @@ ShadowRoot::InsertSheet(StyleSheet* aSheet,
|
||||
|
||||
linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(mProtoBinding->SheetCount() == DocumentOrShadowRoot::SheetCount());
|
||||
MOZ_DIAGNOSTIC_ASSERT(mProtoBinding->SheetCount() ==
|
||||
DocumentOrShadowRoot::SheetCount() +
|
||||
mAdoptedStyleSheets.Length());
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
// FIXME(emilio, bug 1425759): For now we keep them duplicated, the proto
|
||||
// binding will disappear soon (tm).
|
||||
// FIXME(emilio, bug 1425759): For now we keep regular sheets duplicated;
|
||||
// adopted sheets are stored as a suffix on the proto binding only.
|
||||
{
|
||||
size_t i = 0;
|
||||
for (RefPtr<StyleSheet>& sheet : mStyleSheets) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(sheet.get() == mProtoBinding->StyleSheetAt(i++));
|
||||
}
|
||||
for (RefPtr<CSSStyleSheet>& sheet : mAdoptedStyleSheets) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(sheet.get() == mProtoBinding->StyleSheetAt(i++));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -287,7 +303,7 @@ ShadowRoot::InsertSheet(StyleSheet* aSheet,
|
||||
for (size_t i = 0; i <= SheetCount(); i++) {
|
||||
if (i == SheetCount()) {
|
||||
AppendStyleSheet(*aSheet);
|
||||
mProtoBinding->AppendStyleSheet(aSheet);
|
||||
mProtoBinding->InsertStyleSheetAt(i, aSheet);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -322,6 +338,39 @@ ShadowRoot::RemoveSheet(StyleSheet* aSheet)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ShadowRoot::AdoptedStyleSheetsChanged(
|
||||
const nsTArray<RefPtr<CSSStyleSheet>>& aOldSheets,
|
||||
const nsTArray<RefPtr<CSSStyleSheet>>& aNewSheets)
|
||||
{
|
||||
OwnerDoc()->BeginUpdate(UPDATE_STYLE);
|
||||
|
||||
bool applicableChange = false;
|
||||
for (size_t i = 0; i < aOldSheets.Length(); ++i) {
|
||||
CSSStyleSheet* sheet = aOldSheets[i];
|
||||
if (aOldSheets.IndexOf(sheet) == i) {
|
||||
mProtoBinding->RemoveStyleSheet(sheet);
|
||||
}
|
||||
applicableChange = applicableChange || sheet->IsApplicable();
|
||||
sheet->RemoveAdopter(this);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < aNewSheets.Length(); ++i) {
|
||||
CSSStyleSheet* sheet = aNewSheets[i];
|
||||
sheet->AddAdopter(this);
|
||||
if (aNewSheets.IndexOf(sheet) == i) {
|
||||
mProtoBinding->AppendStyleSheet(sheet);
|
||||
}
|
||||
applicableChange = applicableChange || sheet->IsApplicable();
|
||||
}
|
||||
|
||||
if (applicableChange) {
|
||||
StyleSheetChanged();
|
||||
}
|
||||
|
||||
OwnerDoc()->EndUpdate(UPDATE_STYLE);
|
||||
}
|
||||
|
||||
void
|
||||
ShadowRoot::AddToIdTable(Element* aElement, nsIAtom* aId)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ class nsXBLPrototypeBinding;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class CSSStyleSheet;
|
||||
class EventChainPreVisitor;
|
||||
|
||||
namespace dom {
|
||||
@@ -59,6 +60,9 @@ public:
|
||||
// [deprecated] Shadow DOM v0
|
||||
void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent);
|
||||
void RemoveSheet(StyleSheet* aSheet);
|
||||
void AdoptedStyleSheetsChanged(
|
||||
const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aOldSheets,
|
||||
const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aNewSheets);
|
||||
StyleSheetList* StyleSheets()
|
||||
{
|
||||
return &DocumentOrShadowRoot::EnsureDOMStyleSheets();
|
||||
@@ -173,4 +177,3 @@ protected:
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_shadowroot_h__
|
||||
|
||||
@@ -40,12 +40,14 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Timeout)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptHandler)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Timeout)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
@@ -25,6 +25,22 @@ VisualViewport::~VisualViewport()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(VisualViewport)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VisualViewport,
|
||||
DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VisualViewport,
|
||||
DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VisualViewport)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(VisualViewport, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(VisualViewport, DOMEventTargetHelper)
|
||||
|
||||
/* virtual */
|
||||
JSObject*
|
||||
VisualViewport::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
|
||||
@@ -21,6 +21,9 @@ class VisualViewport final: public mozilla::DOMEventTargetHelper
|
||||
{
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VisualViewport, DOMEventTargetHelper)
|
||||
|
||||
explicit VisualViewport(nsPIDOMWindowInner* aWindow);
|
||||
explicit VisualViewport(nsIGlobalObject* aGlobal);
|
||||
|
||||
|
||||
@@ -695,7 +695,7 @@ nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
|
||||
!MayContainRelevantNodes(aContainer) ||
|
||||
(!aFirstNewContent->HasChildren() &&
|
||||
!aFirstNewContent->GetNextSibling() &&
|
||||
!MatchSelf(aFirstNewContent))) {
|
||||
!MatchSelf<MatchSelfMode::Insertion>(aFirstNewContent))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -731,7 +731,7 @@ nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
|
||||
// The new stuff is somewhere in the middle of our list; check
|
||||
// whether we need to invalidate
|
||||
for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
|
||||
if (MatchSelf(cur)) {
|
||||
if (MatchSelf<MatchSelfMode::Insertion>(cur)) {
|
||||
// Uh-oh. We're gonna have to add elements into the middle
|
||||
// of our list. That's not worth the effort.
|
||||
SetDirty();
|
||||
@@ -788,7 +788,7 @@ nsContentList::ContentInserted(nsIDocument *aDocument,
|
||||
if (mState != LIST_DIRTY &&
|
||||
MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
|
||||
nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
|
||||
MatchSelf(aChild)) {
|
||||
MatchSelf<MatchSelfMode::Insertion>(aChild)) {
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
@@ -808,7 +808,7 @@ nsContentList::ContentRemoved(nsIDocument *aDocument,
|
||||
if (mState != LIST_DIRTY &&
|
||||
MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
|
||||
nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
|
||||
MatchSelf(aChild)) {
|
||||
MatchSelf<MatchSelfMode::Removal>(aChild)) {
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
@@ -853,6 +853,7 @@ nsContentList::Match(Element *aElement)
|
||||
ni->Equals(mXMLMatchAtom, mMatchNameSpaceId);
|
||||
}
|
||||
|
||||
template <nsContentList::MatchSelfMode Mode>
|
||||
bool
|
||||
nsContentList::MatchSelf(nsIContent *aContent)
|
||||
{
|
||||
@@ -864,8 +865,20 @@ nsContentList::MatchSelf(nsIContent *aContent)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Match(aContent->AsElement()))
|
||||
auto matches = [&](Element* aElement) {
|
||||
if (Match(aElement)) {
|
||||
return true;
|
||||
}
|
||||
if constexpr (Mode == MatchSelfMode::Removal) {
|
||||
return mFunc == nsContentUtils::MatchClassNames &&
|
||||
aElement->IsSVGElement() && aElement->MayHaveClass();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (matches(aContent->AsElement())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mDeep)
|
||||
return false;
|
||||
@@ -873,7 +886,7 @@ nsContentList::MatchSelf(nsIContent *aContent)
|
||||
for (nsIContent* cur = aContent->GetFirstChild();
|
||||
cur;
|
||||
cur = cur->GetNextNode(aContent)) {
|
||||
if (cur->IsElement() && Match(cur->AsElement())) {
|
||||
if (cur->IsElement() && matches(cur->AsElement())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +367,15 @@ protected:
|
||||
* @return whether we match
|
||||
*/
|
||||
bool Match(mozilla::dom::Element *aElement);
|
||||
|
||||
enum class MatchSelfMode : uint8_t {
|
||||
Insertion,
|
||||
// Also accept SVG elements that may have a class. SMIL can change their
|
||||
// class without notification, so they can be in the list without
|
||||
// matching now. Needed when aContent leaves the subtree.
|
||||
Removal,
|
||||
};
|
||||
|
||||
/**
|
||||
* See if anything in the subtree rooted at aContent, including
|
||||
* aContent itself, matches our criterion.
|
||||
@@ -374,6 +383,7 @@ protected:
|
||||
* @param aContent the root of the subtree to match against
|
||||
* @return whether we match something in the tree rooted at aContent
|
||||
*/
|
||||
template <MatchSelfMode Mode>
|
||||
bool MatchSelf(nsIContent *aContent);
|
||||
|
||||
/**
|
||||
|
||||
@@ -2073,6 +2073,10 @@ public:
|
||||
aClasses);
|
||||
}
|
||||
|
||||
static bool MatchClassNames(mozilla::dom::Element* aElement,
|
||||
int32_t aNamespaceID,
|
||||
nsIAtom* aAtom, void* aData);
|
||||
|
||||
/**
|
||||
* Returns a presshell for this document, if there is one. This will be
|
||||
* aDoc's direct presshell if there is one, otherwise we'll look at all
|
||||
@@ -3012,9 +3016,6 @@ private:
|
||||
|
||||
static void DropFragmentParsers();
|
||||
|
||||
static bool MatchClassNames(mozilla::dom::Element* aElement,
|
||||
int32_t aNamespaceID,
|
||||
nsIAtom* aAtom, void* aData);
|
||||
static void DestroyClassNameArray(void* aData);
|
||||
static void* AllocClassMatchingInfo(nsINode* aRootNode,
|
||||
const nsString* aClasses);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/BinarySearch.h"
|
||||
#include "mozilla/CSSStyleSheet.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/EffectSet.h"
|
||||
#include "mozilla/IntegerRange.h"
|
||||
@@ -1402,6 +1403,10 @@ nsDocument::~nsDocument()
|
||||
for (StyleSheet* sheet : mStyleSheets) {
|
||||
sheet->ClearAssociatedDocument();
|
||||
}
|
||||
for (CSSStyleSheet* sheet : mAdoptedStyleSheets) {
|
||||
sheet->RemoveAdopter(this);
|
||||
}
|
||||
mAdoptedStyleSheets.Clear();
|
||||
for (auto& sheets : mAdditionalSheets) {
|
||||
for (StyleSheet* sheet : sheets) {
|
||||
sheet->ClearAssociatedDocument();
|
||||
@@ -1635,6 +1640,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
|
||||
|
||||
// Traverse all our nsCOMArrays.
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
|
||||
|
||||
@@ -1740,6 +1746,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
|
||||
|
||||
for (CSSStyleSheet* sheet : tmp->mAdoptedStyleSheets) {
|
||||
sheet->RemoveAdopter(tmp);
|
||||
}
|
||||
tmp->mAdoptedStyleSheets.Clear();
|
||||
|
||||
if (tmp->mStyleSheetSetList) {
|
||||
tmp->mStyleSheetSetList->Disconnect();
|
||||
tmp->mStyleSheetSetList = nullptr;
|
||||
@@ -2205,6 +2216,12 @@ nsDocument::FillStyleSet(nsStyleSet* aStyleSet)
|
||||
AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
|
||||
SheetType::Doc);
|
||||
|
||||
for (CSSStyleSheet* sheet : mAdoptedStyleSheets) {
|
||||
if (sheet->IsApplicable()) {
|
||||
aStyleSet->AppendStyleSheet(SheetType::Doc, sheet);
|
||||
}
|
||||
}
|
||||
|
||||
mStyleSetFilled = true;
|
||||
}
|
||||
|
||||
@@ -3872,6 +3889,37 @@ nsDocument::RemoveStyleSheetFromStyleSets(StyleSheet* aSheet)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::AdoptedStyleSheetsChanged(
|
||||
const nsTArray<RefPtr<CSSStyleSheet>>& aOldSheets,
|
||||
const nsTArray<RefPtr<CSSStyleSheet>>& aNewSheets)
|
||||
{
|
||||
BeginUpdate(UPDATE_STYLE);
|
||||
|
||||
nsCOMPtr<nsIPresShell> shell = GetShell();
|
||||
nsStyleSet* styleSet = shell ? shell->StyleSet() : nullptr;
|
||||
|
||||
for (size_t i = 0; i < aOldSheets.Length(); ++i) {
|
||||
CSSStyleSheet* sheet = aOldSheets[i];
|
||||
if (styleSet && sheet->IsApplicable() &&
|
||||
aOldSheets.IndexOf(sheet) == i) {
|
||||
styleSet->RemoveStyleSheet(SheetType::Doc, sheet);
|
||||
}
|
||||
sheet->RemoveAdopter(this);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < aNewSheets.Length(); ++i) {
|
||||
CSSStyleSheet* sheet = aNewSheets[i];
|
||||
sheet->AddAdopter(this);
|
||||
if (styleSet && sheet->IsApplicable() &&
|
||||
aNewSheets.IndexOf(sheet) == i) {
|
||||
styleSet->AppendStyleSheet(SheetType::Doc, sheet);
|
||||
}
|
||||
}
|
||||
|
||||
EndUpdate(UPDATE_STYLE);
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::RemoveStyleSheet(StyleSheet* aSheet)
|
||||
{
|
||||
@@ -3961,6 +4009,18 @@ nsDocument::SetStyleSheetApplicableState(StyleSheet* aSheet,
|
||||
} else {
|
||||
RemoveStyleSheetFromStyleSets(aSheet);
|
||||
}
|
||||
} else if (mAdoptedStyleSheets.IndexOf(aSheet->AsConcrete()) !=
|
||||
mAdoptedStyleSheets.NoIndex) {
|
||||
nsCOMPtr<nsIPresShell> shell = GetShell();
|
||||
if (shell) {
|
||||
if (aApplicable) {
|
||||
shell->StyleSet()->AppendStyleSheet(SheetType::Doc,
|
||||
aSheet->AsConcrete());
|
||||
} else {
|
||||
shell->StyleSet()->RemoveStyleSheet(SheetType::Doc,
|
||||
aSheet->AsConcrete());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have to always notify, since this will be called for sheets
|
||||
|
||||
@@ -95,6 +95,7 @@ class nsISecurityConsoleMessage;
|
||||
class nsPIBoxObject;
|
||||
|
||||
namespace mozilla {
|
||||
class CSSStyleSheet;
|
||||
class EventChainPreVisitor;
|
||||
namespace dom {
|
||||
class BoxObject;
|
||||
@@ -471,6 +472,9 @@ public:
|
||||
nsTArray<RefPtr<mozilla::StyleSheet>>& aNewSheets) override;
|
||||
virtual void AddStyleSheetToStyleSets(mozilla::StyleSheet* aSheet);
|
||||
virtual void RemoveStyleSheetFromStyleSets(mozilla::StyleSheet* aSheet);
|
||||
void AdoptedStyleSheetsChanged(
|
||||
const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aOldSheets,
|
||||
const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aNewSheets);
|
||||
|
||||
virtual void InsertStyleSheetAt(mozilla::StyleSheet* aSheet,
|
||||
size_t aIndex) override;
|
||||
|
||||
@@ -1738,8 +1738,9 @@ nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear,
|
||||
if (mActiveWindow)
|
||||
window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
|
||||
SendFocusOrBlurEvent(eBlur, presShell,
|
||||
content->GetComposedDoc(), content, 1,
|
||||
doc, content, 1,
|
||||
false, false, aContentToFocus);
|
||||
}
|
||||
|
||||
|
||||
@@ -444,6 +444,7 @@ GK_ATOM(handheldFriendly, "HandheldFriendly")
|
||||
GK_ATOM(handler, "handler")
|
||||
GK_ATOM(handlers, "handlers")
|
||||
GK_ATOM(HARD, "HARD")
|
||||
GK_ATOM(hasSelectorDependency, "has-selector-dependency")
|
||||
GK_ATOM(hasSameNode, "has-same-node")
|
||||
GK_ATOM(hbox, "hbox")
|
||||
GK_ATOM(head, "head")
|
||||
|
||||
@@ -2176,6 +2176,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualViewport)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
|
||||
@@ -2254,6 +2255,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualViewport)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
|
||||
if (tmp->mApplicationCache) {
|
||||
@@ -6162,7 +6164,6 @@ nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, b
|
||||
// This one is easy. Just ensure the variable is greater than 100;
|
||||
if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
|
||||
// Check security state for use in determing window dimensions
|
||||
|
||||
if (!nsContentUtils::IsCallerChrome()) {
|
||||
//sec check failed
|
||||
if (aWidth && *aWidth < 100) {
|
||||
@@ -6173,6 +6174,21 @@ nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the screen dimensions and keep the size in the range of the
|
||||
// screen size the window is on. We allow twice the screen size because the
|
||||
// size we get for a screen is not always accurate, on Wayland in particular,
|
||||
// and all we need is to keep the size in a reasonable range.
|
||||
nsCOMPtr<nsIDOMScreen> screen = GetScreen();
|
||||
int32_t screenWidth, screenHeight;
|
||||
screen->GetAvailWidth(&screenWidth);
|
||||
screen->GetAvailHeight(&screenHeight);
|
||||
if (aWidth && *aWidth > 2 * screenWidth) {
|
||||
*aWidth = 2 * screenWidth;
|
||||
}
|
||||
if (aHeight && *aHeight > 2 * screenHeight) {
|
||||
*aHeight = 2 * screenHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Arguments to this function should have values in device pixels
|
||||
@@ -8091,8 +8107,10 @@ nsGlobalWindow::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
|
||||
|
||||
nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
|
||||
|
||||
cssSize.width += aWidthDif;
|
||||
cssSize.height += aHeightDif;
|
||||
// The deltas come from content and can be large enough to overflow a 32-bit
|
||||
// add, so do the arithmetic in 64 bits and keep the result in range.
|
||||
cssSize.width = int32_t(std::clamp<int64_t>(int64_t(cssSize.width) + aWidthDif, 0, INT32_MAX));
|
||||
cssSize.height = int32_t(std::clamp<int64_t>(int64_t(cssSize.height) + aHeightDif, 0, INT32_MAX));
|
||||
|
||||
CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
|
||||
|
||||
|
||||
@@ -46,6 +46,9 @@ nsIDocument::FindDocStyleSheetInsertionPoint(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sheet->IsConstructed()) {
|
||||
break;
|
||||
}
|
||||
if (sheet == GetFirstAdditionalAuthorSheet()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -787,6 +787,12 @@ nsresult
|
||||
nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
// JIHAD DIAGNOSTIC (R7): entry + the four early-out conditions. If this line never appears the
|
||||
// plugin was never asked for at all (no nsPluginFrame -> no HasNewFrame), which is a different
|
||||
// bug from being asked for and refused.
|
||||
fprintf(stderr, "[jihad-npapi] InstantiatePluginInstance(aIsLoading=%d) owner=%d type=%d "
|
||||
"mIsLoading=%d instantiating=%d\n", (int)aIsLoading, (int)!!mInstanceOwner,
|
||||
(int)mType, (int)mIsLoading, (int)mInstantiating);
|
||||
if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) ||
|
||||
mInstantiating) {
|
||||
// If we hit this assertion it's probably because LoadObject re-entered :(
|
||||
@@ -822,10 +828,12 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
NS_ENSURE_TRUE(mInstantiating, NS_OK);
|
||||
|
||||
if (!thisContent->GetPrimaryFrame()) {
|
||||
// JIHAD DIAGNOSTIC (R7): a silent NS_OK return — the single most likely place for a plugin
|
||||
// to "do nothing" with no error anywhere.
|
||||
fprintf(stderr, "[jihad-npapi] NO PRIMARY FRAME -> not instantiating\n");
|
||||
LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
|
||||
|
||||
@@ -846,6 +854,8 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
|
||||
rv = pluginHost->InstantiatePluginInstance(mContentType,
|
||||
mURI.get(), this,
|
||||
getter_AddRefs(newOwner));
|
||||
fprintf(stderr, "[jihad-npapi] pluginHost->InstantiatePluginInstance rv=0x%x owner=%d\n",
|
||||
(unsigned)rv, (int)!!newOwner);
|
||||
|
||||
// XXX(johns): We don't suspend native inside stopping plugins...
|
||||
if (appShell) {
|
||||
@@ -2378,6 +2388,11 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
|
||||
FallbackType clickToPlayReason;
|
||||
if (!mActivated && (mType == eType_Null || mType == eType_Plugin) &&
|
||||
!ShouldPlay(clickToPlayReason, false)) {
|
||||
// JIHAD DIAGNOSTIC (R7): this is where a plugin that resolved fine is silently demoted to
|
||||
// eType_Null. Every path out of ShouldPlay that returns false lands here, so one line here
|
||||
// names the reason instead of instrumenting each exit.
|
||||
fprintf(stderr, "[jihad-npapi] demoted to fallback: mType=%d fallbackReason=%d\n",
|
||||
(int)mType, (int)clickToPlayReason);
|
||||
LOG(("OBJLC [%p]: Marking plugin as click-to-play", this));
|
||||
mType = eType_Null;
|
||||
fallbackType = clickToPlayReason;
|
||||
@@ -2887,9 +2902,16 @@ nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
|
||||
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
|
||||
if ((caps & eSupportPlugins) &&
|
||||
pluginHost &&
|
||||
pluginHost->HavePluginForType(aMIMEType, nsPluginHost::eExcludeNone)) {
|
||||
bool jihadHave = pluginHost &&
|
||||
pluginHost->HavePluginForType(aMIMEType, nsPluginHost::eExcludeNone);
|
||||
// JIHAD DIAGNOSTIC (R7 windowless port): says, for one MIME type, whether the element
|
||||
// advertised plugin capability and whether the host has a plugin — the two facts that decide
|
||||
// if the NPAPI subsystem is entered at all. Added because a plugin that never instantiates
|
||||
// produces NO plugin logging whatsoever, which is indistinguishable from one that was never
|
||||
// asked for.
|
||||
fprintf(stderr, "[jihad-npapi] GetTypeOfContent mime=%s caps=0x%x supportsPlugins=%d have=%d\n",
|
||||
nsCString(aMIMEType).get(), caps, (int)!!(caps & eSupportPlugins), (int)jihadHave);
|
||||
if ((caps & eSupportPlugins) && jihadHave) {
|
||||
// ShouldPlay will handle checking for disabled plugins
|
||||
return eType_Plugin;
|
||||
}
|
||||
@@ -3486,9 +3508,15 @@ nsObjectLoadingContent::ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentTyp
|
||||
aReason = eFallbackClickToPlay;
|
||||
|
||||
uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
|
||||
pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
|
||||
nsresult jihadStateRv = pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
|
||||
&enabledState);
|
||||
// JIHAD DIAGNOSTIC (R7): note that enabledState is pre-set to STATE_DISABLED and the rv of
|
||||
// GetStateForType is NOT checked upstream, so a failing lookup silently blocks the plugin
|
||||
// with no other trace. Log the rv as well as the value.
|
||||
fprintf(stderr, "[jihad-npapi] ShouldPlay type=%s enabledState=%u rv=0x%x\n",
|
||||
mContentType.get(), enabledState, (unsigned)jihadStateRv);
|
||||
if (nsIPluginTag::STATE_DISABLED == enabledState) {
|
||||
fprintf(stderr, "[jihad-npapi] ShouldPlay -> FALSE (eFallbackDisabled)\n");
|
||||
aReason = eFallbackDisabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
#include "mozilla/dom/WebGLRenderingContextBinding.h"
|
||||
|
||||
|
||||
extern "C" bool jihad_gpu_compositor_on(); // Jihad T-511: widget/PuppetWidget.cpp
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::dom;
|
||||
@@ -712,7 +714,11 @@ WebGLContext::CreateAndInitGL(bool forceEnabled,
|
||||
|
||||
//////
|
||||
|
||||
const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
|
||||
// Jihad (T-511): on the GPU path (JIHAD_GPU_COMPOSITOR) the headless build's default GL provider
|
||||
// is the null one; the device's GPU is reached through EGL, as the compositor reaches it. On the
|
||||
// software path, and once the daemon has given the GPU path up, the default provider fails and
|
||||
// WebGL stays off.
|
||||
const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL") || jihad_gpu_compositor_on();
|
||||
|
||||
bool tryNativeGL = true;
|
||||
bool tryANGLE = false;
|
||||
|
||||
@@ -508,7 +508,18 @@ WebGLContext::InitAndValidateGL(FailureReason* const out_failReason)
|
||||
gl->GetUIntegerv(LOCAL_GL_STENCIL_VALUE_MASK, &mStencilValueMaskFront);
|
||||
gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_VALUE_MASK, &mStencilValueMaskBack);
|
||||
gl->GetUIntegerv(LOCAL_GL_STENCIL_WRITEMASK, &mStencilWriteMaskFront);
|
||||
gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_WRITEMASK, &mStencilWriteMaskBack);
|
||||
// Jihad (T-511): the Adreno 220's GLES 2.0 driver answers GL_INVALID_ENUM for the back write
|
||||
// mask (valid in GLES 2.0), and the error left behind failed the whole context's init below
|
||||
// ("GL error 0x500 occurred during WebGL context initialization"). Queried in its own error
|
||||
// scope; the front mask stands in (both start as all ones).
|
||||
{
|
||||
GLint backWriteMask = 0;
|
||||
if (gl->GetPotentialInteger(LOCAL_GL_STENCIL_BACK_WRITEMASK, &backWriteMask)) {
|
||||
mStencilWriteMaskBack = GLuint(backWriteMask);
|
||||
} else {
|
||||
mStencilWriteMaskBack = mStencilWriteMaskFront;
|
||||
}
|
||||
}
|
||||
|
||||
AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_VALUE_MASK, mStencilValueMaskFront);
|
||||
AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_VALUE_MASK, mStencilValueMaskBack);
|
||||
|
||||
@@ -682,16 +682,19 @@ DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
|
||||
return NS_ERROR_DOM_INDEX_SIZE_ERR;
|
||||
}
|
||||
|
||||
nsAutoString format;
|
||||
GetRealFormat(aFormat, format);
|
||||
|
||||
// Don't allow the custom type to be assigned.
|
||||
if (aFormat.EqualsLiteral(kCustomTypesMime)) {
|
||||
if (format.EqualsLiteral(kCustomTypesMime)) {
|
||||
return NS_ERROR_TYPE_ERR;
|
||||
}
|
||||
|
||||
if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
|
||||
if (!PrincipalMaySetData(format, aData, aSubjectPrincipal)) {
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
|
||||
return SetDataWithPrincipal(format, aData, aIndex, aSubjectPrincipal);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -29,18 +29,19 @@ using namespace workers;
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Request)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Request)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Request)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Request)
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
|
||||
|
||||
tmp->mReadableStreamBody = nullptr;
|
||||
@@ -47,6 +47,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Response)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
|
||||
@@ -1132,6 +1132,7 @@ void HTMLMediaElement::AbortExistingLoads()
|
||||
|
||||
RemoveMediaElementFromURITable();
|
||||
mLoadingSrc = nullptr;
|
||||
mCORSMode = CORS_NONE;
|
||||
mMediaSource = nullptr;
|
||||
|
||||
if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
|
||||
@@ -1370,6 +1371,9 @@ void HTMLMediaElement::SelectResource()
|
||||
// If we have a 'src' attribute, use that exclusively.
|
||||
nsAutoString src;
|
||||
if (mSrcAttrStream) {
|
||||
// Media provider objects use local mode, so a previous URL load's CORS
|
||||
// mode does not apply.
|
||||
mCORSMode = CORS_NONE;
|
||||
SetupSrcMediaStreamPlayback(mSrcAttrStream);
|
||||
} else if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
|
||||
@@ -117,11 +117,6 @@ public:
|
||||
|
||||
int32_t Index();
|
||||
|
||||
protected:
|
||||
virtual ~HTMLOptionElement();
|
||||
|
||||
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
/**
|
||||
* Get the select content element that contains this option, this
|
||||
* intentionally does not return nsresult, all we care about is if
|
||||
@@ -129,6 +124,11 @@ protected:
|
||||
*/
|
||||
HTMLSelectElement* GetSelect();
|
||||
|
||||
protected:
|
||||
virtual ~HTMLOptionElement();
|
||||
|
||||
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
bool mSelectedChanged;
|
||||
bool mIsSelected;
|
||||
|
||||
|
||||
@@ -732,10 +732,10 @@ bool
|
||||
HTMLSelectElement::MatchSelectedOptions(Element* aElement,
|
||||
int32_t /* unused */,
|
||||
nsIAtom* /* unused */,
|
||||
void* /* unused*/)
|
||||
void* aData)
|
||||
{
|
||||
HTMLOptionElement* option = HTMLOptionElement::FromContent(aElement);
|
||||
return option && option->Selected();
|
||||
return option && option->Selected() && option->GetSelect() == static_cast<HTMLSelectElement*>(aData);
|
||||
}
|
||||
|
||||
nsIHTMLCollection*
|
||||
@@ -743,7 +743,7 @@ HTMLSelectElement::SelectedOptions()
|
||||
{
|
||||
if (!mSelectedOptions) {
|
||||
mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr,
|
||||
nullptr, /* deep */ true);
|
||||
this, /* deep */ true);
|
||||
}
|
||||
return mSelectedOptions;
|
||||
}
|
||||
|
||||
@@ -1415,6 +1415,7 @@ BackgroundFactoryRequestChild::BackgroundFactoryRequestChild(
|
||||
uint64_t aRequestedVersion)
|
||||
: BackgroundRequestChildBase(aOpenRequest)
|
||||
, mFactory(aFactory)
|
||||
, mDatabaseActor(nullptr)
|
||||
, mRequestedVersion(aRequestedVersion)
|
||||
, mIsDeleteOp(aIsDeleteOp)
|
||||
{
|
||||
@@ -1439,6 +1440,15 @@ BackgroundFactoryRequestChild::GetOpenDBRequest() const
|
||||
return static_cast<IDBOpenDBRequest*>(mRequest.get());
|
||||
}
|
||||
|
||||
void
|
||||
BackgroundFactoryRequestChild::SetDatabaseActor(BackgroundDatabaseChild* aActor)
|
||||
{
|
||||
AssertIsOnOwningThread();
|
||||
MOZ_ASSERT(!aActor || !mDatabaseActor);
|
||||
|
||||
mDatabaseActor = aActor;
|
||||
}
|
||||
|
||||
bool
|
||||
BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse)
|
||||
{
|
||||
@@ -1450,6 +1460,11 @@ BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse)
|
||||
|
||||
DispatchErrorEvent(mRequest, aResponse);
|
||||
|
||||
if (mDatabaseActor) {
|
||||
mDatabaseActor->ReleaseDOMObject();
|
||||
MOZ_ASSERT(!mDatabaseActor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1468,6 +1483,7 @@ BackgroundFactoryRequestChild::HandleResponse(
|
||||
IDBDatabase* database = databaseActor->GetDOMObject();
|
||||
if (!database) {
|
||||
databaseActor->EnsureDOMObject();
|
||||
MOZ_ASSERT(mDatabaseActor);
|
||||
|
||||
database = databaseActor->GetDOMObject();
|
||||
MOZ_ASSERT(database);
|
||||
@@ -1475,6 +1491,8 @@ BackgroundFactoryRequestChild::HandleResponse(
|
||||
MOZ_ASSERT(!database->IsClosed());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mDatabaseActor == databaseActor);
|
||||
|
||||
if (database->IsClosed()) {
|
||||
// If the database was closed already, which is only possible if we fired an
|
||||
// "upgradeneeded" event, then we shouldn't fire a "success" event here.
|
||||
@@ -1487,6 +1505,7 @@ BackgroundFactoryRequestChild::HandleResponse(
|
||||
}
|
||||
|
||||
databaseActor->ReleaseDOMObject();
|
||||
MOZ_ASSERT(!mDatabaseActor);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1507,6 +1526,8 @@ BackgroundFactoryRequestChild::HandleResponse(
|
||||
|
||||
DispatchSuccessEvent(&helper, successEvent);
|
||||
|
||||
MOZ_ASSERT(!mDatabaseActor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1737,6 +1758,8 @@ BackgroundDatabaseChild::EnsureDOMObject()
|
||||
|
||||
mDatabase = mTemporaryStrongDatabase;
|
||||
mSpec.forget();
|
||||
|
||||
mOpenRequestActor->SetDatabaseActor(this);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1748,6 +1771,8 @@ BackgroundDatabaseChild::ReleaseDOMObject()
|
||||
MOZ_ASSERT(mOpenRequestActor);
|
||||
MOZ_ASSERT(mDatabase == mTemporaryStrongDatabase);
|
||||
|
||||
mOpenRequestActor->SetDatabaseActor(nullptr);
|
||||
|
||||
mOpenRequestActor = nullptr;
|
||||
|
||||
// This may be the final reference to the IDBDatabase object so we may end up
|
||||
|
||||
@@ -254,6 +254,20 @@ class BackgroundFactoryRequestChild final
|
||||
friend class PermissionRequestParent;
|
||||
|
||||
RefPtr<IDBFactory> mFactory;
|
||||
|
||||
// Normally when opening of a database is successful, we receive a database
|
||||
// actor in request response, so we can use it to call ReleaseDOMObject()
|
||||
// which clears temporary strong reference to IDBDatabase.
|
||||
// However, when there's an error, we don't receive a database actor and
|
||||
// IDBRequest::mTransaction is already cleared (must be). So the only way how
|
||||
// to call ReleaseDOMObject() is to have a back-reference to database actor.
|
||||
// This creates a weak ref cycle between
|
||||
// BackgroundFactoryRequestChild (using mDatabaseActor member) and
|
||||
// BackgroundDatabaseChild actor (using mOpenRequestActor member).
|
||||
// mDatabaseActor is set in EnsureDOMObject() and cleared in
|
||||
// ReleaseDOMObject().
|
||||
BackgroundDatabaseChild* mDatabaseActor;
|
||||
|
||||
const uint64_t mRequestedVersion;
|
||||
const bool mIsDeleteOp;
|
||||
|
||||
@@ -271,6 +285,9 @@ private:
|
||||
// Only destroyed by BackgroundFactoryChild.
|
||||
~BackgroundFactoryRequestChild();
|
||||
|
||||
void
|
||||
SetDatabaseActor(BackgroundDatabaseChild* aActor);
|
||||
|
||||
bool
|
||||
HandleResponse(nsresult aResponse);
|
||||
|
||||
|
||||
@@ -138,7 +138,9 @@ GetJSValFromKeyPathString(JSContext* aCx,
|
||||
// only through getters but we still want to support them for key
|
||||
// extraction. So they need to be handled manually.
|
||||
Blob* blob;
|
||||
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
|
||||
// UNWRAP_OBJECT calls might mutate this.
|
||||
JS::Rooted<JSObject*> blobObj(aCx, obj);
|
||||
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &blobObj, blob))) {
|
||||
if (token.EqualsLiteral("size")) {
|
||||
ErrorResult rv;
|
||||
uint64_t size = blob->GetSize(rv);
|
||||
|
||||
@@ -265,6 +265,10 @@ VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateBufferAndPicture(aBuffer, aPicture)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlanarYCbCrData data = ConstructPlanarYCbCrData(aInfo, aBuffer, aPicture);
|
||||
|
||||
aVideoImage->SetDelayedConversion(true);
|
||||
|
||||
+21
-7
@@ -187,7 +187,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
explicit VideoInfo(int32_t aWidth, int32_t aHeight)
|
||||
VideoInfo(int32_t aWidth, int32_t aHeight)
|
||||
: VideoInfo(nsIntSize(aWidth, aHeight))
|
||||
{
|
||||
}
|
||||
@@ -201,7 +201,6 @@ public:
|
||||
, mCodecSpecificConfig(new MediaByteBuffer)
|
||||
, mExtraData(new MediaByteBuffer)
|
||||
, mRotation(kDegree_0)
|
||||
, mImageRect(nsIntRect(nsIntPoint(), aSize))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -263,15 +262,28 @@ public:
|
||||
|
||||
nsIntRect ImageRect() const
|
||||
{
|
||||
if (mImageRect.width < 0 || mImageRect.height < 0) {
|
||||
if (!mImageRect) {
|
||||
return nsIntRect(0, 0, mImage.width, mImage.height);
|
||||
}
|
||||
return mImageRect;
|
||||
return *mImageRect;
|
||||
}
|
||||
|
||||
void SetImageRect(const nsIntRect& aRect)
|
||||
{
|
||||
mImageRect = aRect;
|
||||
mImageRect = Some(aRect);
|
||||
}
|
||||
void ResetImageRect() {
|
||||
mImageRect.reset();
|
||||
}
|
||||
|
||||
// Adopts an image size decoded from the bitstream. The picture rectangle is
|
||||
// expressed relative to the image size, so a change in size invalidates it
|
||||
// and it is discarded; an unchanged size keeps the existing rectangle.
|
||||
void AdoptImageSize(const gfx::IntSize& aImage) {
|
||||
if (mImage != aImage) {
|
||||
ResetImageRect();
|
||||
}
|
||||
mImage = aImage;
|
||||
}
|
||||
|
||||
// Returned the crop rectangle scaled to aWidth/aHeight size relative to
|
||||
@@ -332,9 +344,11 @@ public:
|
||||
uint8_t mBitDepth = 8;
|
||||
|
||||
private:
|
||||
friend struct IPC::ParamTraits<VideoInfo>;
|
||||
|
||||
// mImage may be cropped; currently only used with the WebM container.
|
||||
// A negative width or height indicate that no cropping is to occur.
|
||||
nsIntRect mImageRect;
|
||||
// If unset, no cropping is to occur.
|
||||
Maybe<nsIntRect> mImageRect;
|
||||
|
||||
// Indicates whether or not frames may contain alpha information.
|
||||
bool mAlphaPresent = false;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace IPC {
|
||||
WriteParam(aMsg, aParam.mDisplay);
|
||||
WriteParam(aMsg, aParam.mStereoMode);
|
||||
WriteParam(aMsg, aParam.mImage);
|
||||
WriteParam(aMsg, aParam.ImageRect());
|
||||
WriteParam(aMsg, aParam.mImageRect);
|
||||
}
|
||||
|
||||
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
|
||||
@@ -35,8 +35,7 @@ namespace IPC {
|
||||
ReadParam(aMsg, aIter, &aResult->mDisplay) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mStereoMode) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mImage) &&
|
||||
ReadParam(aMsg, aIter, &imageRect)) {
|
||||
aResult->SetImageRect(imageRect);
|
||||
ReadParam(aMsg, aIter, &aResult->mImageRect)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -847,12 +847,15 @@ WMFVideoMFTManager::CreateBasicVideoFrame(IMFSample* aSample,
|
||||
RefPtr<layers::PlanarYCbCrImage> image =
|
||||
new IMFYCbCrImage(buffer, twoDBuffer);
|
||||
|
||||
VideoData::SetVideoDataToImage(image,
|
||||
mVideoInfo,
|
||||
b,
|
||||
pictureRegion,
|
||||
false);
|
||||
|
||||
if (!VideoData::SetVideoDataToImage(image,
|
||||
mVideoInfo,
|
||||
b,
|
||||
pictureRegion,
|
||||
false)) {
|
||||
LOG("CreateBasicVideoFrame: failed to set video data to image (shmem path)");
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
RefPtr<VideoData> v =
|
||||
VideoData::CreateFromImage(mVideoInfo,
|
||||
aStreamOffset,
|
||||
|
||||
@@ -307,8 +307,7 @@ H264Converter::UpdateConfigFromExtraData(MediaByteBuffer* aExtraData)
|
||||
if (mp4_demuxer::H264::DecodeSPSFromExtraData(aExtraData, spsdata) &&
|
||||
spsdata.pic_width > 0 && spsdata.pic_height > 0) {
|
||||
mp4_demuxer::H264::EnsureSPSIsSane(spsdata);
|
||||
mCurrentConfig.mImage.width = spsdata.pic_width;
|
||||
mCurrentConfig.mImage.height = spsdata.pic_height;
|
||||
mCurrentConfig.AdoptImageSize(gfx::IntSize(spsdata.pic_width, spsdata.pic_height));
|
||||
mCurrentConfig.mDisplay.width = spsdata.display_width;
|
||||
mCurrentConfig.mDisplay.height = spsdata.display_height;
|
||||
}
|
||||
|
||||
@@ -577,6 +577,166 @@ typedef struct _NPEvent
|
||||
} NPEvent;
|
||||
#elif defined(XP_UNIX) && defined(MOZ_X11)
|
||||
typedef XEvent NPEvent;
|
||||
#elif defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
/*
|
||||
* JIHAD (webOS, cavekit-addons-extensions.md R7) — THE PALM EVENT PROTOCOL.
|
||||
*
|
||||
* webOS plugins do not speak X11. Palm's own npapi.h defines NPEvent, under XP_WEBOS, as the
|
||||
* union below; the device's Adobe Flash was compiled against exactly that header and its
|
||||
* NPP_HandleEvent reads these fields. Reproduced here from the webOS WebKit fork's
|
||||
* WebCore/plugins/npapi.h (the XP_WEBOS block, NpPalmEventsEnum through the NPEvent union),
|
||||
* so that the shape crossing our NPAPI boundary is the shape the plugin was built to read.
|
||||
*
|
||||
* This replaces `typedef void* NPEvent` for the headless toolkit, and that arm was not merely
|
||||
* a placeholder — it was load-bearing in the wrong direction. NPRemoteEvent is a raw memcpy of
|
||||
* sizeof(NPEvent) (dom/plugins/ipc/NPEventUnix.h), so with a void* NPEvent the parent shipped
|
||||
* four bytes of its own address space to the child and the child handed the plugin a pointer
|
||||
* into a foreign process. No event could ever have carried a payload. Making NPEvent a real
|
||||
* POD is what lets the whole existing NPP_HandleEvent path work unmodified, parent to child.
|
||||
*
|
||||
* Gated on MOZ_WIDGET_HEADLESS because that is precisely the configuration this port builds
|
||||
* (--enable-default-toolkit=cairo-headless, no X anywhere in the bundle) and the only one that
|
||||
* reaches this arm. A non-webOS headless embedding would get these structs too; that is
|
||||
* harmless, since the alternative it loses is an NPEvent that could not carry an event.
|
||||
*
|
||||
* POINTER-BEARING MEMBERS DO NOT SURVIVE IPC. dstBuffer/graphicsContext (draw),
|
||||
* touches (touch) and reserved (system) are process-local. Pen, key and system events use
|
||||
* none of them; the draw event is built inside the child (PluginInstanceChild.cpp) and never
|
||||
* crosses the boundary, which is why it stays valid.
|
||||
*
|
||||
* One deliberate omission from Palm's union: NpPalmAccelerometerEvent. It is 16 bytes against
|
||||
* NpPalmDrawEvent's 44, so leaving it out changes sizeof(NPEvent) by nothing — and the union
|
||||
* is only ever read through the plugin's OWN copy of this declaration, so an unused member
|
||||
* here is unobservable. It is left out rather than copied because it carries a struct-scoped
|
||||
* `enum Orientation` whose tag would leak to file scope, for no gain.
|
||||
*/
|
||||
typedef enum NpPalmEventsEnum
|
||||
{
|
||||
npPalmPenDownEvent = 1 << 0,
|
||||
npPalmPenUpEvent = 1 << 1,
|
||||
npPalmPenMoveEvent = 1 << 2,
|
||||
npPalmKeyDownEvent = 1 << 3,
|
||||
npPalmKeyUpEvent = 1 << 4,
|
||||
npPalmKeyRepeatEvent = 1 << 5,
|
||||
npPalmKeyPressEvent = 1 << 6,
|
||||
npPalmDrawEvent = 1 << 7,
|
||||
npPalmSystemEvent = 1 << 8,
|
||||
npPalmGestureEvent = 1 << 9,
|
||||
npPalmAccelerometerEvent = 1 << 10,
|
||||
npPalmTouchStartEvent = 1 << 11,
|
||||
npPalmTouchMoveEvent = 1 << 12,
|
||||
npPalmTouchEndEvent = 1 << 13,
|
||||
npPalmTouchCancelledEvent = 1 << 14,
|
||||
npPalmPenDoubleClickEvent = 1 << 15,
|
||||
npPalmPenClickEvent = 1 << 16
|
||||
} NpPalmEventsEnum;
|
||||
|
||||
typedef enum NpPalmSystemEventsEnum
|
||||
{
|
||||
npPalmPauseEvent = 1,
|
||||
npPalmResumeEvent = 2,
|
||||
npPalmGainFocusEvent = 3,
|
||||
npPalmLoseFocusEvent = 4,
|
||||
npPalmDimmedEvent = 5,
|
||||
npPalmSetFullScreenEvent = 6,
|
||||
npPalmUnsetFullScreenEvent = 7,
|
||||
npPalmPageLoadingEvent = 8,
|
||||
npPalmPageLoadCompleteEvent = 9,
|
||||
npPalmViewPortChangedEvent = 10,
|
||||
npPalmSpotlightStartEvent = 11,
|
||||
npPalmSpotlightEndEvent = 12
|
||||
} NpPalmSystemEventsEnum;
|
||||
|
||||
typedef enum NpPalmKeyModifiersEnum
|
||||
{
|
||||
npPalmCtrlKeyModifier = 1 << 0,
|
||||
npPalmAltKeyModifier = 1 << 1,
|
||||
npPalmShiftKeyModifier = 1 << 2,
|
||||
npPalmMetaKeyModifier = 1 << 3
|
||||
} NpPalmKeyModifiersEnum;
|
||||
|
||||
typedef struct NpPalmKeyEvent
|
||||
{
|
||||
int32_t chr; /* 32-bit so any Unicode character fits */
|
||||
int32_t modifiers;
|
||||
int32_t rawkeyCode;
|
||||
int32_t rawModifier;
|
||||
} NpPalmKeyEvent;
|
||||
|
||||
typedef struct NpPalmPenEvent
|
||||
{
|
||||
int32_t xCoord, yCoord; /* plugin-local, in the plugin's own unscaled pixels */
|
||||
int32_t modifiers;
|
||||
} NpPalmPenEvent;
|
||||
|
||||
typedef struct NpPalmTouchPoint
|
||||
{
|
||||
int32_t xCoord, yCoord;
|
||||
} NpPalmTouchPoint;
|
||||
|
||||
typedef struct NpPalmTouchEvent
|
||||
{
|
||||
int32_t touchCount;
|
||||
NpPalmTouchPoint* touches; /* process-local — never send over IPC */
|
||||
int32_t modifiers;
|
||||
} NpPalmTouchEvent;
|
||||
|
||||
typedef struct NpPalmDrawEvent
|
||||
{
|
||||
void* dstBuffer; /* process-local; API 1.0 raster destination */
|
||||
uint32_t dstRowBytes;
|
||||
int32_t srcLeft;
|
||||
int32_t srcRight;
|
||||
int32_t srcTop;
|
||||
int32_t srcBottom;
|
||||
void* graphicsContext; /* process-local; the Piranha PGContext, API 2.0 */
|
||||
int32_t dstLeft;
|
||||
int32_t dstRight;
|
||||
int32_t dstTop;
|
||||
int32_t dstBottom;
|
||||
} NpPalmDrawEvent;
|
||||
|
||||
typedef struct NpPalmSystemEvent
|
||||
{
|
||||
NpPalmSystemEventsEnum type;
|
||||
int32_t value;
|
||||
int32_t viewLeft;
|
||||
int32_t viewRight;
|
||||
int32_t viewTop;
|
||||
int32_t viewBottom;
|
||||
void* reserved; /* process-local */
|
||||
} NpPalmSystemEvent;
|
||||
|
||||
typedef enum NpPalmGestureEventEnum
|
||||
{
|
||||
npPalmGestureStartEvent = 1,
|
||||
npPalmGestureChangeEvent = 2,
|
||||
npPalmGestureEndEvent = 3,
|
||||
npPalmGestureSingleTapEvent = 4
|
||||
} NpPalmGestureEventEnum;
|
||||
|
||||
typedef struct NpPalmGestureEvent
|
||||
{
|
||||
NpPalmGestureEventEnum type;
|
||||
int32_t x, y;
|
||||
float scale, rotate;
|
||||
int32_t center_x, center_y;
|
||||
int32_t modifiers;
|
||||
} NpPalmGestureEvent;
|
||||
|
||||
typedef struct NPEvent
|
||||
{
|
||||
NpPalmEventsEnum eventType;
|
||||
|
||||
union {
|
||||
NpPalmKeyEvent keyEvent;
|
||||
NpPalmPenEvent penEvent;
|
||||
NpPalmDrawEvent drawEvent;
|
||||
NpPalmSystemEvent systemEvent;
|
||||
NpPalmGestureEvent gestureEvent;
|
||||
NpPalmTouchEvent touchEvent;
|
||||
} data;
|
||||
} NPEvent;
|
||||
#else
|
||||
typedef void* NPEvent;
|
||||
#endif
|
||||
|
||||
@@ -341,6 +341,10 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
|
||||
RefPtr<nsNPAPIPlugin> plugin = new nsNPAPIPlugin();
|
||||
|
||||
PluginLibrary* pluginLib = GetNewPluginLibrary(aPluginTag);
|
||||
// JIHAD DIAGNOSTIC (R7): GetNewPluginLibrary is where the OOP decision is made —
|
||||
// RunPluginOOP() ? PluginModuleParent::LoadModule (launches plugin-container) : a
|
||||
// PluginPRLibrary loaded in-process. A null here is the plugin-container launch failing.
|
||||
fprintf(stderr, "[jihad-npapi] GetNewPluginLibrary lib=%d\n", (int)!!pluginLib);
|
||||
if (!pluginLib) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -386,6 +390,7 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
|
||||
#else
|
||||
NPError pluginCallError;
|
||||
nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &plugin->mPluginFuncs, &pluginCallError);
|
||||
fprintf(stderr, "[jihad-npapi] NP_Initialize rv=0x%x npErr=%d\n", (unsigned)rv, (int)pluginCallError);
|
||||
if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -1939,7 +1944,16 @@ _getvalue(NPP npp, NPNVariable variable, void *result)
|
||||
}
|
||||
|
||||
case NPNVSupportsWindowless: {
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX) || \
|
||||
// MOZ_WIDGET_HEADLESS added for Jihad Browser. This list enumerates the toolkits that can
|
||||
// host a windowless plugin, and it was written before a headless toolkit existed — so a
|
||||
// cairo-headless build answered "windowless unsupported" to every plugin, which is the
|
||||
// opposite of the truth. Headless is the toolkit that MOST needs windowless: there is no X
|
||||
// server, no Drawable and no XEmbed, so windowed hosting is impossible and drawing into a
|
||||
// memory bitmap we own is the only thing that can work. gfxPlatformHeadless already returns
|
||||
// true from SupportsPluginDirectBitmapDrawing(), i.e. the drawing model this unlocks
|
||||
// (NPDrawingModelAsyncBitmapSurface) is implemented for this platform; only this
|
||||
// advertisement was missing, and with it false no plugin would ever choose windowless.
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_HEADLESS) || \
|
||||
(defined(MOZ_X11) && defined(MOZ_WIDGET_GTK))
|
||||
*(NPBool*)result = true;
|
||||
#else
|
||||
|
||||
@@ -843,6 +843,9 @@ nsPluginHost::InstantiatePluginInstance(const nsACString& aMimeType, nsIURI* aUR
|
||||
}
|
||||
|
||||
rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner);
|
||||
// JIHAD DIAGNOSTIC (R7): the real rv is flattened to NS_ERROR_FAILURE below, which is what
|
||||
// made this failure opaque from the caller's side.
|
||||
fprintf(stderr, "[jihad-npapi] SetUpPluginInstance rv=0x%x\n", (unsigned)rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
instanceOwner->Destroy();
|
||||
return NS_ERROR_FAILURE;
|
||||
@@ -961,7 +964,12 @@ nsPluginHost::TrySetUpPluginInstance(const nsACString &aMimeType,
|
||||
#endif
|
||||
|
||||
RefPtr<nsNPAPIPlugin> plugin;
|
||||
GetPlugin(aMimeType, getter_AddRefs(plugin));
|
||||
nsresult jihadGetRv = GetPlugin(aMimeType, getter_AddRefs(plugin));
|
||||
// JIHAD DIAGNOSTIC (R7): GetPlugin -> CreateNPAPIPlugin -> nsNPAPIPlugin::CreatePlugin, which
|
||||
// for an OOP build is PluginModuleParent::LoadModule (the plugin-container launch). Its rv is
|
||||
// DISCARDED here; only the null check survives, so a launch failure is invisible.
|
||||
fprintf(stderr, "[jihad-npapi] GetPlugin rv=0x%x plugin=%d\n",
|
||||
(unsigned)jihadGetRv, (int)!!plugin);
|
||||
if (!plugin) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -987,6 +995,7 @@ nsPluginHost::TrySetUpPluginInstance(const nsACString &aMimeType,
|
||||
// except in some cases not Java, see bug 140931
|
||||
// our COM pointer will free the peer
|
||||
nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType);
|
||||
fprintf(stderr, "[jihad-npapi] instance->Initialize rv=0x%x\n", (unsigned)rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
mInstances.RemoveElement(instance.get());
|
||||
aOwner->SetInstance(nullptr);
|
||||
@@ -4201,3 +4210,64 @@ PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance *aInstance)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── JIHAD (webOS, cavekit-addons-extensions.md R7) ────────────────────────────────────────
|
||||
//
|
||||
// A C entry point for the render daemon, following the jihad_offscreen_* precedent in
|
||||
// widget/PuppetWidget.cpp: the daemon links against libxul but cannot see internal classes,
|
||||
// and nsNPAPIPluginInstance::HandleEvent is neither exported nor virtual, so there is no
|
||||
// other way to reach a plugin from outside.
|
||||
//
|
||||
// This exists for the frozen YAP command pair asyncCmdPluginSpotlightStart/End. The card
|
||||
// scrims and smart-zooms to a plugin, then tells the server the rect it settled on; the
|
||||
// server's job is to pass that on to the plugin as Palm's npPalmSpotlightStartEvent(11) /
|
||||
// npPalmSpotlightEndEvent(12). It is not cosmetic: Flash routes 11 to the same handler as
|
||||
// npPalmSetFullScreenEvent(6) and 12 to the same as npPalmUnsetFullScreenEvent(7)
|
||||
// (libflashplayer.so jump table at 0x44e0c — entries 10 and 11 target 0x44fa0/0x44e80,
|
||||
// identical to entries 5 and 6), so this is how a plugin is told it went fullscreen.
|
||||
//
|
||||
// Broadcast to every running instance rather than targeting one: the spotlight is a
|
||||
// page-level, one-plugin-at-a-time state, this daemon serves a single card, and the
|
||||
// alternative — matching the rect against each instance's frame — would invent a
|
||||
// disambiguation rule that the contract does not define. Returns how many instances were
|
||||
// told, so a caller can tell "no plugin" from "delivered".
|
||||
extern "C" MOZ_EXPORT int
|
||||
jihad_plugin_palm_spotlight(int aOn, int aLeft, int aTop, int aRight, int aBottom)
|
||||
{
|
||||
// Palm's NPEvent exists only where npapi.h defines it: the headless toolkit the device builds
|
||||
// (the desktop GTK build has the X11 one and no Palm plugin to tell; T-387)
|
||||
#if defined(MOZ_ENABLE_NPAPI) && defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
|
||||
if (!host) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmSystemEvent;
|
||||
ev.data.systemEvent.type = aOn ? npPalmSpotlightStartEvent : npPalmSpotlightEndEvent;
|
||||
// Palm's host leaves systemEvent.value uninitialised here and fills only the rect
|
||||
// (libWebKitLuna handlePluginSpotlightStart 0x4e6cec-0x4e6d24, which never writes [sp+8]).
|
||||
// The memset above zeroes it, which is strictly tidier and observably the same.
|
||||
ev.data.systemEvent.viewLeft = aLeft;
|
||||
ev.data.systemEvent.viewTop = aTop;
|
||||
ev.data.systemEvent.viewRight = aRight;
|
||||
ev.data.systemEvent.viewBottom = aBottom;
|
||||
|
||||
int delivered = 0;
|
||||
// Copy the instance list first: HandleEvent re-enters the plugin, which may run script and
|
||||
// tear an instance down underneath us.
|
||||
nsTArray<RefPtr<nsNPAPIPluginInstance>> instances(host->JihadRunningInstances());
|
||||
for (uint32_t i = 0; i < instances.Length(); ++i) {
|
||||
int16_t response = kNPEventNotHandled;
|
||||
if (NS_SUCCEEDED(instances[i]->HandleEvent(&ev, &response,
|
||||
NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO))) {
|
||||
delivered++;
|
||||
}
|
||||
}
|
||||
return delivered;
|
||||
#else
|
||||
(void)aOn; (void)aLeft; (void)aTop; (void)aRight; (void)aBottom;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -274,6 +274,13 @@ public:
|
||||
InfallibleTArray<nsCString>& result,
|
||||
bool firstMatchOnly);
|
||||
|
||||
// Jihad R7 (webOS): a snapshot of the live instances, for jihad_plugin_palm_spotlight.
|
||||
// A COPY, not a reference: dispatching an event re-enters the plugin, which can run script
|
||||
// and destroy an instance, and mInstances is mutated by that teardown.
|
||||
const nsTArray<RefPtr<nsNPAPIPluginInstance>>& JihadRunningInstances() const {
|
||||
return mInstances;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class nsPluginUnloadRunnable;
|
||||
|
||||
|
||||
@@ -569,6 +569,75 @@ NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect)
|
||||
GetContentsScaleFactor(&scaleFactor);
|
||||
rect.ScaleRoundOut(scaleFactor);
|
||||
mPluginFrame->InvalidateLayer(nsDisplayItem::TYPE_PLUGIN, &rect);
|
||||
|
||||
// JIHAD (2026-08-09): drive the offscreen daemon's re-capture for WINDOWLESS plugins.
|
||||
//
|
||||
// A windowless plugin has no mWidget, so the mWidget->Invalidate() branch above (which is what
|
||||
// ultimately reaches PuppetWidget::Invalidate and sets the daemon's mJihadDirty flag) never
|
||||
// runs. InvalidateLayer() alone only marks the plugin's ImageLayer for recomposition; with the
|
||||
// in-process BasicLayerManager and no separate compositor, that repaints the widget's own layer
|
||||
// backbuffer but never triggers jihad_offscreen_render_region, the path that actually renders a
|
||||
// frame into the adapter's shared buffer for the card. Measured: Flash pushes a fresh magenta
|
||||
// frame into its ImageContainer ~15x/s and BasicImageLayer::Paint runs on each, yet
|
||||
// render_region fired ONCE (at load) so the card froze on the pre-content frame. The control
|
||||
// plugin only appeared to work because its static fill was already present at that single
|
||||
// capture. Invalidate the nearest real widget here so every async plugin frame marks the daemon
|
||||
// dirty and gets composited to the card.
|
||||
//
|
||||
// THE RECT MATTERS, and the first version of this got it wrong. It invalidated
|
||||
// nearest->GetBounds() — the WIDGET's bounds, i.e. the whole viewport — on the reasoning
|
||||
// that the daemon re-renders the whole document per capture anyway. That reasoning stopped
|
||||
// being true when the daemon learned to repaint only the damaged rows: a whole-viewport
|
||||
// software render costs ~40 ms on this hardware against ~4 ms for a 320x240 plugin box, and
|
||||
// a full-viewport damage box silently forced the expensive path on every single frame.
|
||||
// Measured: an animated SWF ran at 19 fps with 1 partial repaint in 39, and the damage box
|
||||
// logged as 768x942 with the plugin's own 320x240 unioned uselessly into it.
|
||||
//
|
||||
// So invalidate the plugin's own area, translated from plugin-local into the widget's
|
||||
// coordinates the same way the rest of this file does it: content-box origin relative to the
|
||||
// frame, offset to the widget, in device px.
|
||||
if (!mWidget) {
|
||||
nsIWidget* nearest = mPluginFrame->GetNearestWidget();
|
||||
if (nearest) {
|
||||
nsIntRect widgetRect = rect;
|
||||
nsPresContext* pc = mPluginFrame->PresContext();
|
||||
// Offset to the ROOT frame, which is what the widget's coordinates are relative to.
|
||||
// GetReferenceFrame() was the first attempt and it is wrong here: for a plugin it
|
||||
// resolves to a frame that makes the offset come out 0, so every invalidation landed at
|
||||
// the viewport's top-left corner instead of on the plugin. That is not a small error —
|
||||
// the damage-only repaint then faithfully refreshed the wrong 320x240 box and the
|
||||
// plugin's own area never updated again, which reads on screen as the animation
|
||||
// freezing after its first frame. This is the same offset nsPluginFrame itself uses to
|
||||
// build NPWindow.x/y.
|
||||
nsIFrame* rootFrame = nullptr;
|
||||
if (nsIPresShell* ps = mPluginFrame->PresContext()->GetPresShell()) {
|
||||
rootFrame = ps->GetRootFrame();
|
||||
}
|
||||
nsPoint origin = mPluginFrame->GetContentRectRelativeToSelf().TopLeft() +
|
||||
(rootFrame ? mPluginFrame->GetOffsetToCrossDoc(rootFrame) : nsPoint());
|
||||
widgetRect.MoveBy(pc->AppUnitsToDevPixels(origin.x),
|
||||
pc->AppUnitsToDevPixels(origin.y));
|
||||
// A plugin is entitled to report an invalid rect larger than itself, and an empty one
|
||||
// would invalidate nothing at all; clamp to the plugin's own box, and fall back to the
|
||||
// widget bounds if that leaves nothing (correctness before speed).
|
||||
nsIntRect pluginBox(pc->AppUnitsToDevPixels(origin.x),
|
||||
pc->AppUnitsToDevPixels(origin.y),
|
||||
pc->AppUnitsToDevPixels(
|
||||
mPluginFrame->GetContentRectRelativeToSelf().width),
|
||||
pc->AppUnitsToDevPixels(
|
||||
mPluginFrame->GetContentRectRelativeToSelf().height));
|
||||
widgetRect = widgetRect.Intersect(pluginBox);
|
||||
if (widgetRect.IsEmpty()) {
|
||||
widgetRect = pluginBox;
|
||||
}
|
||||
if (widgetRect.IsEmpty()) {
|
||||
nearest->Invalidate(nearest->GetBounds());
|
||||
} else {
|
||||
nearest->Invalidate(LayoutDeviceIntRect(widgetRect.x, widgetRect.y,
|
||||
widgetRect.width, widgetRect.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2530,6 +2599,122 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
|
||||
rv = nsEventStatus_eConsumeNoDefault;
|
||||
#endif
|
||||
|
||||
// Guard MUST match the one npapi.h uses to define NPEvent as Palm's union. Any other
|
||||
// predicate compiles this against `typedef void* NPEvent` and does not build.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
// JIHAD (webOS, cavekit-addons-extensions.md R7) — DELIVER INPUT IN THE PALM PROTOCOL.
|
||||
//
|
||||
// Everything above this point is a platform arm: XP_MACOSX, XP_WIN, MOZ_X11. A
|
||||
// cairo-headless build matches none of them, so this function used to reduce to
|
||||
// `return nsEventStatus_eIgnore;` and a windowless plugin received no mouse, key, focus or
|
||||
// blur event in its whole lifetime. Measured on the TouchPad before this branch existed: a
|
||||
// tap hit-tested the <EMBED>, the daemon logged `mouseSend <EMBED> ... : down`, and the
|
||||
// plugin's NPP_HandleEvent was called exactly once ever — for the child's own gain-focus
|
||||
// injection. Nothing upstream is at fault; the DOM listeners, the hit test, the windowless
|
||||
// guards and the IPC hop are all live and correct (see the Dispatch*ToPlugin callers).
|
||||
//
|
||||
// The event shape is Palm's, because NPEvent on this platform IS Palm's union (npapi.h) and
|
||||
// the device's Flash was compiled against that declaration.
|
||||
{
|
||||
NPEvent pluginEvent;
|
||||
memset(&pluginEvent, 0, sizeof(pluginEvent));
|
||||
|
||||
if (anEvent.mClass == eMouseEventClass) {
|
||||
const WidgetMouseEvent& mouseEvent = *anEvent.AsMouseEvent();
|
||||
|
||||
switch (anEvent.mMessage) {
|
||||
case eMouseDown: pluginEvent.eventType = npPalmPenDownEvent; break;
|
||||
case eMouseUp: pluginEvent.eventType = npPalmPenUpEvent; break;
|
||||
case eMouseMove: pluginEvent.eventType = npPalmPenMoveEvent; break;
|
||||
// The synthesized click/dblclick that follows a down/up pair. Palm's host sends these
|
||||
// as their own event types rather than swallowing them (libWebKitLuna
|
||||
// handleMouseEvent 0x4e946c-0x4e94d8 maps click -> 65536 and dblclick -> 32768, in
|
||||
// ADDITION to the down/up pair, not instead of it), so this is what a plugin written
|
||||
// for webOS was built to see. The X11 arm above drops them instead, which is right
|
||||
// for X11 and wrong here.
|
||||
//
|
||||
// Flash itself ignores both: its NPP_HandleEvent jump table (libflashplayer.so
|
||||
// 0x4514c-0x4522c) compares only against 1/2/4/8/16/64/128/256/512, and anything
|
||||
// else falls through to "not handled" — confirmed on device, where a tap logs
|
||||
// `palm event 0x10000 ... handled=0` between two handled pen events. Sending them
|
||||
// anyway is deliberate: the contract is the host's, not this one plugin's, and a
|
||||
// plugin that acts only on the click is otherwise silently unusable.
|
||||
case eMouseClick: pluginEvent.eventType = npPalmPenClickEvent; break;
|
||||
case eMouseDoubleClick: pluginEvent.eventType = npPalmPenDoubleClickEvent; break;
|
||||
default: break; // over/out/wheel/aux: no Palm equivalent, drop
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
// The plugin-local point, computed exactly as the MOZ_X11 arm above computes it. This
|
||||
// is also what Palm's own host does — PluginView's webOS block uses the renderer's
|
||||
// absoluteToLocal() on the same absolute location — so the two hosts agree on the
|
||||
// space without either being ported to the other.
|
||||
//
|
||||
// Deliberately NO zoom or scroll arithmetic here. The card's fit-zoom is not an engine
|
||||
// zoom (it is applied when the daemon scales the capture context), layout.css.
|
||||
// devPixelsPerPx is pinned to 1.0, and the daemon has already mapped card px to CSS
|
||||
// viewport px before dispatching. Adding a factor here is how plugin input drifts away
|
||||
// from where page content thinks it was clicked; the criterion is that both go through
|
||||
// the same transform, and they do precisely because this arm adds none.
|
||||
const nsPresContext* presContext = mPluginFrame->PresContext();
|
||||
nsPoint appPoint =
|
||||
nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) -
|
||||
mPluginFrame->GetContentRectRelativeToSelf().TopLeft();
|
||||
pluginEvent.data.penEvent.xCoord =
|
||||
presContext->AppUnitsToDevPixels(appPoint.x);
|
||||
pluginEvent.data.penEvent.yCoord =
|
||||
presContext->AppUnitsToDevPixels(appPoint.y);
|
||||
pluginEvent.data.penEvent.modifiers =
|
||||
(mouseEvent.IsControl() ? npPalmCtrlKeyModifier : 0) |
|
||||
(mouseEvent.IsAlt() ? npPalmAltKeyModifier : 0) |
|
||||
(mouseEvent.IsShift() ? npPalmShiftKeyModifier : 0) |
|
||||
(mouseEvent.IsMeta() ? npPalmMetaKeyModifier : 0);
|
||||
}
|
||||
} else if (anEvent.mClass == eKeyboardEventClass) {
|
||||
const WidgetKeyboardEvent& keyEvent = *anEvent.AsKeyboardEvent();
|
||||
|
||||
switch (anEvent.mMessage) {
|
||||
case eKeyDown: pluginEvent.eventType = npPalmKeyDownEvent; break;
|
||||
case eKeyUp: pluginEvent.eventType = npPalmKeyUpEvent; break;
|
||||
case eKeyPress: pluginEvent.eventType = npPalmKeyPressEvent; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
// There is no native key event to copy from on this platform (mPluginEvent is a
|
||||
// Windows concept), so the Palm fields are synthesized from the widget event.
|
||||
// rawkeyCode/rawModifier are Palm's "pass the unprocessed event through" pair, which
|
||||
// for a synthesized event is the same key code and modifier set.
|
||||
int32_t mods =
|
||||
(keyEvent.IsControl() ? npPalmCtrlKeyModifier : 0) |
|
||||
(keyEvent.IsAlt() ? npPalmAltKeyModifier : 0) |
|
||||
(keyEvent.IsShift() ? npPalmShiftKeyModifier : 0) |
|
||||
(keyEvent.IsMeta() ? npPalmMetaKeyModifier : 0);
|
||||
pluginEvent.data.keyEvent.chr =
|
||||
static_cast<int32_t>(keyEvent.PseudoCharCode());
|
||||
pluginEvent.data.keyEvent.modifiers = mods;
|
||||
pluginEvent.data.keyEvent.rawkeyCode = static_cast<int32_t>(keyEvent.mKeyCode);
|
||||
pluginEvent.data.keyEvent.rawModifier = mods;
|
||||
}
|
||||
} else if (anEvent.mMessage == eFocus || anEvent.mMessage == eBlur) {
|
||||
// Focus arrives as a bare WidgetGUIEvent with a null widget (see DispatchFocusToPlugin),
|
||||
// so it must not touch the coordinate path above.
|
||||
pluginEvent.eventType = npPalmSystemEvent;
|
||||
pluginEvent.data.systemEvent.type =
|
||||
(anEvent.mMessage == eFocus) ? npPalmGainFocusEvent : npPalmLoseFocusEvent;
|
||||
}
|
||||
|
||||
if (pluginEvent.eventType) {
|
||||
int16_t response = kNPEventNotHandled;
|
||||
mInstance->HandleEvent(&pluginEvent, &response,
|
||||
NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
|
||||
if (response == kNPEventHandled) {
|
||||
rv = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -2977,23 +3162,40 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
|
||||
if (!mWidget) {
|
||||
// native (single process)
|
||||
mWidget = do_CreateInstance(kWidgetCID, &rv);
|
||||
nsWidgetInitData initData;
|
||||
initData.mWindowType = eWindowType_plugin;
|
||||
initData.mUnicode = false;
|
||||
initData.clipChildren = true;
|
||||
initData.clipSiblings = true;
|
||||
rv = mWidget->Create(parentWidget.get(), nullptr,
|
||||
LayoutDeviceIntRect(0, 0, 0, 0), &initData);
|
||||
if (NS_FAILED(rv)) {
|
||||
mWidget->Destroy();
|
||||
mWidget = nullptr;
|
||||
return rv;
|
||||
if (!mWidget) {
|
||||
// JIHAD (2026-10-05): A BUILD WITH NO CHILD-WINDOW WIDGET HAS NO WINDOWED PLUGINS.
|
||||
// The offscreen embedding registers no widget for kWidgetCID, so this came back
|
||||
// null and the Create() call below dereferenced it: the daemon died with SIGSEGV on
|
||||
// every page whose Flash movie was embedded the ordinary way. A plugin is windowed
|
||||
// unless it asks otherwise, and Flash asks only for wmode=opaque/transparent — the
|
||||
// default, wmode=window, is what most of the web uses (found on two "Flash test"
|
||||
// sites; every test page of ours had wmode=opaque). webOS's own browser never had
|
||||
// plugin windows either and handed every plugin a drawable; its Flash build draws
|
||||
// through the Palm window structure in both modes. So do the same: with no window
|
||||
// to give, the instance is windowless.
|
||||
mInstance->SetWindowless(true);
|
||||
windowless = true;
|
||||
} else {
|
||||
nsWidgetInitData initData;
|
||||
initData.mWindowType = eWindowType_plugin;
|
||||
initData.mUnicode = false;
|
||||
initData.clipChildren = true;
|
||||
initData.clipSiblings = true;
|
||||
rv = mWidget->Create(parentWidget.get(), nullptr,
|
||||
LayoutDeviceIntRect(0, 0, 0, 0), &initData);
|
||||
if (NS_FAILED(rv)) {
|
||||
mWidget->Destroy();
|
||||
mWidget = nullptr;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mWidget->EnableDragDrop(true);
|
||||
mWidget->Show(false);
|
||||
mWidget->Enable(false);
|
||||
if (mWidget) {
|
||||
mWidget->EnableDragDrop(true);
|
||||
mWidget->Show(false);
|
||||
mWidget->Enable(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (mPluginFrame) {
|
||||
|
||||
@@ -133,6 +133,12 @@ BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason)
|
||||
{
|
||||
PLUGIN_LOG_DEBUG_METHOD;
|
||||
|
||||
// Jihad R7: end-of-stream is what tells a Flash player the movie is complete. A player that
|
||||
// is never told stops on frame zero, which looks exactly like a player that cannot draw.
|
||||
fprintf(stderr, "[jihad-npapi-child] RecvNPP_DestroyStream reason=%d state=%d\n",
|
||||
(int)reason, (int)mState);
|
||||
fflush(stderr);
|
||||
|
||||
if (ALIVE != mState)
|
||||
NS_RUNTIMEABORT("Unexpected state: recevied NPP_DestroyStream twice?");
|
||||
|
||||
@@ -232,8 +238,12 @@ BrowserStreamChild::Deliver()
|
||||
if (kStreamOpen == mStreamStatus)
|
||||
mStreamStatus = NPRES_DONE;
|
||||
|
||||
(void) mInstance->mPluginIface
|
||||
NPError dsrv = mInstance->mPluginIface
|
||||
->destroystream(&mInstance->mData, &mStream, mStreamStatus);
|
||||
// Jihad R7: the delivery site, as opposed to RecvNPP_DestroyStream which only queues it.
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_DestroyStream status=%d rv=%d\n",
|
||||
(int)mStreamStatus, (int)dsrv);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (DESTROYED == mDestroyPending && mNotifyPending) {
|
||||
NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?");
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* NPN entry points exported as GLOBAL SYMBOLS, for plugins that expect the old Netscape
|
||||
* convention where the host browser exports them rather than only passing an NPNetscapeFuncs
|
||||
* table to NP_Initialize.
|
||||
*
|
||||
* WHY THIS IS ITS OWN TRANSLATION UNIT. The obvious place for these is PluginModuleChild.cpp,
|
||||
* and it does not work: npapi.h already declares NPN_InvalidateRect, and every header in this
|
||||
* build is compiled under `-include config/gcc_hidden.h`, which pushes hidden visibility. GCC
|
||||
* takes visibility from the FIRST declaration it sees, so a later definition carrying
|
||||
* __attribute__((visibility("default"))) is silently ignored and the symbol lands in the
|
||||
* object as GLOBAL HIDDEN — present to `nm`, absent from the dynamic table, and therefore
|
||||
* impossible to interpose. Verified with readelf. This file deliberately includes no NPAPI
|
||||
* headers at all and describes the functions with void* so there is no prior declaration to
|
||||
* inherit hidden visibility from.
|
||||
*
|
||||
* WHY IT MATTERS. webOS's Flash imports NPN_CreateObject and NPN_InvalidateRect as undefined
|
||||
* dynamic symbols. Left alone they bind into libWebKitLuna.so — another browser engine that
|
||||
* is in this process because Flash (and our Piranha drawing path) pull it in. Two consequences,
|
||||
* both measured:
|
||||
* 1. Flash's repaint requests go to LunaSysMgr's WebKit and never reach our engine, so the
|
||||
* plugin is only ever painted when something else happens to invalidate it. That alone
|
||||
* makes an animating plugin look frozen.
|
||||
* 2. It is a latent crash: UXP stores a PluginInstanceChild* in NPP::ndata, while
|
||||
* libWebKitLuna's NPN_InvalidateRect casts that same field to a WebCore::PluginView* and
|
||||
* makes a virtual call on it — a wild vtable dispatch on the first repaint.
|
||||
*
|
||||
* The forwarding pointers are installed by PluginModuleChild once the browser function table
|
||||
* exists; until then these are safe no-ops.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
void (*gJihadNPNInvalidateRect)(void* aNPP, void* aRect) = 0;
|
||||
void* (*gJihadNPNCreateObject)(void* aNPP, void* aClass) = 0;
|
||||
|
||||
// Read by the host repaint clock in PluginInstanceChild.cpp: while this is zero the plugin has
|
||||
// never asked to be repainted, so the host has to drive it.
|
||||
unsigned gJihadNPNInvalidateRectCalls = 0;
|
||||
|
||||
__attribute__((visibility("default"))) void
|
||||
NPN_InvalidateRect(void* aNPP, void* aRect)
|
||||
{
|
||||
// The first call is the signal that the plugin's frame clock is finally running; without
|
||||
// it the plugin is only ever painted by an unrelated invalidation, which is
|
||||
// indistinguishable on screen from a plugin that draws nothing. Log it once, and log the
|
||||
// no-forwarder case too — arriving before PluginModuleChild installs the pointers is a
|
||||
// dropped repaint, not a no-op.
|
||||
gJihadNPNInvalidateRectCalls++;
|
||||
static int sLogged = 0;
|
||||
if (sLogged < 8) {
|
||||
sLogged++;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPN_InvalidateRect npp=%p rect=%p fwd=%p\n",
|
||||
aNPP, aRect, (void*)gJihadNPNInvalidateRect);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (gJihadNPNInvalidateRect) {
|
||||
gJihadNPNInvalidateRect(aNPP, aRect);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"))) void*
|
||||
NPN_CreateObject(void* aNPP, void* aClass)
|
||||
{
|
||||
return gJihadNPNCreateObject ? gJihadNPNCreateObject(aNPP, aClass) : 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -125,6 +125,29 @@ child:
|
||||
// of its parent has changed
|
||||
async ContentsScaleFactorChanged(double aContentsScaleFactor);
|
||||
|
||||
// Jihad (webOS, cavekit-addons-extensions.md R7): ask the plugin for ONE frame, now.
|
||||
//
|
||||
// Palm's host issued npPalmDrawEvent every time the PAGE painted, so Flash can sit on its own
|
||||
// frame clock and expect to be asked. Patch 0026 got it drawing by giving the CHILD a timer,
|
||||
// but that timer free-runs against the daemon's tick, so a frame lands at a drifting phase and
|
||||
// waits up to a whole tick to be published. Measured on device with a 30 fps SWF: 1:1 delivery
|
||||
// and a 31-34 ms average, but a gap spread of 9-62 ms, which is what the eye reads as stutter.
|
||||
//
|
||||
// CAUTION, measured 2026-08-10 and it contradicts what 0026 assumed: the plugin DOES invalidate
|
||||
// itself, ~22 times a second, alongside our ~27. The counter 0026 reads to conclude otherwise
|
||||
// (gJihadNPNInvalidateRectCalls) only sees calls that bind the NPN_InvalidateRect SYMBOL, and
|
||||
// an ordinary NPAPI plugin goes through the NPNetscapeFuncs table instead. So this message is
|
||||
// currently a SECOND producer rather than the only one, and whether it should exist at all for
|
||||
// a self-invalidating plugin is open — see the R7 frame-rate criterion.
|
||||
//
|
||||
// This is the pull that removes the second clock. ASYNC on purpose: the stock host pulls the
|
||||
// plugin synchronously inside its page paint (PluginViewPalm), but out-of-process that shape
|
||||
// is intr NPP_HandleEvent, which would block the daemon's main thread — and therefore its
|
||||
// tick and its YAP socket — for a full IPC round trip plus a Flash paint on every frame.
|
||||
// Async keeps the phase lock and costs nothing: the child coalesces duplicate requests via
|
||||
// mCurrentInvalidateTask, so an extra request while one is in flight is a no-op.
|
||||
async JihadRequestFrame();
|
||||
|
||||
// ********************** Async plugins rendering
|
||||
// see https://wiki.mozilla.org/Gecko:AsyncPluginPainting
|
||||
// **********************
|
||||
|
||||
@@ -31,6 +31,7 @@ using mozilla::gfx::SharedDIBSurface;
|
||||
#endif
|
||||
#endif
|
||||
#include "gfxSharedImageSurface.h"
|
||||
#include <dlfcn.h> // Jihad R7: reach the plugin's own glib for npPalmEventLoopValue
|
||||
#include "gfxUtils.h"
|
||||
#include "gfxAlphaRecovery.h"
|
||||
|
||||
@@ -50,6 +51,61 @@ using namespace mozilla::gfx;
|
||||
using namespace mozilla::widget;
|
||||
using namespace std;
|
||||
|
||||
// Defined next to JihadPumpGlib, far below; declared here because DoNPP_New starts it.
|
||||
static void JihadStartGlibPump(PluginInstanceChild* aInstance);
|
||||
|
||||
// The instance the host clock repaints, and the count of repaints the PLUGIN asked for. Both
|
||||
// live here rather than on the instance because the pump is a process-wide free function.
|
||||
// Cleared in ~PluginInstanceChild so the clock cannot outlive what it points at.
|
||||
static PluginInstanceChild* sJihadPalmInstance = nullptr;
|
||||
static uint64_t gJihadPalmDrawEvents = 0;
|
||||
// Defined in JihadNPNInterpose.cpp, which deliberately includes no NPAPI headers.
|
||||
extern "C" unsigned gJihadNPNInvalidateRectCalls;
|
||||
|
||||
// ── WHICH CALL SITE ACTUALLY DRIVES PLUGIN DRAWS (Jihad, addons R7) ──────────────────────
|
||||
//
|
||||
// Three sessions have now attributed the plugin's draw rate to a specific clock by READING the
|
||||
// code, and every attribution was wrong. Patch 0029 finally put a counter on the request side
|
||||
// and the numbers did not agree with any of them: 3988 draws against 3072 host requests, with
|
||||
// the child's own timer provably stood down (one "frame clock -> HOST" transition, no fallback)
|
||||
// and Flash provably not self-invalidating (gJihadNPNInvalidateRectCalls stays 0). So roughly a
|
||||
// quarter of all draws come from a fourth place, and at an 8 ms tick that share DOMINATES.
|
||||
//
|
||||
// Every path to a draw funnels through AsyncShowPluginFrame, so tagging its call sites answers
|
||||
// the question directly rather than by elimination. Reported next to `palm draw`, which is the
|
||||
// count these have to add up to. Declared up here with the other file statics because the first
|
||||
// tagged site is in AnswerNPP_SetWindow, well above the frame-clock block further down.
|
||||
enum {
|
||||
kJihadShowSetWindow = 0, // AnswerNPP_SetWindow / AsyncSetWindow, accumulated rect
|
||||
kJihadShowInvalidate, // InvalidateRect reached from OUR host repaint tick
|
||||
kJihadShowPluginSelf, // InvalidateRect reached from the PLUGIN's own NPN_InvalidateRect
|
||||
kJihadShowRetry, // InvalidateRectDelayed's retry when ShowPluginFrame refused
|
||||
kJihadShowBgUpdate, // RecvUpdateBackground, background changed
|
||||
kJihadShowBgUpdateTail, // RecvUpdateBackground, unconditional tail
|
||||
kJihadShowBgDestroy, // background destroyer
|
||||
kJihadShowSites
|
||||
};
|
||||
static uint32_t gJihadShowFrom[kJihadShowSites];
|
||||
static const char* const kJihadShowNames[kJihadShowSites] = {
|
||||
"setwin", "hostinval", "plugininval", "retry", "bgupd", "bgtail", "bgdestroy"
|
||||
};
|
||||
|
||||
// True only while JihadPalmRepaintTick's own InvalidateRect call is on the stack, so the two
|
||||
// producers can be told apart AT the shared funnel.
|
||||
//
|
||||
// WHY THIS EXISTS, and it invalidates a premise the port has carried since patch 0026:
|
||||
// gJihadNPNInvalidateRectCalls — the counter used to decide "the plugin never asks to be
|
||||
// repainted, so the host must drive it" — is STRUCTURALLY BLIND. It lives in
|
||||
// JihadNPNInterpose.cpp and only counts calls that bind the NPN_InvalidateRect SYMBOL. An
|
||||
// ordinary NPAPI plugin calls through the NPNetscapeFuncs table instead, which
|
||||
// PluginModuleChild fills with its own _invalidaterect (PluginModuleChild.cpp:986); that
|
||||
// forwards straight to InstCast(aNPP)->InvalidateRect (:1302-1310) and never touches the
|
||||
// interpose. So the counter can read 0 for the whole run while the plugin invalidates
|
||||
// constantly — which is exactly what was measured: 1344 host requests against 2662 invalidates
|
||||
// with the child's timer provably off (one "frame clock -> HOST", no fallback).
|
||||
static bool sJihadInHostRepaint = false;
|
||||
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
@@ -193,6 +249,8 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
|
||||
#endif
|
||||
, mAccumulatedInvalidRect(0,0,0,0)
|
||||
, mIsTransparent(false)
|
||||
, mJihadSentPalmGainFocus(false)
|
||||
, mJihadInputEventsLogged(0)
|
||||
, mSurfaceType(gfxSurfaceType::Max)
|
||||
, mPendingPluginCall(false)
|
||||
, mDoAlphaExtraction(false)
|
||||
@@ -231,6 +289,11 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
|
||||
|
||||
PluginInstanceChild::~PluginInstanceChild()
|
||||
{
|
||||
// The host repaint clock outlives any single instance (it is bound to the process-wide
|
||||
// GMainLoop), so it must not be left holding this pointer.
|
||||
if (sJihadPalmInstance == this) {
|
||||
sJihadPalmInstance = nullptr;
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
NS_ASSERTION(!mPluginWindowHWND, "Destroying PluginInstanceChild without NPP_Destroy?");
|
||||
if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
|
||||
@@ -260,6 +323,65 @@ PluginInstanceChild::~PluginInstanceChild()
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── THE webOS PLUGIN ABI (Jihad, addons R7) ───────────────────────────────────────────────
|
||||
//
|
||||
// None of this is in our npapi.h; it is Palm's XP_WEBOS block, and every layout below was
|
||||
// checked TWICE — against WebKit/Source/WebCore/plugins/npapi.h, and against the machine code
|
||||
// of the host that actually shipped on the device. That host is NOT the Qt one in our WebKit
|
||||
// checkout (PluginViewQt.cpp passes a QPainter* and Flash has no Qt dependency at all); it is
|
||||
// WebCore::PluginView compiled from PluginViewPalm.cpp into /usr/lib/libWebKitLuna.so, which
|
||||
// is unstripped on the device and can simply be read. Offsets cited below are into that
|
||||
// library. Get these wrong and the plugin reads garbage, silently.
|
||||
//
|
||||
// The event union itself now lives in npapi.h as the real `NPEvent` for this platform, so the
|
||||
// parent (nsPluginInstanceOwner::ProcessEvent) and the child cannot drift and the type survives
|
||||
// the NPRemoteEvent memcpy. What used to be here was a TRUNCATED private mirror —
|
||||
// systemEvent held only `type`, which is enough for gain-focus and silently wrong for a
|
||||
// spotlight event, whose rect lives in the four fields the mirror did not have.
|
||||
//
|
||||
// These assertions are the actual contract. Each offset was read out of the shipped host's
|
||||
// machine code, and a mismatch here means the plugin reads a different field than the one
|
||||
// written — a failure with no symptom other than wrong behaviour.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
static_assert(sizeof(NPEvent) == 48, "webOS NPEvent is 48 bytes");
|
||||
static_assert(offsetof(NPEvent, data) == 4, "eventType is a 4-byte field at 0");
|
||||
// PluginView::handleMouseEvent (libWebKitLuna 0x4e9458/0x4e9460/0x4e9464) stores x, y and
|
||||
// modifiers to [sp+4], [sp+8], [sp+12] with eventType at [sp].
|
||||
static_assert(offsetof(NPEvent, data.penEvent.xCoord) == 4, "penEvent.xCoord @4");
|
||||
static_assert(offsetof(NPEvent, data.penEvent.yCoord) == 8, "penEvent.yCoord @8");
|
||||
static_assert(offsetof(NPEvent, data.penEvent.modifiers) == 12, "penEvent.modifiers @12");
|
||||
// PluginView::handleKeyboardEvent (0x4e8ebc/0x4e8e80/0x4e8ef4/0x4e8f04).
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.chr) == 4, "keyEvent.chr @4");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.modifiers) == 8, "keyEvent.modifiers @8");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.rawkeyCode) == 12, "keyEvent.rawkeyCode @12");
|
||||
static_assert(offsetof(NPEvent, data.keyEvent.rawModifier) == 16, "keyEvent.rawModifier @16");
|
||||
// PluginView::handlePluginSpotlightStart (0x4e6cec-0x4e6d20) writes 0x100 at [sp], 11 at
|
||||
// [sp+4], then left/right/top/bottom at [sp+12]/[sp+16]/[sp+20]/[sp+24] — note it never
|
||||
// initialises `value` at [sp+8].
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.type) == 4, "systemEvent.type @4");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewLeft) == 12, "systemEvent.viewLeft @12");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewRight) == 16, "systemEvent.viewRight @16");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewTop) == 20, "systemEvent.viewTop @20");
|
||||
static_assert(offsetof(NPEvent, data.systemEvent.viewBottom) == 24, "systemEvent.viewBottom @24");
|
||||
// PluginView::paint (0x4e7e30-0x4e7e44) stores platformContext() at frame+52 and the four
|
||||
// scaled dst coords at +56/+60/+64/+68, i.e. +24/+28/+32/+36 inside the draw member.
|
||||
static_assert(offsetof(NPEvent, data.drawEvent.graphicsContext) == 28, "drawEvent.gc @28");
|
||||
static_assert(offsetof(NPEvent, data.drawEvent.dstLeft) == 32, "drawEvent.dstLeft @32");
|
||||
#endif // XP_UNIX && MOZ_WIDGET_HEADLESS
|
||||
|
||||
// NPWindow.window for a webOS plugin. Palm's host never calls setwindow without one:
|
||||
// PluginView::setNPWindowRect (libWebKitLuna 0x4e74e0) builds it as a STACK LOCAL, points
|
||||
// m_npWindow.window at it (0x4e7570 `str sp, [r4, #520]`), calls setwindow, and immediately
|
||||
// re-nulls the pointer (0x4e7614) so the dangling stack address is never left behind.
|
||||
// Field offsets read straight off that function: visible is a byte at +0 (0x4e755c
|
||||
// `strbne r3, [sp]`), bpp is 32 at +4 (0x4e75dc `mov ip,#32; str ip,[sp,#4]`), and scaleFactor
|
||||
// is the double from ScrollView::getScale() at +8 (0x4e7588 `strd r6, [sp, #8]`).
|
||||
struct JihadNpPalmWindow {
|
||||
bool visible;
|
||||
uint32_t bpp;
|
||||
double scaleFactor;
|
||||
};
|
||||
|
||||
NPError
|
||||
PluginInstanceChild::DoNPP_New()
|
||||
{
|
||||
@@ -286,6 +408,44 @@ PluginInstanceChild::DoNPP_New()
|
||||
return rv;
|
||||
}
|
||||
|
||||
// WEBOS EVENT LOOP (Jihad, addons R7).
|
||||
//
|
||||
// Palm's own host does this right after NPP_New and before anything else
|
||||
// (WebKit/Source/WebCore/plugins/qt/PluginViewQt.cpp, PluginView::platformStart):
|
||||
// it hands the plugin a GMainLoop* through npPalmEventLoopValue. A webOS plugin drives
|
||||
// its own frame scheduling off that loop, so a plugin that never receives one is a
|
||||
// plugin that instantiates and then never asks to paint — exactly the symptom this port
|
||||
// had.
|
||||
//
|
||||
// glib is not linked into this process; it arrives as a dependency of the plugin, so it
|
||||
// can only be reached by name once the plugin is loaded — which it is by now. A plugin
|
||||
// that does not know the variable just returns an error, so this is safe for any plugin.
|
||||
if (mPluginIface->setvalue) {
|
||||
static void* sMainLoop = nullptr;
|
||||
if (!sMainLoop) {
|
||||
if (void* glib = dlopen("libglib-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
typedef void* (*GMainLoopNewFn)(void*, int);
|
||||
GMainLoopNewFn newLoop = (GMainLoopNewFn)dlsym(glib, "g_main_loop_new");
|
||||
if (newLoop) {
|
||||
sMainLoop = newLoop(nullptr, 1 /* is_running */);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sMainLoop) {
|
||||
const uint32_t kNpPalmEventLoopValue = 10000;
|
||||
NPError lerr = mPluginIface->setvalue(npp, (NPNVariable)kNpPalmEventLoopValue,
|
||||
sMainLoop);
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmEventLoopValue loop=%p err=%d\n",
|
||||
sMainLoop, (int)lerr);
|
||||
// The plugin now has a loop to hang sources off; start running it. See
|
||||
// JihadStartGlibPump for why this cannot wait for the first paint.
|
||||
JihadStartGlibPump(this);
|
||||
} else {
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmEventLoopValue: no glib main loop\n");
|
||||
}
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
#if defined(XP_MACOSX) && defined(__i386__)
|
||||
@@ -717,8 +877,8 @@ PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue)
|
||||
// on the chrome process. This is because our audio
|
||||
// API calls should run the chrome proc, not content.
|
||||
NPError rv = NPERR_GENERIC_ERROR;
|
||||
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
|
||||
if (chromeInstance) {
|
||||
PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
|
||||
if (chromeInstance) {
|
||||
rv = chromeInstance->PluginRequiresAudioDeviceChanges(this,
|
||||
(NPBool)(intptr_t)aValue);
|
||||
}
|
||||
@@ -964,6 +1124,35 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
|
||||
else
|
||||
*handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
|
||||
|
||||
// Guard MUST match the one npapi.h uses to define NPEvent as Palm's union. Any other
|
||||
// predicate compiles this against `typedef void* NPEvent` and does not build.
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
// JIHAD (webOS R7): the only field-visible evidence that host input reached the plugin.
|
||||
// A closed-source plugin acknowledges nothing — Flash returns its `handled` flag before it
|
||||
// even looks at the event (its NPP_HandleEvent sets the return, then dispatches), so this
|
||||
// line proves DELIVERY and nothing more. Capped, because a pen-move stream would otherwise
|
||||
// flood the daemon log, which is the only channel this device has.
|
||||
if (mJihadInputEventsLogged < 24) {
|
||||
mJihadInputEventsLogged++;
|
||||
if (evcopy.eventType == npPalmKeyDownEvent ||
|
||||
evcopy.eventType == npPalmKeyUpEvent ||
|
||||
evcopy.eventType == npPalmKeyPressEvent) {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm key 0x%x chr=%d raw=%d mods=0x%x "
|
||||
"handled=%d\n",
|
||||
(unsigned)evcopy.eventType, (int)evcopy.data.keyEvent.chr,
|
||||
(int)evcopy.data.keyEvent.rawkeyCode,
|
||||
(unsigned)evcopy.data.keyEvent.modifiers, (int)*handled);
|
||||
} else {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm event 0x%x at %d,%d mods=0x%x "
|
||||
"handled=%d\n",
|
||||
(unsigned)evcopy.eventType, (int)evcopy.data.penEvent.xCoord,
|
||||
(int)evcopy.data.penEvent.yCoord,
|
||||
(unsigned)evcopy.data.penEvent.modifiers, (int)*handled);
|
||||
}
|
||||
fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Release any reference counted objects created in the child process.
|
||||
if (evcopy.type == NPCocoaEventKeyDown ||
|
||||
@@ -1449,6 +1638,8 @@ PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
|
||||
|
||||
if (mPluginIface->setwindow)
|
||||
(void) mPluginIface->setwindow(&mData, &mWindow);
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
// Jihad headless: no windowed plugins; nothing to do.
|
||||
#else
|
||||
# error Implement me for your OS
|
||||
#endif
|
||||
@@ -2827,6 +3018,12 @@ PluginInstanceChild::DoNPP_NewStream(BrowserStreamChild* actor,
|
||||
AssertPluginThread();
|
||||
AutoStackHelper guard(this);
|
||||
NPError rv = actor->StreamConstructed(mimeType, seekable, stype);
|
||||
// JIHAD DIAGNOSTIC (R7): does content actually reach the plugin? A plugin that was
|
||||
// never handed its stream draws nothing for a completely different reason than a
|
||||
// plugin that cannot draw, and on screen the two are identical (an empty box).
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_NewStream mime=%s seekable=%d rv=%d stype=%d\n",
|
||||
mimeType.get(), (int)seekable, (int)rv, (int)*stype);
|
||||
fflush(stderr);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -3417,6 +3614,7 @@ PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType,
|
||||
#endif
|
||||
|
||||
if (!mAccumulatedInvalidRect.IsEmpty()) {
|
||||
gJihadShowFrom[kJihadShowSetWindow]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
}
|
||||
@@ -3741,10 +3939,471 @@ PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow)
|
||||
mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, mWindow.clipRect.bottom));
|
||||
|
||||
if (mPluginIface->setwindow) {
|
||||
// WEBOS: hand the plugin an NpPalmWindow, exactly as Palm's host does.
|
||||
//
|
||||
// Out of the box this embedder passes NPWindow.window = nullptr for every windowless
|
||||
// plugin (DoAsyncSetWindow hard-nulls it, and the MOZ_WIDGET_HEADLESS branch of
|
||||
// AnswerNPP_SetWindow is a no-op), which is correct for an X11 windowless plugin — the
|
||||
// slot means "native window handle" there, and there isn't one. On webOS the same slot
|
||||
// means something completely different: it carries the plugin's visibility, the
|
||||
// surface depth and the page scale. A webOS plugin told window=NULL has been told
|
||||
// nothing about the surface it is supposed to draw into.
|
||||
//
|
||||
// Scoped to the call and re-nulled afterwards, because this is a stack address and
|
||||
// Palm's host is careful about exactly that (libWebKitLuna 0x4e7570 / 0x4e7614).
|
||||
//
|
||||
// scaleFactor is 1.0 rather than the card's fit-zoom: Palm passes ScrollView::getScale()
|
||||
// because its plugin drew into the page's own scaled surface, whereas here the plugin
|
||||
// owns an unscaled CSS-sized buffer and the compositor applies the zoom afterwards.
|
||||
JihadNpPalmWindow palmWindow;
|
||||
palmWindow.visible = true;
|
||||
palmWindow.bpp = 32;
|
||||
palmWindow.scaleFactor = 1.0;
|
||||
void* savedWindow = mWindow.window;
|
||||
mWindow.window = &palmWindow;
|
||||
|
||||
mPluginIface->setwindow(&mData, &mWindow);
|
||||
|
||||
mWindow.window = savedWindow;
|
||||
|
||||
static int sLoggedSetWindow = 0;
|
||||
if (sLoggedSetWindow < 3) {
|
||||
sLoggedSetWindow++;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPP_SetWindow %dx%d at %d,%d type=%d "
|
||||
"palmWindow{visible=1 bpp=32 scale=1.0}\n",
|
||||
(int)mWindow.width, (int)mWindow.height, (int)mWindow.x, (int)mWindow.y,
|
||||
(int)mWindow.type);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// WEBOS: the last thing Palm's PluginView::platformStart does is tell the plugin it has
|
||||
// focus — handlePageGainFocus (libWebKitLuna 0x4e6df0) builds
|
||||
// {eventType = 0x100 (npPalmSystemEvent), systemEvent.type = 3 (npPalmGainFocusEvent)}
|
||||
// and dispatches it. A plugin that is never told it is focused and visible has no
|
||||
// reason to start animating.
|
||||
//
|
||||
// Only gain-focus: Flash's own system-event dispatcher (libflashplayer.so 0x44da0)
|
||||
// routes npPalmPageLoadingEvent(8), npPalmPageLoadCompleteEvent(9) and
|
||||
// npPalmViewPortChangedEvent(10) to a bare `mov r0,#0; pop {pc}`, so those three are
|
||||
// inert in this build and sending them would prove nothing.
|
||||
//
|
||||
// After setwindow, not before, and once: the plugin needs its geometry first, and
|
||||
// UpdateWindowAttributes runs on every resize and every scroll.
|
||||
// (Palm's NPEvent exists only where npapi.h defines it: the headless toolkit the device
|
||||
// builds; the desktop GTK build has the X11 one, T-387)
|
||||
#if defined(XP_UNIX) && defined(MOZ_WIDGET_HEADLESS)
|
||||
if (mPluginIface->event && !mJihadSentPalmGainFocus) {
|
||||
mJihadSentPalmGainFocus = true;
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmSystemEvent;
|
||||
ev.data.systemEvent.type = npPalmGainFocusEvent;
|
||||
int16_t handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&ev));
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmSystemEvent gainFocus handled=%d\n",
|
||||
(int)handled);
|
||||
fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Run any work the plugin scheduled on glib.
|
||||
//
|
||||
// A webOS plugin is handed a GMainLoop through npPalmEventLoopValue and hangs its timers and
|
||||
// idle sources off it — but this process's main loop is chromium's MessageLoop, so unless
|
||||
// glib is pumped explicitly those sources NEVER RUN. The visible symptom is a plugin that
|
||||
// initialises, clears its stage, and then sits on frame zero forever: it is not stuck, it is
|
||||
// simply never being given the chance to advance.
|
||||
//
|
||||
// Bounded so a busy plugin cannot starve the paint it is being pumped for.
|
||||
static int
|
||||
JihadPumpGlib(int aMaxIterations)
|
||||
{
|
||||
typedef int (*GMainContextIterationFn)(void*, int);
|
||||
typedef int (*GMainContextPendingFn)(void*);
|
||||
static bool sTried = false;
|
||||
static GMainContextIterationFn sIterate = nullptr;
|
||||
static GMainContextPendingFn sPending = nullptr;
|
||||
|
||||
if (!sTried) {
|
||||
sTried = true;
|
||||
if (void* glib = dlopen("libglib-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
sIterate = (GMainContextIterationFn)dlsym(glib, "g_main_context_iteration");
|
||||
sPending = (GMainContextPendingFn)dlsym(glib, "g_main_context_pending");
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump iterate=%p pending=%p\n",
|
||||
(void*)sIterate, (void*)sPending);
|
||||
fflush(stderr);
|
||||
}
|
||||
if (!sIterate) {
|
||||
return 0;
|
||||
}
|
||||
int n = 0;
|
||||
while (n < aMaxIterations) {
|
||||
if (sPending && !sPending(nullptr)) {
|
||||
break;
|
||||
}
|
||||
sIterate(nullptr, 0 /* may_block = FALSE */);
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── RUN THE PLUGIN'S EVENT LOOP CONTINUOUSLY (Jihad, addons R7) ───────────────────────────
|
||||
//
|
||||
// Pumping glib only just before a paint deadlocks a webOS plugin, because its startup is
|
||||
// asynchronous: Flash registers com.palm.flashgraphics on the LS2 bus, attaches that socket to
|
||||
// the loop it was handed, and then waits on round-trips to com.palm.systemservice
|
||||
// (getPreferences), com.palm.display (status) and com.palm.db (find) before it starts its frame
|
||||
// clock. Until the clock runs it never calls NPN_InvalidateRect, so nothing ever asks it to
|
||||
// paint — and the only pump was on the paint path. No paint, no pump; no pump, no paint.
|
||||
//
|
||||
// So the pump has to be independent of painting. This reposts itself on the child's own
|
||||
// MessageLoop, which is the SAME thread every other NPAPI call into the plugin runs on — the
|
||||
// arrangement Palm's host gave it, since BrowserServer's main loop simply WAS a GMainLoop.
|
||||
// g_main_loop_run() on a dedicated thread would be the wrong shape: the plugin's timer
|
||||
// callbacks would then fire off the NPAPI thread and reenter us from it.
|
||||
//
|
||||
// MEASURED: before the LS2 role allowed com.palm.flashgraphics, LSRegisterPalmService failed
|
||||
// with -1027, nothing was ever attached to the loop, and g_main_context_pending() returned
|
||||
// FALSE forever. That is fixed in packaging/gen-variant-scripts.sh; the pump only has anything
|
||||
// to run because of it.
|
||||
static const int kJihadGlibPumpIntervalMs = 16; // ~60Hz, the rate a frame clock wants
|
||||
static const int kJihadGlibPumpMaxIters = 20; // bounded: never starve the MessageLoop
|
||||
|
||||
// Host-driven repaint divisor: one repaint every Nth 16 ms tick. 2 is ~30 Hz, which is what
|
||||
// ordinary Flash content is authored at; 4 was ~15 Hz and was the measured ceiling on the
|
||||
// WHOLE pipeline — device 2026-08-10, single instance: plugin draw 21.6-24 fps and RecvShow
|
||||
// 21.3-24.2 fps while the daemon was already compositing 28.4-29.4 fps, i.e. the host was
|
||||
// asking for frames more slowly than it could consume them.
|
||||
//
|
||||
// Env-tunable because the right value is a property of the device under load, not of the
|
||||
// source, and a rebuild per candidate costs a 44 MB push. Clamped: 0 or a negative divisor
|
||||
// would be a modulo-by-zero, and anything past 8 is slower than the pump itself.
|
||||
// Disable the child's OWN repaint timer entirely. $JIHAD_PLUGIN_SELF_DRIVE=0.
|
||||
//
|
||||
// This exists to isolate ONE producer. Measured 2026-08-10, there are two: our host pull and the
|
||||
// plugin invalidating itself (~26/s and ~23/s). Turning off only the daemon's pull
|
||||
// (JIHAD_PLUGIN_PULL=0) does NOT isolate the plugin, because the child's host-drive lease then
|
||||
// expires and this timer takes over — that run reproduces patch 0026 rather than answering the
|
||||
// question, and it measured WORSE than the pull on both rate (24-29 vs 30-33 fps) and spread.
|
||||
// With both switches off the plugin's own NPN_InvalidateRect is the only frame source left, which
|
||||
// is the experiment that decides whether a host clock should exist here at all.
|
||||
static bool JihadChildSelfDriveEnabled()
|
||||
{
|
||||
static int s_on = -1;
|
||||
if (s_on < 0) {
|
||||
const char* e = getenv("JIHAD_PLUGIN_SELF_DRIVE");
|
||||
s_on = (e && *e && atoi(e) == 0) ? 0 : 1; // default ON; only an explicit 0 disables
|
||||
fprintf(stderr, "[jihad-npapi-child] self-drive timer %s\n", s_on ? "enabled" : "DISABLED");
|
||||
fflush(stderr);
|
||||
}
|
||||
return s_on != 0;
|
||||
}
|
||||
|
||||
static int JihadRepaintEveryTicks()
|
||||
{
|
||||
static int sDiv = 0;
|
||||
if (!sDiv) {
|
||||
sDiv = 2;
|
||||
const char* e = getenv("JIHAD_PLUGIN_REPAINT_DIV");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 1 && v <= 8) sDiv = v;
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] host repaint divisor=%d (~%d Hz)\n",
|
||||
sDiv, 1000 / (kJihadGlibPumpIntervalMs * sDiv));
|
||||
fflush(stderr);
|
||||
}
|
||||
return sDiv;
|
||||
}
|
||||
|
||||
// ── REPAINT A webOS PLUGIN FROM THE HOST'S CLOCK (Jihad, addons R7) ──────────────────────
|
||||
//
|
||||
// Gecko paints a windowless plugin only when something invalidates it, and for an ordinary
|
||||
// NPAPI plugin that is the plugin itself calling NPN_InvalidateRect. A webOS plugin is under
|
||||
// no such obligation, because the host it was written for never made it one: Palm's
|
||||
// PluginView::paint (libWebKitLuna 0x4e7a3c) issues npPalmDrawEvent every time the PAGE
|
||||
// paints, and BrowserServer's compositor painted continuously. Flash on that host could sit
|
||||
// on its frame clock and simply expect to be asked.
|
||||
//
|
||||
// So this drives the repaint until the plugin proves it drives itself, then gets out of the
|
||||
// way — a plugin that calls NPN_InvalidateRect gets Gecko's normal damage-driven painting and
|
||||
// none of this cost. Measured: with no host clock, Flash produced exactly 3 draw events in
|
||||
// 30 s and 0 invalidates.
|
||||
void
|
||||
PluginInstanceChild::JihadPalmRepaintTick()
|
||||
{
|
||||
if (!mLayersRendering || mWindow.width <= 0 || mWindow.height <= 0) {
|
||||
return;
|
||||
}
|
||||
NPRect windowRect = { 0, 0,
|
||||
uint16_t(mWindow.height), uint16_t(mWindow.width) };
|
||||
sJihadInHostRepaint = true;
|
||||
InvalidateRect(&windowRect);
|
||||
sJihadInHostRepaint = false;
|
||||
}
|
||||
|
||||
// ── HOST-DRIVEN FRAME CLOCK (Jihad, addons R7) ───────────────────────────────────────────
|
||||
//
|
||||
// When the daemon is pulling (it calls jihad_plugin_request_frame after each card frame it
|
||||
// publishes), the timer above must get out of the way, or the plugin has TWO drivers and the
|
||||
// extra draws are exactly the drift the pull exists to remove.
|
||||
//
|
||||
// The handoff is timestamp-based rather than a latch because there is no unregister: the daemon
|
||||
// can stop asking at any moment — the card is backgrounded (BrowserPageGoanna::freeze returns
|
||||
// early from maybePaint), the page navigates, the daemon is stopped or crashes — and a latched
|
||||
// plugin would then never paint again. So host-driven is a LEASE that must be renewed. If no
|
||||
// request arrives for kJihadHostDriveLeaseMs the timer resumes on its own.
|
||||
//
|
||||
// THE LEASE MUST EXCEED THE DAEMON'S WORST-CASE GAP BETWEEN REQUESTS, and the first version of
|
||||
// this got that wrong in a way that is worth keeping written down, because the symptom looked
|
||||
// like a mysterious third party rather than like a bug.
|
||||
//
|
||||
// The daemon does not ask again while a request is outstanding; it waits out
|
||||
// kJihadPluginReqTimeoutMs (250 ms) before re-asking. The lease here was 150 ms — SHORTER — so a
|
||||
// single late frame guaranteed the lease expired, the child resumed self-driving, and the two
|
||||
// clocks this patch exists to collapse were both running again. Measured on device before the
|
||||
// fix: the child logged HOST -> SELF -> HOST, and the invalidate counter split almost exactly in
|
||||
// half, 1856 requests from the host against 1846 from the child's own timer, summing to the 3702
|
||||
// total. The 23% of "unexplained" draws that sent an earlier session hunting through
|
||||
// RecvUpdateBackground were this, and the background paths were measured at exactly ZERO.
|
||||
//
|
||||
// So the invariant, not a tuned number: lease > request timeout + one grid period + margin.
|
||||
// 500 ms is 250 + 33 + slack. A card that really stopped publishing still resumes self-drawing
|
||||
// in half a second, which is well inside what a resume can absorb.
|
||||
static const uint32_t kJihadHostDriveLeaseMs = 500;
|
||||
static uint32_t sJihadLastHostRequestMs = 0;
|
||||
static bool sJihadEverHostDriven = false;
|
||||
|
||||
static bool JihadHostIsDrivingFrames()
|
||||
{
|
||||
bool driving = false;
|
||||
if (sJihadEverHostDriven) {
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
// Unsigned subtraction, so the 32-bit PR_IntervalNow wrap is handled without a signed
|
||||
// compare that would read as "lease expired" for one interval every wrap.
|
||||
driving = (nowMs - sJihadLastHostRequestMs) < kJihadHostDriveLeaseMs;
|
||||
}
|
||||
|
||||
// LOG EVERY TRANSITION. A watchdog that silently resumes self-driving would make a BROKEN
|
||||
// pull look like "no change" in the frame numbers instead of "the feature is off" — the same
|
||||
// trap family as an empty log passing every grep, and a wedged plugin reading as a pacing
|
||||
// regression. Both of those cost real time in this tree already; this one is cheap to close.
|
||||
static bool sWas = false;
|
||||
if (driving != sWas) {
|
||||
sWas = driving;
|
||||
fprintf(stderr, "[jihad-npapi-child] frame clock -> %s\n",
|
||||
driving ? "HOST (daemon pull)" : "SELF (timer; host lease expired)");
|
||||
fflush(stderr);
|
||||
}
|
||||
return driving;
|
||||
}
|
||||
|
||||
bool
|
||||
PluginInstanceChild::RecvJihadRequestFrame()
|
||||
{
|
||||
sJihadEverHostDriven = true;
|
||||
sJihadLastHostRequestMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
|
||||
// A plugin that drives itself gets Gecko's normal damage-driven painting, exactly as the
|
||||
// timer path decides below; asking such a plugin for frames would double its draw rate.
|
||||
if (gJihadNPNInvalidateRectCalls != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint64_t sHostFrames = 0;
|
||||
static uint32_t sReportMs = 0;
|
||||
if (!(++sHostFrames % 64)) {
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (sReportMs) {
|
||||
fprintf(stderr, "[jihad-npapi-child] host-driven: 64 requests in %ums\n",
|
||||
nowMs - sReportMs);
|
||||
fflush(stderr);
|
||||
}
|
||||
sReportMs = nowMs;
|
||||
}
|
||||
|
||||
JihadPalmRepaintTick();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class JihadGlibPumpTask final : public Runnable
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
int n = JihadPumpGlib(kJihadGlibPumpMaxIters);
|
||||
|
||||
// The pump itself keeps running at 16 ms whatever drives the frames — Flash's LS2
|
||||
// socket, its timers and its audio sources all live on this GMainLoop, and starving
|
||||
// them is what deadlocked startup before patch 0026. Only the REPAINT defers.
|
||||
static uint64_t sRepaintTicks = 0;
|
||||
if (sJihadPalmInstance && gJihadNPNInvalidateRectCalls == 0 &&
|
||||
JihadChildSelfDriveEnabled() && !JihadHostIsDrivingFrames() &&
|
||||
(++sRepaintTicks % JihadRepaintEveryTicks()) == 0) {
|
||||
sJihadPalmInstance->JihadPalmRepaintTick();
|
||||
}
|
||||
|
||||
// One-shot liveness, because "the plugin is idle" and "the pump died" look
|
||||
// identical on screen. Report the first tick that actually ran a source.
|
||||
static bool sSawFirstSource = false;
|
||||
static uint64_t sTicks = 0;
|
||||
sTicks++;
|
||||
if (n && !sSawFirstSource) {
|
||||
sSawFirstSource = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: first source ran on tick %llu\n",
|
||||
(unsigned long long)sTicks);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
MessageLoop::current()->PostDelayedTask(
|
||||
RefPtr<Runnable>(new JihadGlibPumpTask()).forget(),
|
||||
kJihadGlibPumpIntervalMs);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Idempotent: several plugin instances share one GMainLoop, so they share one pump.
|
||||
static void
|
||||
JihadStartGlibPump(PluginInstanceChild* aInstance)
|
||||
{
|
||||
sJihadPalmInstance = aInstance;
|
||||
static bool sStarted = false;
|
||||
if (sStarted) {
|
||||
return;
|
||||
}
|
||||
MessageLoop* loop = MessageLoop::current();
|
||||
if (!loop) {
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: no MessageLoop, not starting\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
sStarted = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] glib pump: starting, every %dms\n",
|
||||
kJihadGlibPumpIntervalMs);
|
||||
fflush(stderr);
|
||||
loop->PostDelayedTask(RefPtr<Runnable>(new JihadGlibPumpTask()).forget(),
|
||||
kJihadGlibPumpIntervalMs);
|
||||
}
|
||||
|
||||
// ── PIRANHA CONTEXT FOR A WEBOS PLUGIN (Jihad, addons R7) ────────────────────────────────
|
||||
//
|
||||
// webOS plugins that answer npPalmUseGraphicsContext=true expect NpPalmDrawEvent to carry a
|
||||
// Piranha PGContext* rather than a raster pointer. Piranha ships on the device inside
|
||||
// libWebKitLuna.so (PGContext/PGSurface) and libPiranha.so; neither is linked into this
|
||||
// process, so everything is resolved by dlsym on the mangled names — no webOS headers, and
|
||||
// nothing breaks on a desktop build where the libraries simply are not there.
|
||||
//
|
||||
// Feasibility was established separately before any of this was written; see
|
||||
// render/goanna/test/piranha_offscreen_probe.c, which does exactly this sequence in a bare
|
||||
// process and confirms the pixels land in caller-owned memory.
|
||||
//
|
||||
// The context is cached per (buffer,size) because a plugin repaints constantly and both
|
||||
// objects would otherwise leak once per frame.
|
||||
static void*
|
||||
JihadPiranhaContextFor(unsigned char* aData, int aWidth, int aHeight, int aStride)
|
||||
{
|
||||
// PGSurface::wrap() has no stride parameter, so it can only describe tightly packed
|
||||
// rows. Refuse rather than hand Piranha a buffer it would misread into a sheared image.
|
||||
if (!aData || aWidth <= 0 || aHeight <= 0 || aStride != aWidth * 4) {
|
||||
static bool sWarnedStride = false;
|
||||
if (!sWarnedStride) {
|
||||
sWarnedStride = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha: stride %d != %d*4, not wrapping\n",
|
||||
aStride, aWidth);
|
||||
fflush(stderr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
typedef void* (*PGSurfaceWrapFn)(unsigned, unsigned, const unsigned char*, bool);
|
||||
typedef void* (*PGContextCreateFn)(void);
|
||||
typedef void (*PGContextSetSurfaceFn)(void*, void*);
|
||||
|
||||
static bool sTried = false;
|
||||
static PGSurfaceWrapFn sWrap = nullptr;
|
||||
static PGContextCreateFn sCreate = nullptr;
|
||||
static PGContextSetSurfaceFn sSetSurface = nullptr;
|
||||
|
||||
if (!sTried) {
|
||||
sTried = true;
|
||||
// g_thread_init() MUST run before libWebKitLuna loads or its constructors abort with
|
||||
// "GLib-ERROR **: The thread system is not yet initialized". It lives in
|
||||
// libgthread-2.0.so.0, NOT libglib-2.0.so.0. Measured, not guessed.
|
||||
if (void* gthread = dlopen("libgthread-2.0.so.0", RTLD_NOW | RTLD_GLOBAL)) {
|
||||
typedef void (*GThreadInitFn)(void*);
|
||||
if (GThreadInitFn init = (GThreadInitFn)dlsym(gthread, "g_thread_init")) {
|
||||
typedef int (*GThreadGetInitFn)(void);
|
||||
GThreadGetInitFn got =
|
||||
(GThreadGetInitFn)dlsym(gthread, "g_thread_get_initialized");
|
||||
if (!got || !got()) {
|
||||
init(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
void* wk = dlopen("libWebKitLuna.so", RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!wk) {
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha: no libWebKitLuna (%s)\n", dlerror());
|
||||
fflush(stderr);
|
||||
} else {
|
||||
sWrap = (PGSurfaceWrapFn)dlsym(wk, "_ZN9PGSurface4wrapEjjPKhb");
|
||||
sCreate = (PGContextCreateFn)dlsym(wk, "_ZN9PGContext6createEv");
|
||||
sSetSurface = (PGContextSetSurfaceFn)dlsym(wk, "_ZN9PGContext10setSurfaceEP9PGSurface");
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha syms wrap=%p create=%p setSurface=%p\n",
|
||||
(void*)sWrap, (void*)sCreate, (void*)sSetSurface);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
if (!sWrap || !sCreate || !sSetSurface) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// A SET of cached contexts, not one. Palm got away with a single context because its
|
||||
// buffer was one process-global screen-sized allocation that never moved; here the child
|
||||
// rotates between a front and a back gfxSharedImageSurface, so a one-entry cache keyed on
|
||||
// the buffer address misses on EVERY frame. That was invisible while the plugin was only
|
||||
// painted twice, and turned into a PGSurface + PGContext leaked per frame the moment the
|
||||
// host started driving repaints at 15Hz.
|
||||
//
|
||||
// Four entries: two buffers is the observed case, and a small fixed array keeps this
|
||||
// allocation-free and bounded even if the surface pool grows. On overflow the oldest entry
|
||||
// is dropped — leaking that one context rather than freeing an object the plugin may still
|
||||
// be holding, which is the safer of the two mistakes and is bounded by the buffer count.
|
||||
struct CacheEntry { unsigned char* data; int w, h; void* ctx; };
|
||||
static CacheEntry sCache[4] = {};
|
||||
static unsigned sCacheNext = 0;
|
||||
|
||||
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sCache); ++i) {
|
||||
if (sCache[i].ctx && sCache[i].data == aData &&
|
||||
sCache[i].w == aWidth && sCache[i].h == aHeight) {
|
||||
return sCache[i].ctx;
|
||||
}
|
||||
}
|
||||
|
||||
void* surf = sWrap((unsigned)aWidth, (unsigned)aHeight, aData, true);
|
||||
void* ctx = surf ? sCreate() : nullptr;
|
||||
if (ctx) {
|
||||
sSetSurface(ctx, surf);
|
||||
}
|
||||
// Rate-limited: this is now a cache MISS log, and a miss storm is exactly the symptom
|
||||
// worth seeing — but only the first few, or it floods the daemon log at frame rate.
|
||||
static int sLoggedCtx = 0;
|
||||
if (sLoggedCtx < 8) {
|
||||
sLoggedCtx++;
|
||||
fprintf(stderr, "[jihad-npapi-child] piranha ctx %dx%d buf=%p surface=%p context=%p\n",
|
||||
aWidth, aHeight, aData, surf, ctx);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
CacheEntry& slot = sCache[sCacheNext % MOZ_ARRAY_LENGTH(sCache)];
|
||||
sCacheNext++;
|
||||
slot.data = aData; slot.w = aWidth; slot.h = aHeight; slot.ctx = ctx;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void
|
||||
PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect,
|
||||
gfxASurface* aSurface)
|
||||
@@ -3799,7 +4458,187 @@ PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect,
|
||||
::IntersectClipRect((HDC) mWindow.window, rect.left, rect.top, rect.right, rect.bottom);
|
||||
mPluginIface->event(&mData, reinterpret_cast<void*>(&paintEvent));
|
||||
#else
|
||||
NS_RUNTIMEABORT("Surface type not implemented.");
|
||||
// No platform surface to paint onto.
|
||||
//
|
||||
// This is the CLASSIC windowless paint path, and on Unix it is X11 and nothing else: the
|
||||
// browser hands the plugin a Drawable and sends it an XGraphicsExposeEvent. A build with
|
||||
// no X (a headless toolkit) has nothing it can hand over, and NPAPI defines no
|
||||
// alternative for Unix — a plugin that wants to draw without X has to opt into a bitmap
|
||||
// drawing model (NPDrawingModelAsyncBitmapSurface), which is a different path entirely
|
||||
// and is fully supported here.
|
||||
//
|
||||
// Aborting is wrong for an embedder: the plugin process dies, the parent respawns it,
|
||||
// and the whole cycle repeats for every paint, so one plugin that cannot draw becomes an
|
||||
// endless crash loop. Warn once and leave the surface untouched instead — the plugin
|
||||
// stays loaded and scriptable and simply shows nothing, which is a bounded failure the
|
||||
// embedder can report.
|
||||
// fprintf, not NS_WARNING: NS_WARNING compiles to nothing in a release build, and this is
|
||||
// the ONLY signal distinguishing "the plugin drew nothing" from "the plugin was never
|
||||
// asked to draw" — a distinction that is invisible on screen (both are a blank box).
|
||||
// WEBOS RASTER DRAW EVENT (Jihad, addons R7).
|
||||
//
|
||||
// webOS plugins do not use the X11 protocol at all — they are painted by an
|
||||
// npPalmDrawEvent, and NpPalmDrawEvent carries BOTH a raster destination (dstBuffer +
|
||||
// dstRowBytes, "only valid in API version 1.0") and a Piranha PGContext
|
||||
// ("must be used to draw in API version 2.0"). See Palm's own npapi.h, XP_WEBOS block.
|
||||
//
|
||||
// The device's Flash answers npPalmUseGraphicsContext = true, which per that header means
|
||||
// the HOST may then pass a null raster — it is a statement about host behaviour, NOT a
|
||||
// promise that the plugin refuses a non-null dstBuffer. There is no API-version
|
||||
// negotiation anywhere in the contract, so the only way to find out whether a plugin
|
||||
// honours the raster path is to hand it one and look. That is what this does.
|
||||
//
|
||||
// The surface here is already an ordinary shared-memory image (gfxSharedImageSurface,
|
||||
// created a few hundred lines up), living in this process, whose pixels the parent
|
||||
// already composites — so the raster the plugin needs costs nothing to produce.
|
||||
if (aSurface->GetType() == gfxSurfaceType::Image) {
|
||||
gfxImageSurface* img = static_cast<gfxImageSurface*>(aSurface);
|
||||
uint8_t* data = img->Data();
|
||||
int32_t stride = img->Stride();
|
||||
|
||||
if (data && stride > 0) {
|
||||
// NPEvent is Palm's own event union on this platform (npapi.h); the offsets it
|
||||
// depends on are asserted at the top of this file against libWebKitLuna.
|
||||
// THE DRAW ALWAYS COVERS THE WHOLE SURFACE, whatever sub-rect was requested.
|
||||
//
|
||||
// Two things forced this. The PGContext below is built for the FULL surface
|
||||
// (PGSurface::wrap takes width/height and assumes tightly packed rows), so pairing
|
||||
// it with a dstBuffer offset into the middle of that surface makes the plugin's
|
||||
// blit start partway in and run off the end — measured as `FAULT sig=7` (SIGBUS)
|
||||
// in the plugin child the moment the host began sending partial invalidations.
|
||||
// And Palm's own host never sent a partial rect either: PluginView::paint passes
|
||||
// the plugin's whole frame rect, which is why a 2.0 plugin is entitled to assume
|
||||
// the two agree. Redrawing the whole plugin box costs the plugin nothing it was
|
||||
// not already doing every frame.
|
||||
nsIntRect drawRect(0, 0, img->Width(), img->Height());
|
||||
NPEvent ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.eventType = npPalmDrawEvent;
|
||||
// "points to top-left of area to paint"
|
||||
ev.data.drawEvent.dstBuffer = data;
|
||||
ev.data.drawEvent.dstRowBytes = (uint32_t)stride;
|
||||
ev.data.drawEvent.srcLeft = drawRect.x;
|
||||
ev.data.drawEvent.srcTop = drawRect.y;
|
||||
ev.data.drawEvent.srcRight = drawRect.XMost();
|
||||
ev.data.drawEvent.srcBottom = drawRect.YMost();
|
||||
ev.data.drawEvent.dstLeft = ev.data.drawEvent.srcLeft;
|
||||
ev.data.drawEvent.dstTop = ev.data.drawEvent.srcTop;
|
||||
ev.data.drawEvent.dstRight = ev.data.drawEvent.srcRight;
|
||||
ev.data.drawEvent.dstBottom = ev.data.drawEvent.srcBottom;
|
||||
// API 2.0: a real Piranha PGContext bound to the very same pixels.
|
||||
//
|
||||
// The device's Flash answers npPalmUseGraphicsContext = true, and although it
|
||||
// accepts a raster draw event (returns handled=1) it writes nothing into the
|
||||
// buffer — so "handled" evidently means "consumed", not "drew". This gives it
|
||||
// what it actually asked for. Proven possible by
|
||||
// render/goanna/test/piranha_offscreen_probe.c: a PGContext can be created over
|
||||
// wrapped memory in an ordinary process with no display and no compositor.
|
||||
//
|
||||
// PGSurface::wrap() takes no stride, so it assumes tightly-packed w*4 rows. That
|
||||
// holds here (a 320-wide surface has stride 1280) but is asserted rather than
|
||||
// assumed, because a padded stride would silently shear the image.
|
||||
ev.data.drawEvent.graphicsContext = JihadPiranhaContextFor(data, img->Width(),
|
||||
img->Height(), stride);
|
||||
// BOTH fields stay populated, including for a plugin that answered
|
||||
// npPalmUseGraphicsContext = true.
|
||||
//
|
||||
// npapi.h says the raster pointer "is then passed in as null" for such plugins, and
|
||||
// this code used to believe it. The host that actually shipped does not: PluginView
|
||||
// ::paint (libWebKitLuna 0x4e7c70 / 0x4e7c5c) stores a live dstBuffer and a live
|
||||
// dstRowBytes into the event on the SAME branch that stores the PGContext — the
|
||||
// graphics-context case is precisely the one where it computes
|
||||
// dstBuffer = <shared buffer> + (y*screenWidth + x)*4 and
|
||||
// dstRowBytes = screenWidth*4 (0x4e7e04-0x4e7e18). The header comment describes
|
||||
// what a 2.0 plugin is expected to USE, not what the host is allowed to omit, and
|
||||
// handing Flash a null raster is a difference from the reference host that costs
|
||||
// nothing to remove.
|
||||
|
||||
// Let the plugin's own scheduled work run before we ask it to draw, so the
|
||||
// frame it paints is current rather than whatever it had at instantiation.
|
||||
JihadPumpGlib(50);
|
||||
|
||||
int16_t handled = mPluginIface->event
|
||||
? mPluginIface->event(&mData, reinterpret_cast<void*>(&ev)) : -1;
|
||||
gJihadPalmDrawEvents++;
|
||||
|
||||
static int sLogged = 0;
|
||||
// FRAME-RATE READOUT. A static SWF and a 30fps game are indistinguishable from
|
||||
// a per-event log (capped) or a screenshot (one instant), and the difference is
|
||||
// the whole question for animated content. One line every 2s costs nothing and
|
||||
// is the only way to see the plugin's real cadence in the field.
|
||||
{
|
||||
static uint32_t sDraws = 0;
|
||||
static uint32_t sLastReport = 0;
|
||||
sDraws++;
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (!sLastReport) {
|
||||
sLastReport = nowMs;
|
||||
} else if (nowMs - sLastReport >= 2000) {
|
||||
fprintf(stderr, "[jihad-npapi-child] palm draw: %u in %ums (%.1f fps)\n",
|
||||
sDraws, nowMs - sLastReport,
|
||||
sDraws * 1000.0 / double(nowMs - sLastReport));
|
||||
// WHICH SITE ASKED FOR THOSE DRAWS. These are requests to draw, not draws:
|
||||
// AsyncShowPluginFrame coalesces while an invalidate task is already pending,
|
||||
// so the sum is an UPPER bound on sDraws and the interesting number is the
|
||||
// SHARE each site holds, not the total. Printed on the same 2 s boundary so a
|
||||
// reader can line them up without correlating timestamps.
|
||||
{
|
||||
char buf[192];
|
||||
size_t n = 0;
|
||||
buf[0] = '\0';
|
||||
for (int i = 0; i < kJihadShowSites; ++i) {
|
||||
// Clamp rather than trust the running total: snprintf returns the
|
||||
// length it WANTED, so an overflow would make the next call pass a
|
||||
// negative size as size_t and write off the end.
|
||||
if (n < sizeof(buf) - 1) {
|
||||
int w = snprintf(buf + n, sizeof(buf) - n, " %s=%u",
|
||||
kJihadShowNames[i], gJihadShowFrom[i]);
|
||||
if (w > 0) {
|
||||
n += size_t(w);
|
||||
if (n >= sizeof(buf)) n = sizeof(buf) - 1;
|
||||
}
|
||||
}
|
||||
gJihadShowFrom[i] = 0;
|
||||
}
|
||||
fprintf(stderr, "[jihad-npapi-child] show req:%s\n", buf);
|
||||
}
|
||||
fflush(stderr);
|
||||
sDraws = 0;
|
||||
sLastReport = nowMs;
|
||||
}
|
||||
}
|
||||
if (sLogged < 3) {
|
||||
sLogged++;
|
||||
// handled= is reported but means nothing on its own: Flash's NPP_HandleEvent
|
||||
// sets its return to 1 (libflashplayer.so 0x45274) BEFORE calling the draw
|
||||
// handler and returns it unconditionally, so handled=1 is emitted even when
|
||||
// the handler bails without touching the surface. Only pixels are evidence.
|
||||
fprintf(stderr, "[jihad-npapi-child] npPalmDrawEvent %s %d,%d %dx%d "
|
||||
"stride=%d ctx=%p handled=%d\n",
|
||||
ev.data.drawEvent.graphicsContext ? "pgcontext" : "raster",
|
||||
drawRect.x, drawRect.y, drawRect.width, drawRect.height,
|
||||
(int)stride, ev.data.drawEvent.graphicsContext, (int)handled);
|
||||
fflush(stderr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing we can paint onto at all.
|
||||
//
|
||||
// Aborting is wrong for an embedder: the plugin process dies, the parent respawns it,
|
||||
// and the whole cycle repeats for every paint, so one plugin that cannot draw becomes an
|
||||
// endless crash loop. Warn once and leave the surface untouched instead.
|
||||
// fprintf, not NS_WARNING: NS_WARNING compiles to nothing in a release build, and this is
|
||||
// the ONLY signal distinguishing "the plugin drew nothing" from "the plugin was never
|
||||
// asked to draw" — a distinction that is invisible on screen (both are a blank box).
|
||||
static bool sWarned = false;
|
||||
if (!sWarned) {
|
||||
sWarned = true;
|
||||
fprintf(stderr, "[jihad-npapi-child] no paintable surface for this windowless "
|
||||
"plugin (type=%d); it will NOT paint.\n", (int)aSurface->GetType());
|
||||
fflush(stderr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4264,6 +5103,7 @@ PluginInstanceChild::InvalidateRectDelayed(void)
|
||||
}
|
||||
|
||||
if (!ShowPluginFrame()) {
|
||||
gJihadShowFrom[kJihadShowRetry]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
}
|
||||
@@ -4317,6 +5157,8 @@ PluginInstanceChild::InvalidateRect(NPRect* aInvalidRect)
|
||||
mAccumulatedInvalidRect.UnionRect(r, mAccumulatedInvalidRect);
|
||||
// If we are able to paint and invalidate sent, then reset
|
||||
// accumulated rectangle
|
||||
gJihadShowFrom[sJihadInHostRepaint ? kJihadShowInvalidate
|
||||
: kJihadShowPluginSelf]++;
|
||||
AsyncShowPluginFrame();
|
||||
return;
|
||||
}
|
||||
@@ -4360,6 +5202,7 @@ PluginInstanceChild::RecvUpdateBackground(const SurfaceDescriptor& aBackground,
|
||||
IntSize bgSize = mBackground->GetSize();
|
||||
mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect,
|
||||
nsIntRect(0, 0, bgSize.width, bgSize.height));
|
||||
gJihadShowFrom[kJihadShowBgUpdate]++;
|
||||
AsyncShowPluginFrame();
|
||||
return true;
|
||||
}
|
||||
@@ -4369,6 +5212,7 @@ PluginInstanceChild::RecvUpdateBackground(const SurfaceDescriptor& aBackground,
|
||||
|
||||
// This must be asynchronous, because we may be nested within RPC messages
|
||||
// which do not expect to receiving paint events.
|
||||
gJihadShowFrom[kJihadShowBgUpdateTail]++;
|
||||
AsyncShowPluginFrame();
|
||||
|
||||
return true;
|
||||
@@ -4401,6 +5245,7 @@ PluginInstanceChild::RecvPPluginBackgroundDestroyerConstructor(
|
||||
// NB: we don't have to XSync here because only ShowPluginFrame()
|
||||
// uses mBackground, and it always XSyncs after finishing.
|
||||
mBackground = nullptr;
|
||||
gJihadShowFrom[kJihadShowBgDestroy]++;
|
||||
AsyncShowPluginFrame();
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,10 @@ protected:
|
||||
virtual bool
|
||||
RecvContentsScaleFactorChanged(const double& aContentsScaleFactor) override;
|
||||
|
||||
// Jihad: the host is driving the frame clock — draw one now. See PPluginInstance.ipdl.
|
||||
virtual bool
|
||||
RecvJihadRequestFrame() override;
|
||||
|
||||
virtual bool
|
||||
AnswerNPP_Destroy(NPError* result) override;
|
||||
|
||||
@@ -641,6 +645,22 @@ private:
|
||||
// and p->getvalue return always false
|
||||
bool mIsTransparent;
|
||||
|
||||
// Jihad R7 (webOS): npPalmGainFocusEvent is a once-per-instance startup signal, but the
|
||||
// setwindow that carries it runs again on every resize and scroll.
|
||||
bool mJihadSentPalmGainFocus;
|
||||
|
||||
// Jihad R7 (webOS): cap on the input-delivery breadcrumb in AnswerNPP_HandleEvent. A
|
||||
// pen-move stream is unbounded; the daemon log is the only field-debug channel there is.
|
||||
uint32_t mJihadInputEventsLogged;
|
||||
|
||||
public:
|
||||
// Jihad R7 (webOS): repaint this instance from the host's own clock. See
|
||||
// JihadGlibPumpTask::Run — a webOS plugin is not obliged to ask for repaints, because the
|
||||
// host it was written for painted it as part of every page paint.
|
||||
void JihadPalmRepaintTick();
|
||||
|
||||
private:
|
||||
|
||||
// Surface type optimized of parent process
|
||||
gfxSurfaceType mSurfaceType;
|
||||
|
||||
|
||||
@@ -115,6 +115,16 @@ PluginInstanceParent::LookupPluginInstanceByID(uintptr_t aId)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Jihad: every live instance, so a C entry point can reach one. jihad_plugin_frame_seq needs no
|
||||
// instance because it only counts; a frame REQUEST has to be sent on an actual actor.
|
||||
//
|
||||
// A plain file-static array maintained in the ctor/dtor below, not a hashtable: this is at most a
|
||||
// couple of entries on a single-card embedding, it is only ever touched on the main thread (the
|
||||
// daemon calls the export from inside its own tick, on that same thread), and the alternative —
|
||||
// walking nsPluginHost's nsNPAPIPluginInstance list the way jihad_plugin_palm_spotlight does —
|
||||
// would still have to get from there to the PluginInstanceParent actor to call Send on it.
|
||||
static nsTArray<PluginInstanceParent*>* gJihadLiveInstances = nullptr;
|
||||
|
||||
PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
|
||||
NPP npp,
|
||||
const nsCString& aMimeType,
|
||||
@@ -145,10 +155,24 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
|
||||
sPluginInstanceList = new nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>();
|
||||
}
|
||||
#endif
|
||||
// Jihad: join the live list that jihad_plugin_request_frame walks. Leaked on purpose — it
|
||||
// outlives every instance and there is no shutdown hook here that is guaranteed to run
|
||||
// after the last dtor.
|
||||
if (!gJihadLiveInstances) {
|
||||
gJihadLiveInstances = new nsTArray<PluginInstanceParent*>();
|
||||
}
|
||||
gJihadLiveInstances->AppendElement(this);
|
||||
}
|
||||
|
||||
PluginInstanceParent::~PluginInstanceParent()
|
||||
{
|
||||
// Jihad: leave the live list FIRST. Anything below can run script or spin the loop, and a
|
||||
// frame request delivered to a half-destroyed actor is a use-after-free rather than a
|
||||
// dropped frame.
|
||||
if (gJihadLiveInstances) {
|
||||
gJihadLiveInstances->RemoveElement(this);
|
||||
}
|
||||
|
||||
if (mNPP)
|
||||
mNPP->pdata = nullptr;
|
||||
|
||||
@@ -269,6 +293,8 @@ PluginInstanceParent::AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle*
|
||||
XID id;
|
||||
#elif defined(XP_DARWIN)
|
||||
intptr_t id;
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
intptr_t id; // Jihad headless: no native window id
|
||||
#else
|
||||
#warning Implement me
|
||||
#endif
|
||||
@@ -769,6 +795,51 @@ PluginInstanceParent::RecvShowDirectBitmap(Shmem&& buffer,
|
||||
return false;
|
||||
}
|
||||
|
||||
// NO COMPOSITOR PROCESS: composite the plugin's bitmap directly.
|
||||
//
|
||||
// Everything below this point routes the plugin's pixels through a TextureClient to a
|
||||
// compositor, and that requires ImageBridgeChild — which only exists with off-main-thread
|
||||
// composition. This embedder deliberately runs OMTC off (the offscreen renderer uses the
|
||||
// in-process BasicLayerManager and hands finished frames to the browser adapter itself),
|
||||
// so ImageBridgeChild::GetSingleton() is null and TextureClientRecycleAllocator is built
|
||||
// around a null KnowsCompositor. The first plugin paint then dereferenced it and took the
|
||||
// whole daemon down with it — upstream never sees this because the DXGI path a few
|
||||
// hundred lines up checks the forwarder and the direct-bitmap path does not.
|
||||
//
|
||||
// A BasicLayerManager does not need a TextureClient at all: it draws an ImageLayer from
|
||||
// the image's own SourceSurface. So wrap the pixels in a plain SourceSurfaceImage and
|
||||
// hand that to the container.
|
||||
//
|
||||
// The pixels are COPIED, not wrapped. `source` above wraps the shmem the plugin drew
|
||||
// into, and the plugin owns that buffer and may draw the next frame into it as soon as
|
||||
// this call returns; the texture path's UpdateFromSurface copies for the same reason.
|
||||
if (!ImageBridgeChild::GetSingleton()) {
|
||||
RefPtr<gfx::DataSourceSurface> copy =
|
||||
gfx::Factory::CreateDataSourceSurface(size, format);
|
||||
if (!copy) {
|
||||
NS_WARNING("Could not allocate a surface for plugin bitmap!");
|
||||
return false;
|
||||
}
|
||||
{
|
||||
gfx::DataSourceSurface::ScopedMap srcMap(source, gfx::DataSourceSurface::READ);
|
||||
gfx::DataSourceSurface::ScopedMap dstMap(copy, gfx::DataSourceSurface::WRITE);
|
||||
if (!srcMap.IsMapped() || !dstMap.IsMapped()) {
|
||||
return false;
|
||||
}
|
||||
const int32_t bpp = BytesPerPixel(format);
|
||||
const int32_t rowBytes = size.width * bpp;
|
||||
for (int32_t y = 0; y < size.height; y++) {
|
||||
memcpy(dstMap.GetData() + (ptrdiff_t)y * dstMap.GetStride(),
|
||||
srcMap.GetData() + (ptrdiff_t)y * srcMap.GetStride(),
|
||||
rowBytes);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(size, copy);
|
||||
SetCurrentImage(image);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocate a texture for the compositor.
|
||||
RefPtr<TextureClientRecycleAllocator> allocator = mParent->EnsureTextureAllocatorForDirectBitmap();
|
||||
RefPtr<TextureClient> texture = allocator->CreateOrRecycle(
|
||||
@@ -864,6 +935,55 @@ PluginInstanceParent::RecvShowDirectDXGISurface(const WindowsHandle& handle,
|
||||
}
|
||||
#endif // MOZ_ENABLE_NPAPI
|
||||
|
||||
// Jihad: monotonic count of plugin frames that have reached the parent. Process-wide rather
|
||||
// than per-instance, matching how the daemon consumes it — the embedding is single-page and the
|
||||
// question it answers is "has ANY plugin produced a new frame since I last published". A plain
|
||||
// uint32_t incremented and read on the same (main) thread; the daemon reads it from the same
|
||||
// thread inside its tick, so no atomics are needed and none are used elsewhere in this bridge.
|
||||
static uint32_t gJihadPluginFrameSeq = 0;
|
||||
|
||||
extern "C" {
|
||||
// Weak-linked from the daemon (render/goanna/GoannaRenderPage.cpp), the same way
|
||||
// jihad_offscreen_take_dirty is: an older libxul simply does not export it, and the daemon then
|
||||
// falls back to its dirty-flag behaviour instead of failing to load.
|
||||
MOZ_EXPORT uint32_t jihad_plugin_frame_seq(void)
|
||||
{
|
||||
return gJihadPluginFrameSeq;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// Ask every live plugin for one frame. Returns how many were asked, so the daemon can tell "no
|
||||
// plugin" from "delivered" — the same contract as jihad_plugin_palm_spotlight.
|
||||
//
|
||||
// WEAK on the daemon side: a libxul without this symbol keeps the child's own timer as the frame
|
||||
// clock (patch 0026 behaviour), which is slower to settle but never wrong.
|
||||
MOZ_EXPORT int jihad_plugin_request_frame(void)
|
||||
{
|
||||
#ifdef MOZ_ENABLE_NPAPI
|
||||
if (!gJihadLiveInstances) {
|
||||
return 0;
|
||||
}
|
||||
// Copy first: SendJihadRequestFrame can fail and tear the actor down, which would mutate the
|
||||
// array underneath the loop. Same reasoning as the instance-list copy in
|
||||
// jihad_plugin_palm_spotlight (nsPluginHost.cpp).
|
||||
nsTArray<PluginInstanceParent*> snap(*gJihadLiveInstances);
|
||||
int asked = 0;
|
||||
for (uint32_t i = 0; i < snap.Length(); ++i) {
|
||||
if (!gJihadLiveInstances->Contains(snap[i])) {
|
||||
continue; // torn down by an earlier Send in this same loop
|
||||
}
|
||||
if (snap[i]->SendJihadRequestFrame()) {
|
||||
asked++;
|
||||
}
|
||||
}
|
||||
return asked;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
const SurfaceDescriptor& newSurface,
|
||||
@@ -877,6 +997,27 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
|
||||
MOZ_ASSERT(!IsUsingDirectDrawing());
|
||||
|
||||
// FRAME-RATE READOUT, parent side. Paired with the child's `palm draw:` line: the child
|
||||
// reports how often the plugin was ASKED to draw, this reports how many of those frames
|
||||
// actually crossed the process boundary. A gap between the two numbers is the difference
|
||||
// between "the plugin is slow" and "we are dropping its frames", which are opposite bugs.
|
||||
{
|
||||
static uint32_t sShows = 0;
|
||||
static uint32_t sLastReport = 0;
|
||||
sShows++;
|
||||
uint32_t nowMs = uint32_t(PR_IntervalToMilliseconds(PR_IntervalNow()));
|
||||
if (!sLastReport) {
|
||||
sLastReport = nowMs;
|
||||
} else if (nowMs - sLastReport >= 2000) {
|
||||
fprintf(stderr, "[jihad-npapi] RecvShow: %u in %ums (%.1f fps)\n",
|
||||
sShows, nowMs - sLastReport,
|
||||
sShows * 1000.0 / double(nowMs - sLastReport));
|
||||
fflush(stderr);
|
||||
sShows = 0;
|
||||
sLastReport = nowMs;
|
||||
}
|
||||
}
|
||||
|
||||
// XXXjwatt rewrite to use Moz2D
|
||||
RefPtr<gfxASurface> surface;
|
||||
if (newSurface.type() == SurfaceDescriptor::TShmem) {
|
||||
@@ -959,14 +1100,75 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
updatedRect.bottom - updatedRect.top);
|
||||
surface->MarkDirty(ur);
|
||||
|
||||
bool isPlugin = true;
|
||||
RefPtr<gfx::SourceSurface> sourceSurface =
|
||||
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin);
|
||||
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface);
|
||||
// JIHAD (2026-08-09): route the legacy windowless show through the SAME
|
||||
// representation the direct-bitmap path uses, for the SAME reasons.
|
||||
//
|
||||
// Background: the webOS npPalmDrawEvent path (PaintRectToPlatformSurface) makes Flash
|
||||
// blit its stage into mCurrentSurface — measured full-magenta in the child — and ships
|
||||
// it here via SendShow. The control plugin's pixels reach the screen through
|
||||
// RecvShowDirectBitmap; Flash's did not, although both feed the one mImageContainer the
|
||||
// BasicLayerManager reads. Two concrete differences made the legacy path invisible:
|
||||
// 1. It WRAPPED the shmem (GetSourceSurfaceForSurface(nullptr,…)) instead of copying.
|
||||
// The child double-buffers and SwapSurfaces() immediately after SendShow returns,
|
||||
// so a wrapped front buffer is the very buffer the child repaints next — the image
|
||||
// the layer later samples is racing the child. RecvShowDirectBitmap copies for
|
||||
// exactly this reason ("the plugin owns that buffer and may draw the next frame
|
||||
// into it as soon as this call returns").
|
||||
// 2. It stamped no frameID. SetCurrentImage() on the working path assigns a monotonic
|
||||
// ++mFrameID; the ImageContainer/ImageLayer treats a repeated frameID as "no new
|
||||
// frame". A fixed 0 can be coalesced away.
|
||||
// Copying into a fresh DataSourceSurface and stamping a monotonic frameID makes this
|
||||
// path identical in substance to the one already proven to composite here.
|
||||
IntSize ssize = surface->GetSize();
|
||||
RefPtr<Image> image;
|
||||
RefPtr<gfxImageSurface> imgSurf = surface->GetAsImageSurface();
|
||||
if (imgSurf) {
|
||||
// Always present the copy as B8G8R8A8, and force the alpha channel opaque when the
|
||||
// source carries no alpha (RGB24 / B8G8R8X8). This is Bug 1196927 in this file's own
|
||||
// words (see PluginInstanceChild::PaintRectToSurface): Cairo/Skia leave the X byte of
|
||||
// a BGRX surface zeroed, and a BasicImageLayer then composites those pixels multiplied
|
||||
// by alpha 0 — i.e. fully transparent. That is exactly why Flash's magenta stage
|
||||
// arrived here (RecvShow logged px0=0xffff00ff) yet BasicImageLayer::Paint drew
|
||||
// nothing visible, while the control plugin — which ships B8G8R8A8 — composited fine
|
||||
// through the identical layer path. webOS windowless plugins are opaque
|
||||
// (wmode=opaque forces the windowless instance in the first place), so forcing alpha
|
||||
// opaque is correct here.
|
||||
const SurfaceFormat srcFmt = surface->GetSurfaceFormat();
|
||||
const bool srcHasAlpha =
|
||||
srcFmt != SurfaceFormat::B8G8R8X8 &&
|
||||
srcFmt != SurfaceFormat::R8G8B8X8;
|
||||
RefPtr<gfx::DataSourceSurface> copy =
|
||||
gfx::Factory::CreateDataSourceSurface(ssize, SurfaceFormat::B8G8R8A8);
|
||||
if (copy) {
|
||||
gfx::DataSourceSurface::ScopedMap dstMap(copy, gfx::DataSourceSurface::WRITE);
|
||||
if (dstMap.IsMapped()) {
|
||||
const int32_t rowBytes = ssize.width * 4;
|
||||
for (int32_t y = 0; y < ssize.height; y++) {
|
||||
uint8_t* dstRow = dstMap.GetData() + (ptrdiff_t)y * dstMap.GetStride();
|
||||
memcpy(dstRow, imgSurf->Data() + (ptrdiff_t)y * imgSurf->Stride(),
|
||||
rowBytes);
|
||||
if (!srcHasAlpha) {
|
||||
for (int32_t x = 0; x < ssize.width; x++) {
|
||||
dstRow[x * 4 + 3] = 0xff; // BGRA: byte 3 is alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
image = new SourceSurfaceImage(ssize, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
// Fallback to the original wrap if the copy path could not be taken.
|
||||
bool isPlugin = true;
|
||||
RefPtr<gfx::SourceSurface> sourceSurface =
|
||||
gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin);
|
||||
image = new SourceSurfaceImage(ssize, sourceSurface);
|
||||
}
|
||||
|
||||
ImageContainer::NonOwningImage holder(image);
|
||||
holder.mFrameID = ++mFrameID;
|
||||
AutoTArray<ImageContainer::NonOwningImage,1> imageList;
|
||||
imageList.AppendElement(
|
||||
ImageContainer::NonOwningImage(image));
|
||||
imageList.AppendElement(holder);
|
||||
|
||||
ImageContainer *container = GetImageContainer();
|
||||
container->SetCurrentImages(imageList);
|
||||
@@ -976,6 +1178,17 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
|
||||
}
|
||||
|
||||
mFrontSurface = surface;
|
||||
// Jihad: count DELIVERED plugin frames so the embedder can tell a new one from a repeat.
|
||||
// The daemon's only frame signal is PuppetWidget's sticky dirty boolean, which carries no
|
||||
// identity: the refresh driver and ordinary page damage set the same flag, so "there is
|
||||
// damage" cannot answer "did the plugin draw again". Measured 2026-08-10 on a 30 fps SWF,
|
||||
// that ambiguity is what made both available policies wrong — publishing on every dirty
|
||||
// tick sent ~78 card frames for ~60 plugin frames (duplicates, and 22-44 deferred because
|
||||
// the adapter could not drain them), while pacing on a fixed 33 ms grid sent ~57 for ~68
|
||||
// (dropped frames, so the animation stepped two positions at once). The stock webOS host
|
||||
// never had to choose: it pulls the plugin synchronously inside the page paint, so it is
|
||||
// 1:1 by construction. This counter is what lets the daemon be 1:1 too.
|
||||
++gJihadPluginFrameSeq;
|
||||
RecvNPN_InvalidateRect(updatedRect);
|
||||
|
||||
PLUGIN_LOG_DEBUG((" (RecvShow invalidated for surface %p)",
|
||||
@@ -1301,7 +1514,11 @@ PluginInstanceParent::GetImageContainer()
|
||||
return mImageContainer;
|
||||
}
|
||||
|
||||
if (IsUsingDirectDrawing()) {
|
||||
if (IsUsingDirectDrawing() && ImageBridgeChild::GetSingleton()) {
|
||||
// ASYNCHRONOUS means "shared with the compositor through ImageBridge". Without an
|
||||
// ImageBridge the async container silently gets no image client and never publishes
|
||||
// anything, so with OMTC off this has to be an ordinary local container — which is
|
||||
// what the BasicLayerManager reads from. See RecvShowDirectBitmap.
|
||||
mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
|
||||
} else {
|
||||
mImageContainer = LayerManager::CreateImageContainer();
|
||||
|
||||
@@ -117,6 +117,8 @@ typedef HWND NativeWindowHandle;
|
||||
typedef XID NativeWindowHandle;
|
||||
#elif defined(XP_DARWIN)
|
||||
typedef intptr_t NativeWindowHandle; // never actually used, will always be 0
|
||||
#elif defined(MOZ_WIDGET_HEADLESS)
|
||||
typedef intptr_t NativeWindowHandle; // Jihad headless: no native window handle
|
||||
#else
|
||||
#error Need NativeWindowHandle for this platform
|
||||
#endif
|
||||
|
||||
@@ -60,6 +60,9 @@ using namespace mozilla::ipc;
|
||||
using namespace mozilla::plugins;
|
||||
using namespace mozilla::widget;
|
||||
|
||||
// Defined at the bottom of this file; see the NPN SYMBOL INTERPOSITION block there.
|
||||
namespace mozilla { namespace plugins { void JihadInstallNPNInterposition(); } }
|
||||
|
||||
#if defined(XP_WIN)
|
||||
const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
|
||||
const wchar_t * kMozillaWindowClass = L"MozillaWindowClass";
|
||||
@@ -232,6 +235,10 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
|
||||
{
|
||||
NS_ASSERTION(aChannel, "need a channel");
|
||||
|
||||
// Point the exported NPN_* globals at the real implementations before any plugin can be
|
||||
// loaded, so a plugin that resolves them as symbols reaches us rather than libWebKitLuna.
|
||||
JihadInstallNPNInterposition();
|
||||
|
||||
if (!InitGraphics())
|
||||
return false;
|
||||
|
||||
@@ -251,7 +258,11 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
|
||||
nsPluginFile pluginFile(localFile);
|
||||
|
||||
nsPluginInfo info = nsPluginInfo();
|
||||
if (NS_FAILED(pluginFile.GetPluginInfo(info, &mLibrary))) {
|
||||
nsresult jihadRv = pluginFile.GetPluginInfo(info, &mLibrary);
|
||||
fprintf(stderr, "[jihad-npapi-child] GetPluginInfo rv=0x%x lib=%d\n",
|
||||
(unsigned)jihadRv, (int)!!mLibrary);
|
||||
fflush(stderr);
|
||||
if (NS_FAILED(jihadRv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1843,6 +1854,8 @@ bool
|
||||
PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv)
|
||||
{
|
||||
*rv = DoNP_Initialize(aSettings);
|
||||
fprintf(stderr, "[jihad-npapi-child] AnswerNP_Initialize rv=%d\n", (int)*rv);
|
||||
fflush(stderr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1882,6 +1895,8 @@ PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings)
|
||||
# error Please implement me for your platform
|
||||
#endif
|
||||
|
||||
fprintf(stderr, "[jihad-npapi-child] DoNP_Initialize plugin returned %d\n", (int)result);
|
||||
fflush(stderr);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2617,3 +2632,31 @@ PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceChangeDetails(
|
||||
#endif
|
||||
}
|
||||
#endif // MOZ_ENABLE_NPAPI
|
||||
|
||||
// ── NPN SYMBOL INTERPOSITION (Jihad, addons R7) ──────────────────────────────────────────
|
||||
//
|
||||
// The exported NPN_* globals themselves live in JihadNPNInterpose.cpp (they must, to escape
|
||||
// hidden visibility — see the long comment there). All that is needed here is to point them
|
||||
// at the same implementations the browser function table uses, which is done once the table
|
||||
// exists. Installed from a static initializer-free helper called by the child at startup.
|
||||
extern "C" {
|
||||
extern void (*gJihadNPNInvalidateRect)(void*, void*);
|
||||
extern void* (*gJihadNPNCreateObject)(void*, void*);
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace plugins {
|
||||
|
||||
void JihadInstallNPNInterposition()
|
||||
{
|
||||
const NPNetscapeFuncs& f = PluginModuleChild::sBrowserFuncs;
|
||||
gJihadNPNInvalidateRect = (void(*)(void*, void*))f.invalidaterect;
|
||||
gJihadNPNCreateObject = (void*(*)(void*, void*))f.createobject;
|
||||
fprintf(stderr, "[jihad-npapi-child] NPN interposition installed invalidaterect=%p "
|
||||
"createobject=%p\n",
|
||||
(void*)f.invalidaterect, (void*)f.createobject);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // namespace plugins
|
||||
} // namespace mozilla
|
||||
@@ -444,6 +444,8 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId,
|
||||
parent->mSubprocess->SetCallRunnableImmediately(!parent->mIsStartingAsync);
|
||||
bool launched = parent->mSubprocess->Launch(Move(onLaunchedRunnable),
|
||||
aPluginTag->mSandboxLevel);
|
||||
fprintf(stderr, "[jihad-npapi] LoadModule path=%s async=%d launched=%d\n",
|
||||
aFilePath, (int)parent->mIsStartingAsync, (int)launched);
|
||||
if (!launched) {
|
||||
// We never reached open
|
||||
parent->mShutdown = true;
|
||||
@@ -455,7 +457,10 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId,
|
||||
parent->mIsBlocklisted = NS_FAILED(rv) || blocklistState != 0;
|
||||
if (!parent->mIsStartingAsync) {
|
||||
int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0);
|
||||
if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) {
|
||||
bool connected = parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000);
|
||||
fprintf(stderr, "[jihad-npapi] LoadModule WaitUntilConnected(%ds) = %d\n",
|
||||
(int)launchTimeoutSecs, (int)connected);
|
||||
if (!connected) {
|
||||
parent->mShutdown = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1134,9 +1139,20 @@ PluginModuleChromeParent::OnHangUIContinue()
|
||||
}
|
||||
#endif // XP_WIN
|
||||
|
||||
// Defined with the rest of the Jihad CPU-boost helpers further down this file.
|
||||
namespace { void JihadCpuBoostReleaseAll(); }
|
||||
|
||||
void
|
||||
PluginModuleParent::ActorDestroy(ActorDestroyReason why)
|
||||
{
|
||||
// Release the CPU boost here as well as in NPP_Destroy, because on this port NPP_Destroy is
|
||||
// NOT the path a plugin usually leaves by: navigating away from a Flash page ends in the
|
||||
// child's own mozalloc_abort, so the instance never gets an orderly destroy and the boost
|
||||
// would leak — device-observed 2026-08-10, tunables still at 40/50000 after navigation with
|
||||
// no release logged. This runs for every ActorDestroyReason, normal and abnormal, which also
|
||||
// covers a plugin crash.
|
||||
JihadCpuBoostReleaseAll();
|
||||
|
||||
switch (why) {
|
||||
case AbnormalShutdown: {
|
||||
mShutdown = true;
|
||||
@@ -1271,6 +1287,172 @@ NP_BEGIN_MACRO \
|
||||
return i->func; \
|
||||
NP_END_MACRO
|
||||
|
||||
// ── Jihad: hold a CPU-frequency boost while a plugin instance is alive ──────────────────────
|
||||
//
|
||||
// WHY. The TouchPad's governor is Palm's `ondemandtcl`, whose stock `up_threshold` is 95 — it
|
||||
// only raises the clock when a single core exceeds 95% load. Flash never gets there: measured on
|
||||
// device, the daemon runs ~26% and plugin-container ~11% of two cores, so a passively-watched
|
||||
// animation sits at the FLOOR. Sampling `scaling_cur_freq` once a second during playback gave
|
||||
// 192000-384000 out of a 1188000 maximum, snapping back to 1188000 the moment the page was
|
||||
// navigated away. The whole system was only 69% busy, so this was never a capacity wall.
|
||||
//
|
||||
// Dropping `up_threshold` to 40 and `sampling_rate` to 50000 pins 1188000 and lifts the card
|
||||
// composite from 20.4-27.5 fps to 30.7-35.2, daemon paints 24.0-28.7 to 31.4-33.0, and the frame
|
||||
// gap MAXIMUM — the number that reads as stutter — from 63-81 ms to 49-56. It also clears the
|
||||
// audio xruns, because MP3 decode at 192 MHz underruns.
|
||||
//
|
||||
// NEVER WRITE `scaling_governor`. Switching away from `ondemandtcl` DEADLOCKS cpufreq in the
|
||||
// kernel: every later reader of any cpufreq node blocks in unkillable D state — the daemon,
|
||||
// `powerlog`, and the restore path itself — so the boost is never released and only a reboot
|
||||
// clears it (and a clean `reboot` will not run, because init waits on those D-state tasks).
|
||||
// Governor TUNABLE writes, which is all this does, are safe and return instantly.
|
||||
//
|
||||
// These are SYSTEM-WIDE tunables, so they are held only while a plugin instance exists and the
|
||||
// ORIGINAL values are read back and restored on teardown. The upstart job restores them again
|
||||
// from `post-stop`, because a daemon that dies holding the boost would otherwise leave the whole
|
||||
// device tuned for battery burn. Both hooks run on the main thread, so the counter needs no lock.
|
||||
namespace {
|
||||
|
||||
const char kJihadOndemandDir[] = "/sys/devices/system/cpu/cpufreq/ondemandtcl";
|
||||
|
||||
// The stock values, used ONLY as the restore fallback when the current value already equals what
|
||||
// we are about to write — which happens when another variant's daemon is holding the boost.
|
||||
// Saving the boosted value as "original" there would make the last release pin the device.
|
||||
const char kJihadStockUpThreshold[] = "95";
|
||||
const char kJihadStockSamplingRate[] = "200000";
|
||||
|
||||
// The instances that actually TOOK a boost, not a bare count. NPP_Destroy also runs for instances
|
||||
// that failed before the boost was taken, and decrementing a count there would release the boost
|
||||
// out from under a plugin that is still playing.
|
||||
nsTArray<NPP> gJihadBoostedInstances;
|
||||
bool gJihadBoostHeld = false;
|
||||
char gJihadSavedUpThreshold[32];
|
||||
char gJihadSavedSamplingRate[32];
|
||||
|
||||
bool
|
||||
JihadReadTunable(const char* aName, char* aOut, size_t aOutLen)
|
||||
{
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/%s", kJihadOndemandDir, aName);
|
||||
FILE* f = fopen(path, "r");
|
||||
if (!f) return false;
|
||||
char* got = fgets(aOut, aOutLen, f);
|
||||
fclose(f);
|
||||
if (!got) return false;
|
||||
// Trim the trailing newline so a value read back can be compared and rewritten verbatim.
|
||||
size_t n = strlen(aOut);
|
||||
while (n > 0 && (aOut[n - 1] == '\n' || aOut[n - 1] == '\r')) aOut[--n] = '\0';
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
JihadWriteTunable(const char* aName, const char* aValue)
|
||||
{
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/%s", kJihadOndemandDir, aName);
|
||||
FILE* f = fopen(path, "w");
|
||||
if (!f) return false;
|
||||
bool ok = fputs(aValue, f) >= 0;
|
||||
ok = (fclose(f) == 0) && ok;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Env-tunable for the same reason the repaint divisor is: the right value is a property of the
|
||||
// device under load, not of the source, and a rebuild per candidate costs a 44 MB push.
|
||||
void
|
||||
JihadCpuBoostTargets(const char** aUpThreshold, const char** aSamplingRate)
|
||||
{
|
||||
static char sUp[32] = "40";
|
||||
static char sRate[32] = "50000";
|
||||
static bool sInit = false;
|
||||
if (!sInit) {
|
||||
sInit = true;
|
||||
const char* e = getenv("JIHAD_PLUGIN_CPU_UP_THRESHOLD");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 5 && v <= 95) snprintf(sUp, sizeof(sUp), "%d", v);
|
||||
}
|
||||
e = getenv("JIHAD_PLUGIN_CPU_SAMPLING_RATE");
|
||||
if (e && *e) {
|
||||
int v = atoi(e);
|
||||
if (v >= 10000 && v <= 1000000) snprintf(sRate, sizeof(sRate), "%d", v);
|
||||
}
|
||||
}
|
||||
*aUpThreshold = sUp;
|
||||
*aSamplingRate = sRate;
|
||||
}
|
||||
|
||||
bool
|
||||
JihadCpuBoostEnabled()
|
||||
{
|
||||
const char* e = getenv("JIHAD_PLUGIN_CPU_BOOST");
|
||||
return !(e && e[0] == '0' && e[1] == '\0');
|
||||
}
|
||||
|
||||
void
|
||||
JihadCpuBoostAcquire(NPP aInstance)
|
||||
{
|
||||
if (!JihadCpuBoostEnabled() || gJihadBoostedInstances.Contains(aInstance)) return;
|
||||
gJihadBoostedInstances.AppendElement(aInstance);
|
||||
if (gJihadBoostedInstances.Length() != 1 || gJihadBoostHeld) return;
|
||||
|
||||
const char* wantUp;
|
||||
const char* wantRate;
|
||||
JihadCpuBoostTargets(&wantUp, &wantRate);
|
||||
|
||||
if (!JihadReadTunable("up_threshold", gJihadSavedUpThreshold,
|
||||
sizeof(gJihadSavedUpThreshold)) ||
|
||||
!strcmp(gJihadSavedUpThreshold, wantUp)) {
|
||||
strcpy(gJihadSavedUpThreshold, kJihadStockUpThreshold);
|
||||
}
|
||||
if (!JihadReadTunable("sampling_rate", gJihadSavedSamplingRate,
|
||||
sizeof(gJihadSavedSamplingRate)) ||
|
||||
!strcmp(gJihadSavedSamplingRate, wantRate)) {
|
||||
strcpy(gJihadSavedSamplingRate, kJihadStockSamplingRate);
|
||||
}
|
||||
|
||||
bool ok = JihadWriteTunable("up_threshold", wantUp);
|
||||
ok = JihadWriteTunable("sampling_rate", wantRate) && ok;
|
||||
gJihadBoostHeld = ok;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost acquire: up_threshold %s->%s sampling_rate %s->%s%s\n",
|
||||
gJihadSavedUpThreshold, wantUp, gJihadSavedSamplingRate, wantRate,
|
||||
ok ? "" : " FAILED (not root, or no ondemandtcl)");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
void
|
||||
JihadCpuBoostRelease(NPP aInstance)
|
||||
{
|
||||
if (!gJihadBoostedInstances.RemoveElement(aInstance)) return;
|
||||
if (!gJihadBoostedInstances.IsEmpty() || !gJihadBoostHeld) return;
|
||||
|
||||
JihadWriteTunable("up_threshold", gJihadSavedUpThreshold);
|
||||
JihadWriteTunable("sampling_rate", gJihadSavedSamplingRate);
|
||||
gJihadBoostHeld = false;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost release: up_threshold=%s sampling_rate=%s\n",
|
||||
gJihadSavedUpThreshold, gJihadSavedSamplingRate);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
// Drop every outstanding boost at once. Used when the plugin module itself goes away, which on
|
||||
// this port is the NORMAL exit path (see ActorDestroy), not just the crash path.
|
||||
void
|
||||
JihadCpuBoostReleaseAll()
|
||||
{
|
||||
if (gJihadBoostedInstances.IsEmpty()) return;
|
||||
gJihadBoostedInstances.Clear();
|
||||
if (!gJihadBoostHeld) return;
|
||||
|
||||
JihadWriteTunable("up_threshold", gJihadSavedUpThreshold);
|
||||
JihadWriteTunable("sampling_rate", gJihadSavedSamplingRate);
|
||||
gJihadBoostHeld = false;
|
||||
fprintf(stderr, "[jihad-npapi] cpu boost release (module gone): up_threshold=%s sampling_rate=%s\n",
|
||||
gJihadSavedUpThreshold, gJihadSavedSamplingRate);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NPError
|
||||
PluginModuleParent::NPP_Destroy(NPP instance,
|
||||
NPSavedData** saved)
|
||||
@@ -1296,6 +1478,7 @@ PluginModuleParent::NPP_Destroy(NPP instance,
|
||||
instance->pdata = nullptr;
|
||||
|
||||
Unused << PluginInstanceParent::Call__delete__(parentInstance);
|
||||
JihadCpuBoostRelease(instance);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -2292,6 +2475,12 @@ PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance,
|
||||
|
||||
UpdatePluginTimeout();
|
||||
|
||||
// Taken ONLY here, on the single success path, and released in NPP_Destroy. Every failure
|
||||
// return above this point either deletes |parentInstance| itself or hands it to NPP_Destroy,
|
||||
// and a boost acquired on one of those paths would never be released — which on this device
|
||||
// means leaving the WHOLE system tuned for battery burn with nothing owning it.
|
||||
JihadCpuBoostAcquire(instance);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,14 @@ PluginProcessChild::Init()
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[jihad-npapi-child] InitForChrome plugin=%s parentPid=%d\n",
|
||||
pluginFilename.c_str(), (int)ParentPid());
|
||||
fflush(stderr);
|
||||
bool retval = mPlugin.InitForChrome(pluginFilename, ParentPid(),
|
||||
IOThreadChild::message_loop(),
|
||||
IOThreadChild::channel());
|
||||
fprintf(stderr, "[jihad-npapi-child] InitForChrome = %d\n", (int)retval);
|
||||
fflush(stderr);
|
||||
#if defined(XP_MACOSX)
|
||||
if (nsCocoaFeatures::OnYosemiteOrLater()) {
|
||||
// Explicitly turn off CGEvent logging. This works around bug 1092855.
|
||||
|
||||
@@ -88,6 +88,10 @@ UNIFIED_SOURCES += [
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
# Jihad: must NOT be unified — it deliberately includes no NPAPI headers so its exported
|
||||
# NPN_* symbols escape the hidden-visibility pragma. Merging it with a TU that includes
|
||||
# npapi.h would reintroduce the prior hidden declaration and silently un-export them.
|
||||
'JihadNPNInterpose.cpp',
|
||||
'PluginInstanceChild.cpp', # 'PluginThreadCallback' : ambiguous symbol
|
||||
'PluginModuleChild.cpp', # Redefinition of mozilla::WindowsDllInterceptor sUser32Intercept
|
||||
]
|
||||
|
||||
@@ -18,6 +18,8 @@ interface mixin DocumentOrShadowRoot {
|
||||
|
||||
readonly attribute Element? activeElement;
|
||||
readonly attribute StyleSheetList styleSheets;
|
||||
[Throws]
|
||||
attribute object adoptedStyleSheets;
|
||||
|
||||
readonly attribute Element? pointerLockElement;
|
||||
[LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
|
||||
|
||||
@@ -295,11 +295,11 @@ nsXMLContentSink::DidBuildModel(bool aTerminated)
|
||||
}
|
||||
|
||||
mXSLTProcessor->SetSourceContentModel(mDocument, mDocumentChildren);
|
||||
mDocumentChildren.Clear();
|
||||
// Since the processor now holds a reference to us we drop our reference
|
||||
// to it to avoid owning cycles
|
||||
mXSLTProcessor = nullptr;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Kick off layout for non-XSLT transformed documents.
|
||||
|
||||
// Check if we want to prettyprint
|
||||
@@ -881,6 +881,12 @@ nsXMLContentSink::SetDocElement(int32_t aNameSpaceID,
|
||||
|
||||
if (!mDocumentChildren.IsEmpty()) {
|
||||
for (nsIContent* child : mDocumentChildren) {
|
||||
if (MOZ_UNLIKELY(child->GetParentNode())) {
|
||||
child->Remove();
|
||||
if (MOZ_UNLIKELY(child->GetParentNode())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mDocument->AppendChildTo(child, false);
|
||||
}
|
||||
mDocumentChildren.Clear();
|
||||
@@ -991,6 +997,12 @@ nsXMLContentSink::HandleStartElement(const char16_t *aName,
|
||||
if (!SetDocElement(nameSpaceID, localName, content) && appendContent) {
|
||||
NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
|
||||
|
||||
if (MOZ_UNLIKELY(content->GetParentNode())) {
|
||||
content->Remove();
|
||||
if (MOZ_UNLIKELY(content->GetParentNode())) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
parent->AppendChildTo(content, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5159,7 +5159,7 @@ HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent)
|
||||
// If the event is a mouse event, we need to check if the target content is
|
||||
// the focused editing host or its descendant.
|
||||
if (aGUIEvent->AsMouseEventBase()) {
|
||||
nsIContent* editingHost = GetActiveEditingHost();
|
||||
RefPtr<nsIContent> editingHost = GetActiveEditingHost();
|
||||
// If there is no active editing host, we cannot handle the mouse event
|
||||
// correctly.
|
||||
if (!editingHost) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
// Local Includes
|
||||
#include "nsDocShellTreeOwner.h"
|
||||
#include "nsIBadCertListener2.h" // JIHAD: route cert errors to the embedder chrome
|
||||
#include "nsWebBrowser.h"
|
||||
|
||||
// Helper Classes
|
||||
@@ -143,6 +144,15 @@ nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aIID.Equals(NS_GET_IID(nsIBadCertListener2))) {
|
||||
// JIHAD: the embedder's chrome is what implements this — see the matching comment in
|
||||
// nsDocShell::GetInterface. Same forwarding shape as nsIWebBrowserChromeFocus below.
|
||||
if (mWebBrowserChromeWeak != nullptr) {
|
||||
return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
|
||||
}
|
||||
return mOwnerWin ? mOwnerWin->QueryInterface(aIID, aSink) : NS_NOINTERFACE;
|
||||
}
|
||||
|
||||
if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) {
|
||||
if (mWebBrowserChromeWeak != nullptr) {
|
||||
return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
|
||||
@@ -1082,6 +1092,9 @@ ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* aInBrowser,
|
||||
|
||||
ChromeTooltipListener::~ChromeTooltipListener()
|
||||
{
|
||||
if (mTooltipTimer) {
|
||||
mTooltipTimer->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Hook up things to the chrome like context menus and tooltips, if the chrome
|
||||
@@ -1329,7 +1342,7 @@ void
|
||||
ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
|
||||
void* aChromeTooltipListener)
|
||||
{
|
||||
auto self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
|
||||
RefPtr<ChromeTooltipListener> self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
|
||||
if (self && self->mPossibleTooltipNode) {
|
||||
// The actual coordinates we want to put the tooltip at are relative to the
|
||||
// toplevel docshell of our mWebBrowser. We know what the screen
|
||||
@@ -1374,7 +1387,7 @@ ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
|
||||
self->mPossibleTooltipNode, getter_Copies(tooltipText),
|
||||
getter_Copies(directionText), &textFound);
|
||||
|
||||
if (textFound) {
|
||||
if (self->mPossibleTooltipNode && textFound) {
|
||||
nsString tipText(tooltipText);
|
||||
nsString dirText(directionText);
|
||||
LayoutDeviceIntPoint screenDot = widget->WidgetToScreenOffset();
|
||||
|
||||
@@ -390,6 +390,14 @@ nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow,
|
||||
|
||||
nsCOMPtr<nsISelection> selection;
|
||||
|
||||
// JIHAD: frame->GetSelectionController can leave selCon NULL, and this dereferenced it
|
||||
// unconditionally — a null deref that took the whole process down. It is reachable in an
|
||||
// offscreen embedding with no chrome: this is the ONLY thing that made find-in-page
|
||||
// unusable there (faultaddr=0x0, measured repeatedly). Guarding costs nothing and the
|
||||
// rest of the function already tolerates a null `selection`.
|
||||
if (!selCon) {
|
||||
return;
|
||||
}
|
||||
selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(selection));
|
||||
@@ -707,7 +715,24 @@ nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) {
|
||||
// JIHAD: this same-origin check is why find-in-page took the whole process down in this
|
||||
// embedding, and the engine says why itself. nsContentUtils::SubjectPrincipal() begins:
|
||||
// JSContext* cx = GetCurrentJSContext();
|
||||
// if (!cx) {
|
||||
// MOZ_CRASH("Accessing the Subject Principal without an AutoJSAPI on the stack is forbidden");
|
||||
// }
|
||||
// An EMBEDDER calls nsIWebBrowserFind::FindNext from C++ with no script on the stack, so
|
||||
// there is no JSContext and MOZ_CRASH fires — a deliberate abort that presents as SIGSEGV at
|
||||
// address 0, which is precisely the fault this port recorded for two weeks and attributed to
|
||||
// "a frame-selection controller the offscreen browser doesn't set up". That was wrong: the
|
||||
// selection machinery is fine and the crash happens BEFORE any of it, on the security check.
|
||||
//
|
||||
// The check is meaningful only when there IS a caller to check. With no JS on the stack the
|
||||
// caller is native embedding code, which is already fully trusted — it is inside libxul's own
|
||||
// process, holding the nsIWebBrowserFind for that very docShell. So: consult the subject
|
||||
// principal only when a JSContext exists, and otherwise proceed.
|
||||
if (nsContentUtils::GetCurrentJSContext() &&
|
||||
!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) {
|
||||
return NS_ERROR_DOM_PROP_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
@@ -809,8 +834,11 @@ nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow)
|
||||
nsCOMPtr<nsISelection> sel;
|
||||
if (frame) {
|
||||
frame->GetSelectionController(presContext, getter_AddRefs(selCon));
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
// JIHAD: same unguarded deref as above — GetSelectionController can yield null.
|
||||
if (selCon) {
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
}
|
||||
if (sel) {
|
||||
int32_t count = -1;
|
||||
sel->GetRangeCount(&count);
|
||||
@@ -820,7 +848,12 @@ nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow)
|
||||
}
|
||||
}
|
||||
|
||||
// JIHAD: and the fallback QI is not guaranteed either — if the presShell does not answer
|
||||
// nsISelectionController we must return nothing rather than crash.
|
||||
selCon = do_QueryInterface(presShell);
|
||||
if (!selCon) {
|
||||
return nullptr;
|
||||
}
|
||||
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(sel));
|
||||
return sel.forget();
|
||||
|
||||
@@ -12,5 +12,6 @@ if CONFIG['NS_PRINTING']:
|
||||
DIRS += ['win']
|
||||
elif toolkit == 'cocoa':
|
||||
DIRS += ['mac']
|
||||
elif CONFIG['MOZ_PDF_PRINTING']:
|
||||
elif CONFIG['MOZ_PDF_PRINTING'] or toolkit == 'headless':
|
||||
# Jihad headless: reuse the gtk-free PDF printing prompt service.
|
||||
DIRS += ['unixshared']
|
||||
+1
-1
@@ -1374,7 +1374,7 @@ public:
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSize);
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
static already_AddRefed<ScaledFont>
|
||||
CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize);
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
#include "NativeFontResourceMac.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
#include "ScaledFontFontconfig.h"
|
||||
#endif
|
||||
|
||||
@@ -584,7 +584,7 @@ Factory::CreateScaledFontWithCairo(const NativeFont& aNativeFont, Float aSize, c
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_HEADLESS)
|
||||
already_AddRefed<ScaledFont>
|
||||
Factory::CreateScaledFontForFontconfigFont(cairo_scaled_font_t* aScaledFont, FcPattern* aPattern, Float aSize)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* 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/. */
|
||||
|
||||
// Jihad (T-512): the GPU path's diagnostics print only with JIHAD_GPU_LOG=1, or in a device round's
|
||||
// JIHAD_GPU_TIMELINE, JIHAD_GPU_VERIFY or JIHAD_GPU_DUMP_EVERY; failures and fallbacks print anyway
|
||||
// (plain fprintf). An expression, its switch read once at each place, so it stands anywhere a call
|
||||
// does. The daemon has the same gate (render/goanna/JihadGpuLog.h in Jihad Browser).
|
||||
|
||||
#ifndef MOZILLA_GFX_JIHADGPULOG_H_
|
||||
#define MOZILLA_GFX_JIHADGPULOG_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define JIHAD_GPU_LOG(...) \
|
||||
([]() { static const bool sJihadGpuLogOn = getenv("JIHAD_GPU_LOG") || getenv("JIHAD_GPU_TIMELINE") || \
|
||||
getenv("JIHAD_GPU_VERIFY") || getenv("JIHAD_GPU_DUMP_EVERY"); \
|
||||
return sJihadGpuLogOn; }() ? fprintf(stderr, __VA_ARGS__) : 0)
|
||||
|
||||
#endif // MOZILLA_GFX_JIHADGPULOG_H_
|
||||
+4
-3
@@ -57,6 +57,7 @@ EXPORTS.mozilla.gfx += [
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.gfx += ['ssse3-scaler.h']
|
||||
EXPORTS.mozilla.gfx += ['JihadGpuLog.h'] # Jihad (T-512): the GPU path's diagnostics gate
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
|
||||
EXPORTS.mozilla.gfx += [
|
||||
@@ -91,7 +92,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
|
||||
'JobScheduler_posix.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
SOURCES += [
|
||||
'ScaledFontFontconfig.cpp',
|
||||
]
|
||||
@@ -209,12 +210,12 @@ FINAL_LIBRARY = 'xul'
|
||||
for var in ('USE_CAIRO', 'MOZ2D_HAS_MOZ_CAIRO'):
|
||||
DEFINES[var] = True
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
DEFINES['MOZ_ENABLE_FREETYPE'] = True
|
||||
|
||||
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'headless'):
|
||||
CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
|
||||
|
||||
LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
|
||||
|
||||
@@ -358,7 +358,8 @@ gl::Error Image9::setManagedSurface(IDirect3DSurface9 *surface)
|
||||
surface->GetDesc(&desc);
|
||||
ASSERT(desc.Pool == D3DPOOL_MANAGED);
|
||||
|
||||
if ((GLsizei)desc.Width == mWidth && (GLsizei)desc.Height == mHeight)
|
||||
if ((GLsizei)desc.Width == mWidth && (GLsizei)desc.Height == mHeight &&
|
||||
desc.Format == mD3DFormat)
|
||||
{
|
||||
if (mSurface)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -812,6 +812,7 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
|
||||
"Adreno 205",
|
||||
"Adreno (TM) 200",
|
||||
"Adreno (TM) 205",
|
||||
"Adreno (TM) 220",
|
||||
"Adreno (TM) 305",
|
||||
"Adreno (TM) 320",
|
||||
"Adreno (TM) 330",
|
||||
|
||||
@@ -172,6 +172,7 @@ enum class GLRenderer {
|
||||
Adreno205,
|
||||
AdrenoTM200,
|
||||
AdrenoTM205,
|
||||
AdrenoTM220, // Jihad: the TouchPad's GPU (Adreno (TM) 220)
|
||||
AdrenoTM305,
|
||||
AdrenoTM320,
|
||||
AdrenoTM330,
|
||||
@@ -1232,6 +1233,13 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
// Jihad T-502 diagnostic: the driver's own scissor box and viewport against this context's
|
||||
// cache of them (fScissor/fViewport skip a call that matches the cache)
|
||||
void JihadRawAndCached(GLint* aRawScissor, GLint* aCachedScissor, GLint* aRawViewport, GLint* aCachedViewport) {
|
||||
raw_fGetIntegerv(LOCAL_GL_SCISSOR_BOX, aRawScissor);
|
||||
raw_fGetIntegerv(LOCAL_GL_VIEWPORT, aRawViewport);
|
||||
for (int i = 0; i < 4; i++) { aCachedScissor[i] = mScissorRect[i]; aCachedViewport[i] = mViewportRect[i]; }
|
||||
}
|
||||
|
||||
void fGetIntegerv(GLenum pname, GLint* params);
|
||||
|
||||
|
||||
@@ -38,6 +38,18 @@ public:
|
||||
~GLContextEGL();
|
||||
|
||||
virtual GLContextType GetContextType() const override { return GLContextType::EGL; }
|
||||
// Jihad T-502: upload 32-bit surfaces as BGRA where the driver takes them (the content is
|
||||
// BGRA already). On the Adreno 220 an RGBA upload of a 768x942 layer costs 35 ms and a
|
||||
// BGRA_EXT one 8 (tools/device/gpu-bench.c, 2026-10-17); as RGBA the CPU swizzled every
|
||||
// tile first and the driver swizzled it back.
|
||||
virtual GLenum GetPreferredARGB32Format() const override {
|
||||
return IsExtensionSupported(EXT_texture_format_BGRA8888) ? LOCAL_GL_BGRA : LOCAL_GL_RGBA;
|
||||
}
|
||||
|
||||
// Jihad T-502: a 1x1 pbuffer context sharing aShare's objects (the compositor's
|
||||
// renderbuffers), made on and for the readback thread.
|
||||
static already_AddRefed<GLContextEGL>
|
||||
JihadCreateSharedContext(GLContextEGL* aShare, nsACString* const out_failureId);
|
||||
|
||||
static GLContextEGL* Cast(GLContext* gl) {
|
||||
MOZ_ASSERT(gl->GetContextType() == GLContextType::EGL);
|
||||
@@ -76,6 +88,23 @@ public:
|
||||
|
||||
void SetEGLSurfaceOverride(EGLSurface surf);
|
||||
|
||||
// Jihad T-502 round 104: make a pbuffer of exactly this size current in place of the
|
||||
// context's own (the Adreno 220 driver bins an FBO render by the window surface's height:
|
||||
// a 942-row frame in a 1400-row pbuffer lost its bottom 458 rows in some composites).
|
||||
// Returns false (and keeps the current surface) when the pbuffer cannot be made.
|
||||
bool JihadUsePBufferOfSize(int aWidth, int aHeight);
|
||||
EGLSurface mJihadPbOverride = nullptr;
|
||||
// Round 106: a band-sized pbuffer made current for the band composites of a scroll-only
|
||||
// frame, then the frame's pbuffer again. false when the band pbuffer cannot be made.
|
||||
// T-527: aTall picks a second pbuffer of its own (the tall band surface), so a frame that needs
|
||||
// the tall one and the next that needs the short one do not remake either
|
||||
bool JihadUseBandPBuffer(int aWidth, int aHeight, bool aTall = false);
|
||||
void JihadUseFramePBuffer();
|
||||
EGLSurface mJihadBandPb = nullptr;
|
||||
int mJihadBandPbW = 0, mJihadBandPbH = 0;
|
||||
EGLSurface mJihadBandPbTall = nullptr;
|
||||
int mJihadBandPbTallW = 0, mJihadBandPbTallH = 0;
|
||||
|
||||
virtual bool MakeCurrentImpl(bool aForce) override;
|
||||
|
||||
virtual bool IsCurrent() override;
|
||||
@@ -111,6 +140,8 @@ public:
|
||||
const gfx::IntSize& size,
|
||||
const SurfaceCaps& minCaps,
|
||||
nsACString* const out_FailureId);
|
||||
// Jihad (T-511): the share root of the GPU path's pbuffer contexts (JIHAD_GPU_NO_SHARE=1: none)
|
||||
static GLContextEGL* JihadShareRoot(CreateContextFlags flags, const SurfaceCaps& minCaps);
|
||||
|
||||
protected:
|
||||
friend class GLContextProviderEGL;
|
||||
@@ -119,6 +150,10 @@ public:
|
||||
const EGLConfig mConfig;
|
||||
protected:
|
||||
EGLSurface mSurface;
|
||||
public:
|
||||
// Jihad (T-511): the size of this context's own pbuffer surface (0x0 when it has none)
|
||||
mozilla::gfx::IntSize mJihadPbSize;
|
||||
protected:
|
||||
public:
|
||||
const EGLContext mContext;
|
||||
protected:
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
#include "GLContextProvider.h"
|
||||
#include "GLLibraryEGL.h"
|
||||
#include "GLScreenBuffer.h"
|
||||
#include "mozilla/StaticMutex.h" // Jihad T-511: the share root
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/widget/CompositorWidget.h"
|
||||
@@ -93,6 +94,8 @@
|
||||
#include "ScopedGLHelpers.h"
|
||||
#include "TextureImageEGL.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
@@ -246,9 +249,22 @@ GLContextEGL::~GLContextEGL()
|
||||
printf_stderr("Destroying context %p surface %p on display %p\n", mContext, mSurface, EGL_DISPLAY());
|
||||
#endif
|
||||
|
||||
// Jihad (T-511): released from this thread before it is destroyed. MarkDestroyed() above leaves it
|
||||
// current, and the Adreno 220 driver then kept pointers into its framebuffers: the next context's
|
||||
// teardown on this thread faulted in set_framebuffer (libGLESv2 gl2_surface_update_writeonly_flag)
|
||||
// on the second WebGL page closed, every time (device rounds 290-296)
|
||||
if (getenv("JIHAD_GPU_COMPOSITOR") && sEGLLibrary.fGetCurrentContext() == mContext) {
|
||||
sEGLLibrary.fMakeCurrent(EGL_DISPLAY(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
}
|
||||
sEGLLibrary.fDestroyContext(EGL_DISPLAY(), mContext);
|
||||
sEGLLibrary.UnsetCachedCurrentContext();
|
||||
|
||||
// Jihad (T-513 review): the frame-sized and the band pbuffers made for this context
|
||||
if (mJihadBandPb && mJihadBandPb != mSurface) DestroySurface(mJihadBandPb);
|
||||
if (mJihadBandPbTall && mJihadBandPbTall != mSurface) DestroySurface(mJihadBandPbTall); // T-527
|
||||
if (mJihadPbOverride && mJihadPbOverride != mSurface && mJihadPbOverride != mJihadBandPb &&
|
||||
mJihadPbOverride != mJihadBandPbTall) DestroySurface(mJihadPbOverride);
|
||||
mJihadBandPb = mJihadBandPbTall = mJihadPbOverride = nullptr;
|
||||
DestroySurface(mSurface);
|
||||
}
|
||||
|
||||
@@ -336,6 +352,66 @@ GLContextEGL::SetEGLSurfaceOverride(const EGLSurface surf) {
|
||||
MOZ_ALWAYS_TRUE(MakeCurrent(true));
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextEGL::JihadUsePBufferOfSize(int aWidth, int aHeight)
|
||||
{
|
||||
if (aWidth <= 0 || aHeight <= 0) {
|
||||
return false;
|
||||
}
|
||||
const EGLint attrs[] = { LOCAL_EGL_WIDTH, aWidth, LOCAL_EGL_HEIGHT, aHeight, LOCAL_EGL_NONE };
|
||||
EGLSurface surf = sEGLLibrary.fCreatePbufferSurface(EGL_DISPLAY(), mConfig, attrs);
|
||||
if (!surf) {
|
||||
fprintf(stderr, "[jihad-gpu] a %dx%d pbuffer could not be made (0x%x); the context keeps its surface\n",
|
||||
aWidth, aHeight, sEGLLibrary.fGetError());
|
||||
return false;
|
||||
}
|
||||
EGLSurface old = mJihadPbOverride;
|
||||
SetEGLSurfaceOverride(surf);
|
||||
mJihadPbOverride = surf;
|
||||
if (old) {
|
||||
sEGLLibrary.fDestroySurface(EGL_DISPLAY(), old);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextEGL::JihadUseBandPBuffer(int aWidth, int aHeight, bool aTall)
|
||||
{
|
||||
EGLSurface& pb = aTall ? mJihadBandPbTall : mJihadBandPb;
|
||||
int& pbW = aTall ? mJihadBandPbTallW : mJihadBandPbW;
|
||||
int& pbH = aTall ? mJihadBandPbTallH : mJihadBandPbH;
|
||||
if (!pb || pbW != aWidth || pbH != aHeight) {
|
||||
if (pb) {
|
||||
if (mSurfaceOverride == pb) JihadUseFramePBuffer();
|
||||
sEGLLibrary.fDestroySurface(EGL_DISPLAY(), pb);
|
||||
pb = nullptr;
|
||||
}
|
||||
const EGLint attrs[] = { LOCAL_EGL_WIDTH, aWidth, LOCAL_EGL_HEIGHT, aHeight, LOCAL_EGL_NONE };
|
||||
pb = sEGLLibrary.fCreatePbufferSurface(EGL_DISPLAY(), mConfig, attrs);
|
||||
if (!pb) {
|
||||
fprintf(stderr, "[jihad-gpu] a %dx%d band pbuffer could not be made (0x%x)\n", aWidth, aHeight, sEGLLibrary.fGetError());
|
||||
return false;
|
||||
}
|
||||
pbW = aWidth; pbH = aHeight;
|
||||
JIHAD_GPU_LOG("[jihad-gpu] band pbuffer %dx%d made%s\n", aWidth, aHeight, aTall ? " (the tall one)" : "");
|
||||
}
|
||||
if (mSurfaceOverride != pb) {
|
||||
mSurfaceOverride = pb;
|
||||
MakeCurrent(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GLContextEGL::JihadUseFramePBuffer()
|
||||
{
|
||||
EGLSurface frame = mJihadPbOverride ? mJihadPbOverride : EGL_NO_SURFACE;
|
||||
if (mSurfaceOverride != frame) {
|
||||
mSurfaceOverride = frame;
|
||||
MakeCurrent(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GLContextEGL::MakeCurrentImpl(bool aForce) {
|
||||
if (IsDestroyed()) {
|
||||
@@ -857,6 +933,49 @@ ChooseConfig(GLLibraryEGL* egl, CreateContextFlags flags, const SurfaceCaps& min
|
||||
return config;
|
||||
}
|
||||
|
||||
// Jihad (T-511): every pbuffer context on the GPU path, the compositor's and each WebGL canvas's, in
|
||||
// one share group under a 1x1 root that lives as long as the process, so a WebGL frame reaches the
|
||||
// compositor as a texture it samples (the shared GL texture surface) with no readback: 23.7-25 fps
|
||||
// for a 480 px canvas, where the readback through the pbuffer gave 16 and through its texture
|
||||
// framebuffer 8-9. The driver has no EGLImage from a GL texture (EGL_BAD_PARAMETER, gpu-bench.c
|
||||
// section 17). JIHAD_GPU_NO_SHARE=1: unshared contexts, the readback through the pbuffer.
|
||||
static StaticMutex sJihadShareLock;
|
||||
static GLContextEGL* sJihadShareRoot = nullptr; // leaked on purpose: no teardown order to get wrong
|
||||
static bool sJihadShareTried = false;
|
||||
|
||||
/*static*/ GLContextEGL*
|
||||
GLContextEGL::JihadShareRoot(CreateContextFlags flags, const SurfaceCaps& minCaps)
|
||||
{
|
||||
if (!getenv("JIHAD_GPU_COMPOSITOR") || getenv("JIHAD_GPU_NO_SHARE")) {
|
||||
return nullptr;
|
||||
}
|
||||
StaticMutexAutoLock lock(sJihadShareLock);
|
||||
if (sJihadShareTried) {
|
||||
return sJihadShareRoot;
|
||||
}
|
||||
sJihadShareTried = true;
|
||||
SurfaceCaps configCaps;
|
||||
EGLConfig config = ChooseConfig(&sEGLLibrary, flags, minCaps, &configCaps);
|
||||
if (config == EGL_NO_CONFIG) {
|
||||
return nullptr;
|
||||
}
|
||||
mozilla::gfx::IntSize one(1, 1);
|
||||
EGLSurface surface = CreatePBufferSurfaceTryingPowerOfTwo(config, LOCAL_EGL_NONE, one);
|
||||
if (!surface) {
|
||||
return nullptr;
|
||||
}
|
||||
nsCString failureId;
|
||||
RefPtr<GLContextEGL> gl = CreateGLContext(flags, configCaps, nullptr, true, config, surface, &failureId);
|
||||
if (!gl) {
|
||||
sEGLLibrary.fDestroySurface(sEGLLibrary.Display(), surface);
|
||||
fprintf(stderr, "[jihad-gpu] share root: no context (%s): contexts unshared\n", failureId.get());
|
||||
return nullptr;
|
||||
}
|
||||
sJihadShareRoot = gl.forget().take();
|
||||
JIHAD_GPU_LOG("[jihad-gpu] share root made: the compositor's and WebGL's contexts share textures\n");
|
||||
return sJihadShareRoot;
|
||||
}
|
||||
|
||||
/*static*/ already_AddRefed<GLContextEGL>
|
||||
GLContextEGL::CreateEGLPBufferOffscreenContext(CreateContextFlags flags,
|
||||
const mozilla::gfx::IntSize& size,
|
||||
@@ -890,7 +1009,7 @@ GLContextEGL::CreateEGLPBufferOffscreenContext(CreateContextFlags flags,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext(flags, configCaps, nullptr,
|
||||
RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext(flags, configCaps, JihadShareRoot(flags, minCaps),
|
||||
true, config, surface,
|
||||
out_failureId);
|
||||
if (!gl) {
|
||||
@@ -898,16 +1017,57 @@ GLContextEGL::CreateEGLPBufferOffscreenContext(CreateContextFlags flags,
|
||||
sEGLLibrary.fDestroySurface(sEGLLibrary.Display(), surface);
|
||||
return nullptr;
|
||||
}
|
||||
gl->mJihadPbSize = pbSize; // Jihad T-511
|
||||
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<GLContextEGL>
|
||||
GLContextEGL::JihadCreateSharedContext(GLContextEGL* aShare, nsACString* const out_failureId)
|
||||
{
|
||||
if (!sEGLLibrary.EnsureInitialized(false, out_failureId)) {
|
||||
return nullptr;
|
||||
}
|
||||
SurfaceCaps configCaps;
|
||||
EGLConfig config = ChooseConfig(&sEGLLibrary, CreateContextFlags::REQUIRE_COMPAT_PROFILE,
|
||||
SurfaceCaps::ForRGBA(), &configCaps);
|
||||
if (config == EGL_NO_CONFIG) {
|
||||
*out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_NO_CONFIG");
|
||||
return nullptr;
|
||||
}
|
||||
mozilla::gfx::IntSize pbSize(1, 1);
|
||||
EGLSurface surface = GLContextEGL::CreatePBufferSurfaceTryingPowerOfTwo(config, LOCAL_EGL_NONE, pbSize);
|
||||
if (!surface) {
|
||||
*out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_EGL_POT");
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<GLContextEGL> gl = GLContextEGL::CreateGLContext(CreateContextFlags::REQUIRE_COMPAT_PROFILE, configCaps,
|
||||
aShare, true, config, surface, out_failureId);
|
||||
if (!gl) {
|
||||
sEGLLibrary.fDestroySurface(sEGLLibrary.Display(), surface);
|
||||
return nullptr;
|
||||
}
|
||||
return gl.forget();
|
||||
}
|
||||
|
||||
/*static*/ already_AddRefed<GLContext>
|
||||
GLContextProviderEGL::CreateHeadless(CreateContextFlags flags,
|
||||
nsACString* const out_failureId)
|
||||
{
|
||||
mozilla::gfx::IntSize dummySize = mozilla::gfx::IntSize(16, 16);
|
||||
SurfaceCaps dummyCaps = SurfaceCaps::Any();
|
||||
// Jihad (T-511): on the GPU path an offscreen context (a WebGL canvas's) whose frames are read
|
||||
// back (no share group: JIHAD_GPU_NO_SHARE=1, or no root could be made) gets a pbuffer as large
|
||||
// as a canvas on this screen is, RGBA: they are read back through it (ScopedReadbackFB), where a
|
||||
// read from its texture framebuffer ran ~10x slower on the Adreno 220 (8-9 fps for a 480 px
|
||||
// canvas, 16 through the pbuffer). 4 MB of GPU memory a context, so not when the compositor
|
||||
// samples the canvas's texture (no difference measured there, rounds 301).
|
||||
// JIHAD_GPU_NO_PB_READ=1: the 16x16 one, as before.
|
||||
if (getenv("JIHAD_GPU_COMPOSITOR") && !getenv("JIHAD_GPU_NO_PB_READ") &&
|
||||
!GLContextEGL::JihadShareRoot(flags, SurfaceCaps::ForRGBA())) {
|
||||
dummySize = mozilla::gfx::IntSize(1024, 1024);
|
||||
dummyCaps = SurfaceCaps::ForRGBA();
|
||||
}
|
||||
return GLContextEGL::CreateEGLPBufferOffscreenContext(flags, dummySize, dummyCaps,
|
||||
out_failureId);
|
||||
}
|
||||
|
||||
+232
-6
@@ -4,6 +4,10 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GLTextureImage.h"
|
||||
#include <utility> // Jihad: std::swap
|
||||
#include <vector>
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "GLContext.h"
|
||||
#include "gfxContext.h"
|
||||
#include "gfxPlatform.h"
|
||||
@@ -16,6 +20,8 @@
|
||||
|
||||
#include "TextureImageEGL.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
@@ -116,10 +122,216 @@ BasicTextureImage::~BasicTextureImage()
|
||||
// then they went away when the contex was deleted, because it
|
||||
// was the only one that had access to it.
|
||||
if (ctx && ctx->MakeCurrent()) {
|
||||
// Jihad T-504: a texture with storage goes to the pool instead (JihadPoolTexture)
|
||||
if (ctx == mGLContext && !mTexture2 && mTextureState != Created &&
|
||||
JihadPoolTexture(ctx, mTexture, mSize)) {
|
||||
return;
|
||||
}
|
||||
ctx->fDeleteTextures(1, &mTexture);
|
||||
if (mTexture2) {
|
||||
ctx->fDeleteTextures(1, &mTexture2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Jihad T-504: the GPU path's tile textures are kept and reused, not deleted -----------------
|
||||
//
|
||||
// On the TouchPad's Adreno 220 the first write into a texture after its first draw costs ~18 ms for
|
||||
// 256x256 (~75 ms for 512x512, ~285 ms for 1024x1024: the driver converts the texture's storage at
|
||||
// about 14 MB/s), whatever the format, size parity, filter or wrap; every write after that costs
|
||||
// ~1 ms, under a queued draw that reads the texture too; a deleted and regenerated name pays again
|
||||
// (tools/device/gpu-bench.c sections 12 and 13, 2026-10-18). The compositor deletes a tile's texture
|
||||
// with the tile, so every new tile paid the 18 ms on its first change: on desktop Wikipedia ~50 a
|
||||
// zoom, a second of the compositor thread with no frame (the daemon logged 59 re-uploads at 37 ms
|
||||
// average, the first uploads 1.5 ms). So on the GPU path the textures of BasicTextureImages that had
|
||||
// storage go to this pool, up to JIHAD_GPU_TEX_POOL (48: 12 MB of 256x256 tiles), and the next
|
||||
// texture image takes one, whatever its size: its uploads are the fast kind. A texture the pool
|
||||
// cannot give is converted while it is 1x1 (JihadConvertTexture; JIHAD_GPU_NO_TEX_CONVERT=1 skips
|
||||
// it for an A/B).
|
||||
namespace {
|
||||
struct JihadPooledTexture {
|
||||
GLContext* gl;
|
||||
GLuint tex;
|
||||
gfx::IntSize size;
|
||||
};
|
||||
mozilla::StaticMutex sJihadPoolLock;
|
||||
std::vector<JihadPooledTexture> sJihadPool;
|
||||
size_t JihadPoolCap()
|
||||
{
|
||||
static const size_t cap = !getenv("JIHAD_GPU_COMPOSITOR") ? 0
|
||||
: getenv("JIHAD_GPU_TEX_POOL") ? (size_t)atoi(getenv("JIHAD_GPU_TEX_POOL")) : 48;
|
||||
return cap;
|
||||
}
|
||||
uint32_t sJihadPoolHits = 0, sJihadPoolMisses = 0, sJihadPoolKept = 0, sJihadPoolFull = 0;
|
||||
GLContext* sJihadConvertGL = nullptr;
|
||||
GLuint sJihadConvertFb = 0;
|
||||
uint32_t sJihadConverted = 0;
|
||||
double sJihadConvertMs = 0.0;
|
||||
|
||||
// A new texture converted while it is 1x1: a 1x1 image, a clear of it through a framebuffer, a 1x1
|
||||
// write. The conversion stays when the texture is re-specified at any size, and costs ~1-3 ms
|
||||
// against ~18 for a 256x256 tile's first write after its first draw (gpu-bench.c sections 14-15:
|
||||
// a draw that samples the texture converts it only once the draw has finished; a clear does
|
||||
// without a finish). The scissor test is off for the clear and the framebuffer binding restored.
|
||||
void JihadConvertTexture(GLContext* aGL, GLuint aTexture)
|
||||
{
|
||||
const mozilla::TimeStamp t0 = mozilla::TimeStamp::Now();
|
||||
if (sJihadConvertGL != aGL) {
|
||||
sJihadConvertGL = aGL;
|
||||
sJihadConvertFb = 0;
|
||||
aGL->fGenFramebuffers(1, &sJihadConvertFb);
|
||||
}
|
||||
if (!sJihadConvertFb) {
|
||||
return;
|
||||
}
|
||||
const uint32_t px = 0xffffffff;
|
||||
ScopedBindTexture bind(aGL, aTexture);
|
||||
aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_BGRA, 1, 1, 0, LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, &px);
|
||||
{
|
||||
ScopedBindFramebuffer fb(aGL, sJihadConvertFb);
|
||||
ScopedGLState scissor(aGL, LOCAL_GL_SCISSOR_TEST, false);
|
||||
aGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, aTexture, 0);
|
||||
static int sStatusLogged = 0;
|
||||
if (sStatusLogged++ < 1) {
|
||||
JIHAD_GPU_LOG("[jihad-gpu] texture conversion: framebuffer status 0x%x\n", aGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER));
|
||||
}
|
||||
aGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
|
||||
aGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
aGL->fTexSubImage2D(LOCAL_GL_TEXTURE_2D, 0, 0, 0, 1, 1, LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, &px);
|
||||
sJihadConvertMs += (mozilla::TimeStamp::Now() - t0).ToMilliseconds();
|
||||
sJihadConverted++;
|
||||
}
|
||||
}
|
||||
|
||||
bool JihadPoolTexture(GLContext* aGL, GLuint aTexture, const gfx::IntSize& aSize)
|
||||
{
|
||||
if (!JihadPoolCap() || !aTexture) {
|
||||
return false;
|
||||
}
|
||||
mozilla::StaticMutexAutoLock lock(sJihadPoolLock);
|
||||
if (sJihadPool.size() >= JihadPoolCap()) {
|
||||
sJihadPoolFull++;
|
||||
return false;
|
||||
}
|
||||
sJihadPool.push_back(JihadPooledTexture{aGL, aTexture, aSize});
|
||||
sJihadPoolKept++;
|
||||
return true;
|
||||
}
|
||||
|
||||
GLuint JihadTakePooledTexture(GLContext* aGL, const gfx::IntSize& aSize)
|
||||
{
|
||||
if (!JihadPoolCap()) {
|
||||
return 0;
|
||||
}
|
||||
GLuint tex = 0;
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(sJihadPoolLock);
|
||||
for (size_t i = sJihadPool.size(); i-- > 0;) {
|
||||
if (sJihadPool[i].gl == aGL) { // any size: the conversion survives a re-specification
|
||||
tex = sJihadPool[i].tex;
|
||||
sJihadPool.erase(sJihadPool.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tex) sJihadPoolHits++; else sJihadPoolMisses++;
|
||||
const uint32_t n = sJihadPoolHits + sJihadPoolMisses;
|
||||
if (n <= 4 || (n % 50) == 0) {
|
||||
JIHAD_GPU_LOG("[jihad-gpu] texture pool: %u taken from the pool, %u new (%u converted, %.1f ms each), %u kept, %u deleted (pool full), %zu in the pool\n",
|
||||
sJihadPoolHits, sJihadPoolMisses, sJihadConverted, sJihadConverted ? sJihadConvertMs / sJihadConverted : 0.0,
|
||||
sJihadPoolKept, sJihadPoolFull, sJihadPool.size());
|
||||
}
|
||||
}
|
||||
return tex;
|
||||
}
|
||||
|
||||
void JihadDropPooledTextures(GLContext* aGL, bool aDelete)
|
||||
{
|
||||
std::vector<GLuint> mine;
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(sJihadPoolLock);
|
||||
for (size_t i = 0; i < sJihadPool.size();) {
|
||||
if (sJihadPool[i].gl == aGL) {
|
||||
mine.push_back(sJihadPool[i].tex);
|
||||
sJihadPool.erase(sJihadPool.begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aDelete && !mine.empty() && aGL && aGL->MakeCurrent()) {
|
||||
aGL->fDeleteTextures((GLsizei)mine.size(), mine.data());
|
||||
}
|
||||
if (sJihadConvertGL == aGL) {
|
||||
if (aDelete && sJihadConvertFb && aGL && aGL->MakeCurrent()) {
|
||||
aGL->fDeleteFramebuffers(1, &sJihadConvertFb);
|
||||
}
|
||||
sJihadConvertGL = nullptr;
|
||||
sJihadConvertFb = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Jihad T-502: fresh texture names per upload, retired names deleted when the GPU is idle --
|
||||
namespace {
|
||||
mozilla::StaticMutex sJihadRetireLock;
|
||||
std::vector<std::pair<GLContext*, GLuint>> sJihadRetired;
|
||||
}
|
||||
|
||||
void JihadRetireTexture(GLContext* aGL, GLuint aTexture)
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(sJihadRetireLock);
|
||||
sJihadRetired.push_back(std::make_pair(aGL, aTexture));
|
||||
}
|
||||
|
||||
void JihadFlushRetiredTextures(GLContext* aGL)
|
||||
{
|
||||
std::vector<GLuint> mine;
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(sJihadRetireLock);
|
||||
for (size_t i = 0; i < sJihadRetired.size();) {
|
||||
if (sJihadRetired[i].first == aGL) {
|
||||
mine.push_back(sJihadRetired[i].second);
|
||||
sJihadRetired.erase(sJihadRetired.begin() + i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mine.empty() || !aGL || !aGL->MakeCurrent()) {
|
||||
return;
|
||||
}
|
||||
const mozilla::TimeStamp t0 = mozilla::TimeStamp::Now();
|
||||
aGL->fDeleteTextures((GLsizei)mine.size(), mine.data());
|
||||
static uint32_t sN = 0;
|
||||
const double ms = (mozilla::TimeStamp::Now() - t0).ToMilliseconds();
|
||||
if (sN++ < 10 || ms >= 3.0) {
|
||||
if (sN < 200) JIHAD_GPU_LOG("[jihad-gpu] retired textures: %u deleted in %.1f ms\n", (unsigned)mine.size(), ms);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BasicTextureImage::JihadSwapTextures()
|
||||
{
|
||||
// Off unless asked for (JIHAD_GPU_FRESH_TEX=1): measured 2026-10-17 (run 86) the first upload
|
||||
// into a fresh name waits just as the re-upload did (21 ms a tile), so the driver waits for
|
||||
// the pending GPU work, not for the name; the fresh names only added allocation churn.
|
||||
static const bool sFresh = getenv("JIHAD_GPU_COMPOSITOR") && getenv("JIHAD_GPU_FRESH_TEX");
|
||||
if (!sFresh) {
|
||||
return;
|
||||
}
|
||||
if (mTextureState == Created) {
|
||||
return; // never drawn with: the current name is as fresh as a new one
|
||||
}
|
||||
JihadRetireTexture(mGLContext, mTexture);
|
||||
mGLContext->fGenTextures(1, &mTexture);
|
||||
ScopedBindTexture bind(mGLContext, mTexture);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, mTexFilter);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, mTexFilter);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, mWrapMode);
|
||||
mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, mWrapMode);
|
||||
mTextureState = Created;
|
||||
}
|
||||
|
||||
void
|
||||
BasicTextureImage::BindTexture(GLenum aTextureUnit)
|
||||
{
|
||||
@@ -131,6 +343,7 @@ BasicTextureImage::BindTexture(GLenum aTextureUnit)
|
||||
bool
|
||||
BasicTextureImage::DirectUpdate(gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, const gfx::IntPoint& aFrom /* = gfx::IntPoint(0, 0) */)
|
||||
{
|
||||
JihadSwapTextures(); // Jihad T-502: the name the last composite is not reading
|
||||
nsIntRegion region;
|
||||
if (mTextureState == Valid) {
|
||||
region = aRegion;
|
||||
@@ -166,10 +379,16 @@ BasicTextureImage::Resize(const gfx::IntSize& aSize)
|
||||
// we avoid mixing formats.
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
GLenum internalFormat = LOCAL_GL_RGBA;
|
||||
if (mGLContext->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
|
||||
MOZ_ASSERT(!mGLContext->IsGLES());
|
||||
format = LOCAL_GL_BGRA;
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
if (mGLContext->IsGLES()) {
|
||||
// Jihad: BGRA8888 on GLES takes UNSIGNED_BYTE and the same internal format
|
||||
type = LOCAL_GL_UNSIGNED_BYTE;
|
||||
internalFormat = LOCAL_GL_BGRA;
|
||||
} else {
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
}
|
||||
} else {
|
||||
format = LOCAL_GL_RGBA;
|
||||
type = LOCAL_GL_UNSIGNED_BYTE;
|
||||
@@ -177,7 +396,7 @@ BasicTextureImage::Resize(const gfx::IntSize& aSize)
|
||||
|
||||
mGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D,
|
||||
0,
|
||||
LOCAL_GL_RGBA,
|
||||
internalFormat,
|
||||
aSize.width,
|
||||
aSize.height,
|
||||
0,
|
||||
@@ -215,7 +434,9 @@ BasicTextureImage::BasicTextureImage(GLuint aTexture,
|
||||
, mTexture(aTexture)
|
||||
, mTextureState(Created)
|
||||
, mGLContext(aContext)
|
||||
{}
|
||||
{
|
||||
mTexFilter = (aFlags & TextureImage::UseNearestFilter) ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR;
|
||||
}
|
||||
|
||||
static bool
|
||||
WantsSmallTiles(GLContext* gl)
|
||||
@@ -500,8 +721,13 @@ CreateBasicTextureImage(GLContext* aGL,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GLuint texture = 0;
|
||||
aGL->fGenTextures(1, &texture);
|
||||
GLuint texture = JihadTakePooledTexture(aGL, aSize); // Jihad T-504
|
||||
if (!texture) {
|
||||
aGL->fGenTextures(1, &texture);
|
||||
if (JihadPoolCap() && !getenv("JIHAD_GPU_NO_TEX_CONVERT")) {
|
||||
JihadConvertTexture(aGL, texture);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedBindTexture bind(aGL, texture);
|
||||
|
||||
|
||||
@@ -181,6 +181,18 @@ protected:
|
||||
* convert the update surface's pixels into an image on which we can
|
||||
* glTexSubImage2D().
|
||||
*/
|
||||
// Jihad T-502: on the Adreno 220 a glTexImage2D into a name the GPU has drawn with waits for
|
||||
// the pending GPU work (18-78 ms a tile, 2026-10-17 run 83); a never-drawn name takes the
|
||||
// upload at once. So under JIHAD_GPU_COMPOSITOR every upload goes into a fresh name and the
|
||||
// old one is retired here, to be deleted by the compositor once the GPU is idle (deleting a
|
||||
// name in use waits too).
|
||||
void JihadRetireTexture(GLContext* aGL, GLuint aTexture);
|
||||
// Jihad T-504: the GPU path's pool of tile textures (GLTextureImage.cpp explains why)
|
||||
bool JihadPoolTexture(GLContext* aGL, GLuint aTexture, const gfx::IntSize& aSize);
|
||||
GLuint JihadTakePooledTexture(GLContext* aGL, const gfx::IntSize& aSize);
|
||||
void JihadDropPooledTextures(GLContext* aGL, bool aDelete);
|
||||
void JihadFlushRetiredTextures(GLContext* aGL);
|
||||
|
||||
class BasicTextureImage
|
||||
: public TextureImage
|
||||
{
|
||||
@@ -206,6 +218,15 @@ public:
|
||||
protected:
|
||||
GLuint mTexture;
|
||||
TextureState mTextureState;
|
||||
// Jihad T-502: a second GL name, used in turn with the first, under JIHAD_GPU_COMPOSITOR.
|
||||
// On the Adreno 220 binding a texture the last composite still reads waits that
|
||||
// composite out (~11 ms for a 256x256 tile, 2026-10-17 run 77); the name the composite is
|
||||
// not reading takes the upload without waiting. Every upload on that path is a whole one
|
||||
// (GLUploadHelpers: no sub-texture uploads on this GPU), so alternating names is sound.
|
||||
GLuint mTexture2 = 0; // unused since the fresh-name scheme (kept for the destructor)
|
||||
TextureState mTextureState2 = Created;
|
||||
GLint mTexFilter = 0x2601; // GL_LINEAR
|
||||
void JihadSwapTextures(); // now: retire the name and take a fresh one
|
||||
RefPtr<GLContext> mGLContext;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GLUploadHelpers.h"
|
||||
#include "mozilla/TimeStamp.h" // Jihad T-502 device rounds
|
||||
#include <atomic> // Jihad T-517
|
||||
|
||||
#include "GLContext.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
@@ -13,6 +15,8 @@
|
||||
#include "GfxTexturesReporter.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace gfx;
|
||||
@@ -90,8 +94,14 @@ CanUploadSubTextures(GLContext* gl)
|
||||
|
||||
// There are certain GPUs that we don't want to use glTexSubImage2D on
|
||||
// because that function can be very slow and/or buggy
|
||||
// The "(TM)" spellings and the 220 included: on the TouchPad's Adreno 220 twelve 256x256
|
||||
// glTexSubImage2D updates of a 1024x768 texture cost 160 ms against 10 ms for one whole
|
||||
// glTexImage2D (Jihad, tools/device/gpu-bench.c, 2026-10-17).
|
||||
if (gl->Renderer() == GLRenderer::Adreno200 ||
|
||||
gl->Renderer() == GLRenderer::Adreno205)
|
||||
gl->Renderer() == GLRenderer::Adreno205 ||
|
||||
gl->Renderer() == GLRenderer::AdrenoTM200 ||
|
||||
gl->Renderer() == GLRenderer::AdrenoTM205 ||
|
||||
gl->Renderer() == GLRenderer::AdrenoTM220)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -402,6 +412,10 @@ TexImage2DHelper(GLContext* gl,
|
||||
}
|
||||
}
|
||||
|
||||
// Jihad (T-517): every upload's count and time (us), for the timeline's transaction and job lines
|
||||
std::atomic<uint32_t> gJihadUploads(0);
|
||||
std::atomic<uint64_t> gJihadUploadUs(0);
|
||||
|
||||
SurfaceFormat
|
||||
UploadImageDataToTexture(GLContext* gl,
|
||||
unsigned char* aData,
|
||||
@@ -415,9 +429,22 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
GLenum aTextureUnit,
|
||||
GLenum aTextureTarget)
|
||||
{
|
||||
// Jihad T-502 device rounds: the steps before the GL upload call, timed
|
||||
const mozilla::TimeStamp jihadE0 = mozilla::TimeStamp::Now();
|
||||
gl->MakeCurrent();
|
||||
const mozilla::TimeStamp jihadE1 = mozilla::TimeStamp::Now();
|
||||
gl->fActiveTexture(aTextureUnit);
|
||||
gl->fBindTexture(aTextureTarget, aTexture);
|
||||
const mozilla::TimeStamp jihadE2 = mozilla::TimeStamp::Now();
|
||||
struct JihadPreLog { const mozilla::TimeStamp &e0, &e1, &e2; ~JihadPreLog() {
|
||||
static uint32_t sN = 0;
|
||||
const double all = (mozilla::TimeStamp::Now() - e0).ToMilliseconds();
|
||||
++gJihadUploads;
|
||||
gJihadUploadUs += (uint64_t)(all * 1000.0);
|
||||
if (all >= 8.0 && sN++ < 60)
|
||||
JIHAD_GPU_LOG("[jihad-gpu] upload steps: make current %.1f ms, bind %.1f ms, the rest %.1f ms (all %.1f)\n",
|
||||
(e1 - e0).ToMilliseconds(), (e2 - e1).ToMilliseconds(), all - (e2 - e0).ToMilliseconds(), all);
|
||||
} } jihadPreLog{jihadE0, jihadE1, jihadE2};
|
||||
|
||||
GLenum format = 0;
|
||||
GLenum internalFormat = 0;
|
||||
@@ -433,7 +460,7 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
|
||||
format = LOCAL_GL_BGRA;
|
||||
surfaceFormat = SurfaceFormat::R8G8B8A8;
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
type = gl->IsGLES() ? LOCAL_GL_UNSIGNED_BYTE : LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; // Jihad: GLES has no 8_8_8_8_REV; BGRA_EXT takes UNSIGNED_BYTE
|
||||
} else {
|
||||
format = LOCAL_GL_RGBA;
|
||||
surfaceFormat = SurfaceFormat::B8G8R8A8;
|
||||
@@ -447,7 +474,7 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
|
||||
format = LOCAL_GL_BGRA;
|
||||
surfaceFormat = SurfaceFormat::R8G8B8X8;
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
type = gl->IsGLES() ? LOCAL_GL_UNSIGNED_BYTE : LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; // Jihad: GLES has no 8_8_8_8_REV; BGRA_EXT takes UNSIGNED_BYTE
|
||||
} else {
|
||||
format = LOCAL_GL_RGBA;
|
||||
surfaceFormat = SurfaceFormat::B8G8R8X8;
|
||||
@@ -460,7 +487,7 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
// Upload our RGBA as BGRA, but store that the uploaded format is
|
||||
// BGRA. (sample from R to get B)
|
||||
format = LOCAL_GL_BGRA;
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
type = gl->IsGLES() ? LOCAL_GL_UNSIGNED_BYTE : LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; // Jihad: GLES has no 8_8_8_8_REV; BGRA_EXT takes UNSIGNED_BYTE
|
||||
surfaceFormat = SurfaceFormat::B8G8R8A8;
|
||||
} else {
|
||||
format = LOCAL_GL_RGBA;
|
||||
@@ -474,7 +501,7 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
// format used.
|
||||
if (gl->GetPreferredARGB32Format() == LOCAL_GL_BGRA) {
|
||||
format = LOCAL_GL_BGRA;
|
||||
type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV;
|
||||
type = gl->IsGLES() ? LOCAL_GL_UNSIGNED_BYTE : LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; // Jihad: GLES has no 8_8_8_8_REV; BGRA_EXT takes UNSIGNED_BYTE
|
||||
surfaceFormat = SurfaceFormat::B8G8R8X8;
|
||||
} else {
|
||||
format = LOCAL_GL_RGBA;
|
||||
@@ -497,6 +524,9 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unhandled image surface format!");
|
||||
}
|
||||
if (gl->IsGLES() && (format == LOCAL_GL_BGRA || format == LOCAL_GL_RGBA)) {
|
||||
internalFormat = format; // Jihad: GLES2 wants them equal (TexImage2DHelper asserts it)
|
||||
}
|
||||
|
||||
if (aOutUploadSize) {
|
||||
*aOutUploadSize = 0;
|
||||
@@ -506,6 +536,15 @@ UploadImageDataToTexture(GLContext* gl,
|
||||
return gfx::SurfaceFormat::UNKNOWN;
|
||||
}
|
||||
|
||||
// Jihad T-502 device rounds: the format this upload goes up as, and the call's own time
|
||||
static uint32_t sJihadUploads = 0;
|
||||
const mozilla::TimeStamp jihadT0 = mozilla::TimeStamp::Now();
|
||||
struct JihadUpLog { const mozilla::TimeStamp& t0; GLenum fmt; GLenum type; int w, h; bool init; ~JihadUpLog() {
|
||||
const double ms = (mozilla::TimeStamp::Now() - t0).ToMilliseconds();
|
||||
if (sJihadUploads++ < 40 || ms >= 8.0) {
|
||||
if (sJihadUploads < 400) JIHAD_GPU_LOG("[jihad-gpu] upload call %dx%d format 0x%x type 0x%x init %d: %.1f ms\n", w, h, (unsigned)fmt, (unsigned)type, (int)init, ms);
|
||||
}
|
||||
} } jihadUpLog{jihadT0, format, type, aSize.width, aSize.height, aNeedInit};
|
||||
if (aNeedInit || !CanUploadSubTextures(gl)) {
|
||||
// If the texture needs initialized, or we are unable to
|
||||
// upload sub textures, then initialize and upload the entire
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "SharedSurface.h"
|
||||
#include "GLContextEGL.h" // Jihad T-511: the pbuffer readback
|
||||
|
||||
#include "../2d/2D.h"
|
||||
#include "GLBlitHelper.h"
|
||||
@@ -421,11 +422,23 @@ ScopedReadbackFB::ScopedReadbackFB(SharedSurface* src)
|
||||
}
|
||||
case AttachmentType::GLTexture:
|
||||
{
|
||||
mGL->fGenFramebuffers(1, &mTempFB);
|
||||
mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mTempFB);
|
||||
|
||||
GLuint tex = src->ProdTexture();
|
||||
GLenum texImageTarget = src->ProdTextureTarget();
|
||||
// Jihad (T-511): on the GPU path, a canvas frame is drawn onto the context's own pbuffer
|
||||
// and read from there: the Adreno 220 reads a pbuffer ~10x faster than a texture
|
||||
// framebuffer (the WebGL canvas's frame every frame)
|
||||
if (getenv("JIHAD_GPU_COMPOSITOR") && !getenv("JIHAD_GPU_NO_PB_READ") &&
|
||||
mGL->GetContextType() == GLContextType::EGL && texImageTarget == LOCAL_GL_TEXTURE_2D) {
|
||||
const gfx::IntSize pb = GLContextEGL::Cast(mGL)->mJihadPbSize;
|
||||
if (mGL->Screen() && src->mSize.width <= pb.width && src->mSize.height <= pb.height) {
|
||||
mGL->BlitHelper()->BlitTextureToFramebuffer(tex, 0, src->mSize, src->mSize,
|
||||
texImageTarget, true);
|
||||
mGL->Screen()->BindReadFB_Internal(0); // the pbuffer itself, not the screen's FBO
|
||||
break;
|
||||
}
|
||||
}
|
||||
mGL->fGenFramebuffers(1, &mTempFB);
|
||||
mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mTempFB);
|
||||
mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
|
||||
LOCAL_GL_COLOR_ATTACHMENT0,
|
||||
texImageTarget, tex, 0);
|
||||
|
||||
@@ -632,9 +632,13 @@ NVImage::GetAsSourceSurface()
|
||||
|
||||
// Convert the current NV12 or NV21 data to YUV420P so that we can follow the
|
||||
// logics in PlanarYCbCrImage::GetAsSourceSurface().
|
||||
const int bufferLength = mData.mYSize.height * mData.mYStride +
|
||||
mData.mCbCrSize.height * mData.mCbCrSize.width * 2;
|
||||
uint8_t* buffer = new uint8_t[bufferLength];
|
||||
auto bufferLength = CheckedInt<uint32_t>(mData.mYSize.height) * mData.mYStride +
|
||||
CheckedInt<uint32_t>(mData.mCbCrSize.height) * mData.mCbCrSize.width * 2;
|
||||
if (!bufferLength.isValid()) {
|
||||
NS_ERROR("Image buffer length exceeds integer limits.");
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t* buffer = new uint8_t[bufferLength.value()];
|
||||
|
||||
Data aData = mData;
|
||||
aData.mCbCrStride = aData.mCbCrSize.width;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* 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/. */
|
||||
|
||||
#include <cmath> // Jihad T-502: roundf, fabsf
|
||||
#include "LayerTreeInvalidation.h"
|
||||
|
||||
#include <stdint.h> // for uint32_t
|
||||
@@ -26,11 +27,29 @@
|
||||
#include "mozilla/layers/LayerManagerComposite.h"
|
||||
#include "TreeTraversal.h" // for ForEachNode
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
// Jihad T-502 round 115 (see LayerTreeInvalidation.h)
|
||||
static struct { bool on; float scale; bool have; float dx, dy; } sJihadScrollDiff = { false, 1.0f, false, 0.0f, 0.0f };
|
||||
void JihadDiffScrollBegin(bool aOn, float aScale)
|
||||
{
|
||||
sJihadScrollDiff.on = aOn; sJihadScrollDiff.scale = aScale > 0.0f ? aScale : 1.0f;
|
||||
// turning it off keeps what the diff just measured for the caller to take (round 119: the
|
||||
// round-115 shift was cleared here before the bridge could read it)
|
||||
if (aOn) { sJihadScrollDiff.have = false; sJihadScrollDiff.dx = sJihadScrollDiff.dy = 0.0f; }
|
||||
}
|
||||
bool JihadDiffScrollShift(float* aDx, float* aDy)
|
||||
{
|
||||
if (!sJihadScrollDiff.have) return false;
|
||||
*aDx = sJihadScrollDiff.dx; *aDy = sJihadScrollDiff.dy;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LayerPropertiesBase;
|
||||
UniquePtr<LayerPropertiesBase> CloneLayerTreePropertiesInternal(Layer* aRoot, bool aIsMask = false);
|
||||
|
||||
@@ -181,6 +200,50 @@ public:
|
||||
bool transformChanged = !mTransform.FuzzyEqual(GetTransformForInvalidation(mLayer)) ||
|
||||
mLayer->GetPostXScale() != mPostXScale ||
|
||||
mLayer->GetPostYScale() != mPostYScale;
|
||||
if (sJihadScrollDiff.on && transformChanged && !mLayer->GetIsFixedPosition() && !mLayer->GetIsStickyPosition() &&
|
||||
mLayer->GetPostXScale() == mPostXScale && mLayer->GetPostYScale() == mPostYScale) {
|
||||
// Jihad T-502 round 115: a scroll moves the root-content layer by a pure translation; the
|
||||
// rows it carries are shifted by the daemon, so only what else changed is reported
|
||||
const Matrix4x4 now = GetTransformForInvalidation(mLayer);
|
||||
const Matrix4x4& was = mTransform;
|
||||
const bool pure = FuzzyEqual(now._11, was._11) && FuzzyEqual(now._12, was._12) && FuzzyEqual(now._13, was._13) &&
|
||||
FuzzyEqual(now._14, was._14) && FuzzyEqual(now._21, was._21) && FuzzyEqual(now._22, was._22) &&
|
||||
FuzzyEqual(now._23, was._23) && FuzzyEqual(now._24, was._24) && FuzzyEqual(now._31, was._31) &&
|
||||
FuzzyEqual(now._32, was._32) && FuzzyEqual(now._33, was._33) && FuzzyEqual(now._34, was._34) &&
|
||||
FuzzyEqual(now._43, was._43) && FuzzyEqual(now._44, was._44);
|
||||
const float tx = now._41 - was._41, ty = now._42 - was._42;
|
||||
if (pure && FuzzyEqual(tx, 0.0f)) {
|
||||
const bool rootMetrics = mLayer->GetScrollMetadataCount() > 0 &&
|
||||
mLayer->GetScrollMetadata(0).GetMetrics().IsRootContent();
|
||||
const float rows = -ty * sJihadScrollDiff.scale;
|
||||
const bool integral = fabsf(rows - roundf(rows)) < 0.01f;
|
||||
if (rootMetrics && !sJihadScrollDiff.have && integral) {
|
||||
sJihadScrollDiff.have = true; sJihadScrollDiff.dx = tx; sJihadScrollDiff.dy = ty;
|
||||
}
|
||||
if (sJihadScrollDiff.have && FuzzyEqual(ty, sJihadScrollDiff.dy)) {
|
||||
mTransform = now;
|
||||
transformChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Jihad T-502 diagnostic (JIHAD_GPU_DIFFLOG): a transform that changed, and by what
|
||||
static const bool sDiffLog = getenv("JIHAD_GPU_DIFFLOG") != nullptr;
|
||||
static uint32_t sN = 0;
|
||||
if (sDiffLog && transformChanged && sN++ < 400) {
|
||||
const Matrix4x4 now = GetTransformForInvalidation(mLayer);
|
||||
const bool sameLinear = FuzzyEqual(now._11, mTransform._11) && FuzzyEqual(now._12, mTransform._12) &&
|
||||
FuzzyEqual(now._21, mTransform._21) && FuzzyEqual(now._22, mTransform._22);
|
||||
const IntRect vb = mLayer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds();
|
||||
Layer* par = mLayer->GetParent();
|
||||
JIHAD_GPU_LOG("[jihad-gpu] diff: layer %p (parent %p type %d metadata %u, type %d, fixed %d sticky %d, metadata %u, flags 0x%x, visible %d,%d %dx%d, clip %d) transform moved by %.1f,%.1f%s; old ty %.1f new ty %.1f\n",
|
||||
mLayer, par, par ? (int)par->GetType() : -1, par ? (unsigned)par->GetScrollMetadataCount() : 0u,
|
||||
(int)mLayer->GetType(), (int)mLayer->GetIsFixedPosition(), (int)mLayer->GetIsStickyPosition(),
|
||||
(unsigned)mLayer->GetScrollMetadataCount(), (unsigned)mLayer->GetContentFlags(), vb.x, vb.y, vb.width, vb.height,
|
||||
(int)!!mLayer->GetLocalClipRect(), now._41 - mTransform._41, now._42 - mTransform._42,
|
||||
sameLinear ? "" : " (and scaled)", mTransform._42, now._42);
|
||||
}
|
||||
}
|
||||
const Maybe<ParentLayerIntRect>& otherClip = mLayer->GetLocalClipRect();
|
||||
nsIntRegion result;
|
||||
|
||||
@@ -234,6 +297,22 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Jihad T-502 diagnostic (JIHAD_GPU_DIFFLOG): a layer contributing a large change, and why
|
||||
static const bool sDiffLog = getenv("JIHAD_GPU_DIFFLOG") != nullptr;
|
||||
static uint32_t sBig = 0;
|
||||
const IntRect rb = result.GetBounds();
|
||||
if (sDiffLog && rb.height > 300 && sBig++ < 3000) {
|
||||
const IntRect inv = mLayer->GetInvalidRegion().GetRegion().GetBounds();
|
||||
const IntRect vOld = mVisibleRegion.GetBounds();
|
||||
const IntRect vNew = mLayer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds();
|
||||
const Matrix4x4 now = GetTransformForInvalidation(mLayer);
|
||||
JIHAD_GPU_LOG("[jihad-gpu] diff big: layer %p type %d metadata %u: result y %d..%d; transform changed %d (ty %.1f -> %.1f), invalid (layer) y %d..%d, visible old y %d..%d new y %d..%d, clip changed %d\n",
|
||||
mLayer, (int)mLayer->GetType(), (unsigned)mLayer->GetScrollMetadataCount(), rb.y, rb.YMost(), (int)transformChanged,
|
||||
mTransform._42, now._42, inv.y, inv.YMost(), vOld.y, vOld.YMost(), vNew.y, vNew.YMost(),
|
||||
(int)(mUseClipRect && otherClip && !mClipRect.IsEqualInterior(*otherClip)));
|
||||
}
|
||||
}
|
||||
mLayer->ClearInvalidRect();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,13 @@ typedef void (*NotifySubDocInvalidationFunc)(ContainerLayer* aLayer,
|
||||
* A set of cached layer properties (including those of child layers),
|
||||
* used for comparing differences in layer trees.
|
||||
*/
|
||||
// Jihad T-502 round 115: the next ComputeDifferences treats a pure vertical translation of the
|
||||
// root-content scroll layer (and of any non-fixed layer moved by the same amount) as a scroll, not
|
||||
// a change, when it moves the frame by whole rows at aScale (the world scale). The shift it saw,
|
||||
// in layer pixels, is reported by JihadDiffScrollShift; false when none.
|
||||
void JihadDiffScrollBegin(bool aOn, float aScale);
|
||||
bool JihadDiffScrollShift(float* aDx, float* aDy);
|
||||
|
||||
struct LayerProperties
|
||||
{
|
||||
protected:
|
||||
|
||||
+12
-1
@@ -1397,7 +1397,18 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS
|
||||
checkClipRect = true;
|
||||
checkMaskLayers = true;
|
||||
} else {
|
||||
if (gfx::ThebesMatrix(contTransform).HasNonIntegerTranslation()) {
|
||||
// Jihad T-502: HasNonIntegerTranslation() is also true for any SCALE, so under the
|
||||
// offscreen card's world zoom every container with a clipped child (the root, with the
|
||||
// scroll frame's clip) rendered through an intermediate surface the size of its visible
|
||||
// region: one more full-frame pass a composite, nothing of the displayport beyond the
|
||||
// viewport, and fixed-layer compensation measured in the surface's space. On the
|
||||
// compositor's tree an axis-aligned scale+translate clips fine by scissor
|
||||
// (CalculateScissorRect transforms the clip by the parent's effective transform; the
|
||||
// edge may be a pixel off), so no surface for that case. JIHAD_GPU_SURFACES=1 restores it.
|
||||
static const bool sJihadNoSurface = getenv("JIHAD_GPU_COMPOSITOR") && !getenv("JIHAD_GPU_SURFACES");
|
||||
const bool jihadScissorable = sJihadNoSurface && Manager() && Manager()->AsLayerManagerComposite() &&
|
||||
contTransform.PreservesAxisAlignedRectangles();
|
||||
if (gfx::ThebesMatrix(contTransform).HasNonIntegerTranslation() && !jihadScissorable) {
|
||||
checkClipRect = true;
|
||||
}
|
||||
/* In 2D case, only translation and/or positive scaling can be done w/o using IntermediateSurface.
|
||||
|
||||
@@ -87,12 +87,19 @@ public:
|
||||
bool
|
||||
BasicPlanarYCbCrImage::CopyData(const Data& aData)
|
||||
{
|
||||
RecyclingPlanarYCbCrImage::CopyData(aData);
|
||||
|
||||
if (mDelayedConversion) {
|
||||
if (!RecyclingPlanarYCbCrImage::CopyData(aData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The YCbCr copy above succeeded; delaying the RGB conversion is not a
|
||||
// failure. GetAsSourceSurface() converts on demand when mDecodedBuffer is
|
||||
// null. Returning false here made VideoData::CreateAndCopyData report OOM
|
||||
// (MEDIA_ERR_DECODE) for any frame decoded while BasicImageLayer::Paint had
|
||||
// swapped this factory into the video's ImageContainer.
|
||||
if (mDelayedConversion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do some sanity checks to prevent integer overflow
|
||||
if (aData.mYSize.width > PlanarYCbCrImage::MAX_DIMENSION ||
|
||||
aData.mYSize.height > PlanarYCbCrImage::MAX_DIMENSION) {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "gfxUtils.h"
|
||||
#include "TextureClientSharedSurface.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::gl;
|
||||
@@ -69,6 +71,22 @@ ClientCanvasLayer::Initialize(const Data& aData)
|
||||
}
|
||||
|
||||
UniquePtr<SurfaceFactory> factory = GLScreenBuffer::CreateFactory(mGLContext, caps, forwarder, mFlags);
|
||||
// Jihad (T-511): a WebGL canvas whose context shares the compositor's textures (the share root,
|
||||
// GLContextProviderEGL.cpp) hands the compositor its texture: no readback, no upload
|
||||
if (!factory && !mGLFrontbuffer && !aData.mIsMirror && getenv("JIHAD_GPU_COMPOSITOR") &&
|
||||
!getenv("JIHAD_GPU_NO_SHARE") && mGLContext->GetContextType() == gl::GLContextType::EGL &&
|
||||
mGLContext->GetSharedContext()) {
|
||||
RefPtr<LayersIPCChannel> channel = forwarder->GetTextureForwarder();
|
||||
factory = MakeUnique<gl::SurfaceFactory_GLTexture>(mGLContext, caps, channel, mFlags);
|
||||
}
|
||||
// Jihad (T-511): which way a WebGL canvas reaches the compositor on the GPU path (the basic surface:
|
||||
// read back, through the context's pbuffer, ScopedReadbackFB, and uploaded each frame)
|
||||
if (getenv("JIHAD_GPU_COMPOSITOR")) {
|
||||
static int sLogged = 0;
|
||||
if (sLogged++ < 4)
|
||||
JIHAD_GPU_LOG("[jihad-gpu] canvas layer: %s\n",
|
||||
factory ? "a shared surface (zero copy)" : "the basic surface (read back through the pbuffer and uploaded each frame)");
|
||||
}
|
||||
|
||||
if (mGLFrontbuffer || aData.mIsMirror) {
|
||||
// We're using a source other than the one in the default screen.
|
||||
|
||||
@@ -441,7 +441,12 @@ ClientLayerManager::DidComposite(uint64_t aTransactionId,
|
||||
const TimeStamp& aCompositeStart,
|
||||
const TimeStamp& aCompositeEnd)
|
||||
{
|
||||
MOZ_ASSERT(mWidget);
|
||||
// Jihad (T-515): the embedder's offscreen compose jobs can be acknowledged after the widget is
|
||||
// gone (a card closed while a job it had asked for was waited out): nothing is left to tell. The
|
||||
// widget's teardown nulls mWidget; it was dereferenced here (SIGSEGV at 0, 2026-10-19).
|
||||
if (!mWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// |aTransactionId| will be > 0 if the compositor is acknowledging a shadow
|
||||
// layers transaction.
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "gfx2DGlue.h"
|
||||
#include "ReadbackProcessor.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
@@ -146,6 +148,12 @@ already_AddRefed<PaintedLayer>
|
||||
ClientLayerManager::CreatePaintedLayerWithHint(PaintedLayerCreationHint aHint)
|
||||
{
|
||||
NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
|
||||
{
|
||||
// Jihad T-502 device rounds: whether painted layers are tiled (layers.enable-tiles is a
|
||||
// once-only gfx pref: goanna.js, not prefs.js)
|
||||
static bool sLogged = false;
|
||||
if (!sLogged) { sLogged = true; JIHAD_GPU_LOG("[jihad-gpu] painted layers: %s\n", gfxPrefs::LayersTilesEnabled() ? "tiled" : "single-buffer (not tiled)"); }
|
||||
}
|
||||
if (gfxPrefs::LayersTilesEnabled()) {
|
||||
RefPtr<ClientTiledPaintedLayer> layer = new ClientTiledPaintedLayer(this, aHint);
|
||||
CREATE_SHADOW(Painted);
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch
|
||||
#include "VsyncSource.h"
|
||||
|
||||
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
|
||||
|
||||
struct nsCSSValueSharedList;
|
||||
|
||||
namespace mozilla {
|
||||
@@ -512,6 +514,15 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aTransformedSubtreeRoo
|
||||
// We want to translate the layer by the difference between
|
||||
// |transformedAnchor| and |anchor|.
|
||||
LayerPoint translation = transformedAnchor - anchor;
|
||||
{
|
||||
// Jihad T-502 diagnostic (every 50th fixed/sticky layer aligned)
|
||||
static uint32_t sN = 0;
|
||||
if ((sN++ % 50) == 0) {
|
||||
JIHAD_GPU_LOG("[jihad-gpu] align fixed: layer %p (parent %p, fixed %d sticky %d) under subtree root %p (root's parent %p), scroll id %llu: anchor %.1f,%.1f, translation %.1f,%.1f\n",
|
||||
layer, layer->GetParent(), (int)layer->GetIsFixedPosition(), (int)layer->GetIsStickyPosition(), aTransformedSubtreeRoot,
|
||||
aTransformedSubtreeRoot->GetParent(), (unsigned long long)aTransformScrollId, anchor.x, anchor.y, translation.x, translation.y);
|
||||
}
|
||||
}
|
||||
|
||||
// A fixed layer will "consume" (be unadjusted by) the entire translation
|
||||
// calculated above. A sticky layer may consume all, part, or none of it,
|
||||
@@ -537,6 +548,24 @@ AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aTransformedSubtreeRoo
|
||||
unconsumedTranslation = translation - originalTranslation;
|
||||
}
|
||||
|
||||
// Jihad T-502 round 127: a sticky layer held in place this composite counts as fixed for the
|
||||
// readback. T-503: also one the engine has already laid out stuck (an anchor move just put the
|
||||
// async offset at zero, so it is not adjusted now): the next pan steps will hold it. Probed
|
||||
// with a step's translation both ways against its sticky ranges.
|
||||
if (layer->GetIsStickyPosition()) {
|
||||
bool held = fabsf(translation.x) > 0.5f || fabsf(translation.y) > 0.5f;
|
||||
if (!held) {
|
||||
const LayerRect& outer = layer->GetStickyScrollRangeOuter();
|
||||
const LayerRect& inner = layer->GetStickyScrollRangeInner();
|
||||
const LayerPoint base = translation - unconsumedTranslation; // the translation before the sticky clamp
|
||||
for (const float probe : { mJihadProbe, -mJihadProbe }) {
|
||||
const float ty = base.y + probe;
|
||||
const float hy = IntervalOverlap(ty, outer.y, outer.YMost()) - IntervalOverlap(ty, inner.y, inner.YMost());
|
||||
if (fabsf(hy) > 0.5f) held = true;
|
||||
}
|
||||
}
|
||||
if (held) mJihadStuck.push_back(layer);
|
||||
}
|
||||
// Finally, apply the translation to the layer transform. Note that in cases
|
||||
// where the async transform on |aTransformedSubtreeRoot| affects this layer's
|
||||
// clip rect, we need to apply the same translation to said clip rect, so
|
||||
@@ -833,6 +862,19 @@ bool
|
||||
AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer,
|
||||
bool* aOutFoundRoot)
|
||||
{
|
||||
mJihadStuck.clear(); // Jihad T-502 round 127: this composite's stuck sticky layers
|
||||
{
|
||||
// T-513 (review): a sticky layer is probed as far as twice this composite's scroll step (64 px at
|
||||
// least): a fast flick crossed the threshold by more than a fixed 64 px in one frame, its stuck
|
||||
// position was not read and its old pixels slid with the content as a ghost strip
|
||||
// (a frame may compose more than once at one scroll: only a move sets it)
|
||||
const float step = mJihadScrollValid ? fabsf(mJihadScrollY - mJihadPrevScrollY) : 0.0f;
|
||||
if (step > 0.5f) {
|
||||
mJihadProbe = std::max(64.0f, std::min(2.0f * step, 1024.0f));
|
||||
mJihadPrevScrollY = mJihadScrollY;
|
||||
}
|
||||
if (mJihadProbeOverride > 0.0f) mJihadProbe = mJihadProbeOverride; // T-504: an overview composite
|
||||
}
|
||||
bool appliedTransform = false;
|
||||
std::stack<Maybe<ParentLayerIntRect>> stackDeferredClips;
|
||||
|
||||
@@ -909,23 +951,56 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer,
|
||||
}
|
||||
}
|
||||
|
||||
// Jihad T-502 diagnostic: what the tree offers the card's scroll (every 50th layer with
|
||||
// metadata, and every 500th composite's root when nothing has any)
|
||||
{
|
||||
static uint32_t sSeen = 0, sRootSeen = 0;
|
||||
if (layer->GetScrollMetadataCount() > 0 && (sSeen++ % 50) == 0) {
|
||||
const FrameMetrics& m0 = layer->GetScrollMetadata(0).GetMetrics();
|
||||
JIHAD_GPU_LOG("[jihad-gpu] async scroll: layer %p has %u metadata (first: root content %d, id %llu, offset %.1f,%.1f), card scroll valid %d want %.1f,%.1f, controller %d\n",
|
||||
layer, (unsigned)layer->GetScrollMetadataCount(), (int)m0.IsRootContent(), (unsigned long long)m0.GetScrollId(),
|
||||
m0.GetScrollOffset().x, m0.GetScrollOffset().y, (int)mJihadScrollValid, mJihadScrollX, mJihadScrollY, (int)(layer->GetAsyncPanZoomController(0) != nullptr));
|
||||
} else if (layer->GetScrollMetadataCount() == 0 && !layer->GetParent() && (sRootSeen++ % 500) == 0) {
|
||||
JIHAD_GPU_LOG("[jihad-gpu] async scroll: the root layer %p has no metadata (card scroll valid %d want %.1f,%.1f)\n", layer, (int)mJihadScrollValid, mJihadScrollX, mJihadScrollY);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
|
||||
AsyncPanZoomController* controller = layer->GetAsyncPanZoomController(i);
|
||||
// Jihad T-502: without APZ, the card's scroll on the root scroll frame's content
|
||||
AsyncTransform jihadAsync;
|
||||
bool jihadScroll = false;
|
||||
if (!controller) {
|
||||
continue;
|
||||
const FrameMetrics& jm = layer->GetScrollMetadata(i).GetMetrics();
|
||||
if (mJihadScrollValid && jm.IsRootContent()) {
|
||||
const CSSPoint want(mJihadScrollX, mJihadScrollY);
|
||||
const ParentLayerPoint translation = (want - jm.GetScrollOffset()) * jm.GetZoom();
|
||||
jihadAsync = AsyncTransform(LayerToParentLayerScale(1.0f), -translation);
|
||||
jihadScroll = true;
|
||||
static uint32_t sN = 0;
|
||||
if (sN++ < 10 || (sN % 25) == 0) {
|
||||
JIHAD_GPU_LOG("[jihad-gpu] async scroll: want %.1f,%.1f, painted at %.1f,%.1f, zoom %.4f x %.4f -> translation %.1f,%.1f on layer %p (parent %p, id %llu)\n",
|
||||
want.x, want.y, jm.GetScrollOffset().x, jm.GetScrollOffset().y, jm.GetZoom().xScale, jm.GetZoom().yScale, -translation.x, -translation.y,
|
||||
layer, layer->GetParent(), (unsigned long long)jm.GetScrollId());
|
||||
}
|
||||
}
|
||||
if (!jihadScroll) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
hasAsyncTransform = true;
|
||||
|
||||
AsyncTransform asyncTransformWithoutOverscroll =
|
||||
controller->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
|
||||
AsyncTransformComponentMatrix overscrollTransform =
|
||||
controller->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
|
||||
AsyncTransform asyncTransformWithoutOverscroll = controller
|
||||
? controller->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE)
|
||||
: jihadAsync;
|
||||
AsyncTransformComponentMatrix overscrollTransform = controller
|
||||
? controller->GetOverscrollTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE)
|
||||
: AsyncTransformComponentMatrix();
|
||||
AsyncTransformComponentMatrix asyncTransform =
|
||||
AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll)
|
||||
* overscrollTransform;
|
||||
|
||||
if (!layer->IsScrollInfoLayer()) {
|
||||
if (controller && !layer->IsScrollInfoLayer()) {
|
||||
controller->MarkAsyncTransformAppliedToContent();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef GFX_ASYNCCOMPOSITIONMANAGER_H
|
||||
#define GFX_ASYNCCOMPOSITIONMANAGER_H
|
||||
|
||||
#include <vector> // Jihad T-502
|
||||
#include "Units.h" // for ScreenPoint, etc
|
||||
#include "mozilla/layers/LayerManagerComposite.h" // for LayerManagerComposite
|
||||
#include "mozilla/Attributes.h" // for final, etc
|
||||
@@ -115,6 +116,23 @@ public:
|
||||
// Returns true if the next composition will be the first for a
|
||||
// particular document.
|
||||
bool IsFirstPaint() { return mIsFirstPaint; }
|
||||
// Jihad T-502: the card's scroll position (CSS px, absolute), applied to the root scroll
|
||||
// frame's content as an async transform while the engine's own scroll (the metrics' offset)
|
||||
// stays where the last paint put it; fixed and sticky layers are aligned as under APZ.
|
||||
void JihadSetScroll(float aX, float aY, bool aValid) { mJihadScrollX = aX; mJihadScrollY = aY; mJihadScrollValid = aValid; }
|
||||
// round 127: the sticky layers the last composite held in place (a nonzero alignment, or no
|
||||
// async offset to tell by); a sticky layer scrolling with the content is not fixed rows
|
||||
bool JihadIsStuck(Layer* aLayer) const {
|
||||
for (Layer* l : mJihadStuck) if (l == aLayer) return true;
|
||||
return false;
|
||||
}
|
||||
void JihadClearStuck() { mJihadStuck.clear(); }
|
||||
// T-504: the sticky probe's state, kept across a verify composite (the daemon's overview windows sit a frame of
|
||||
// rows from the card's scroll, and their step would leave a probe of up to 1024 px for the next real frame), and
|
||||
// a fixed probe for the overview's composites (every window lists the same sticky layers)
|
||||
void JihadProbeState(float* aPrevY, float* aProbe) const { *aPrevY = mJihadPrevScrollY; *aProbe = mJihadProbe; }
|
||||
void JihadSetProbeState(float aPrevY, float aProbe) { mJihadPrevScrollY = aPrevY; mJihadProbe = aProbe; }
|
||||
void JihadSetProbeOverride(float aProbe) { mJihadProbeOverride = aProbe; }
|
||||
|
||||
// GetFrameUniformity will return the frame uniformity for each layer attached to an APZ
|
||||
// from the recorded data in RecordShadowTransform
|
||||
@@ -225,6 +243,12 @@ private:
|
||||
// front-end (e.g. Java on Android) about this so that it take the new page
|
||||
// size and zoom into account when providing us with the next view transform.
|
||||
bool mIsFirstPaint;
|
||||
float mJihadScrollX = 0.0f, mJihadScrollY = 0.0f; // Jihad T-502
|
||||
bool mJihadScrollValid = false;
|
||||
float mJihadPrevScrollY = 0.0f; // T-513: the card's scroll at the composite before
|
||||
float mJihadProbe = 64.0f; // T-513: how far a sticky layer is probed for sticking (layer px)
|
||||
float mJihadProbeOverride = 0.0f; // T-504: this composite's probe (0: from the scroll step)
|
||||
std::vector<Layer*> mJihadStuck; // round 127 (raw: valid until the next composite)
|
||||
|
||||
// This flag is set during a layers update, so that the first composition
|
||||
// after a layers update has it set. It is cleared after that first composition.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "LayerManagerComposite.h"
|
||||
#include "LayerTreeInvalidation.h" // Jihad T-502: JihadDiffScrollBegin
|
||||
#include <stddef.h> // for size_t
|
||||
#include <stdint.h> // for uint16_t, uint32_t
|
||||
#include "CanvasLayerComposite.h" // for CanvasLayerComposite
|
||||
@@ -416,6 +417,64 @@ LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp,
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
LayerManagerComposite::JihadPrepareOnly()
|
||||
{
|
||||
if (!mRoot) {
|
||||
return;
|
||||
}
|
||||
nsIntRegion opaque;
|
||||
LayerIntRegion visible;
|
||||
PostProcessLayers(mRoot, opaque, visible, Nothing());
|
||||
mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld);
|
||||
}
|
||||
|
||||
nsIntRegion
|
||||
LayerManagerComposite::JihadDiffOnly()
|
||||
{
|
||||
nsIntRegion changed;
|
||||
if (!mRoot) {
|
||||
return changed;
|
||||
}
|
||||
nsIntRegion opaque;
|
||||
LayerIntRegion visible;
|
||||
PostProcessLayers(mRoot, opaque, visible, Nothing());
|
||||
mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld);
|
||||
if (mClonedLayerTreeProperties) {
|
||||
JihadDiffScrollBegin(mJihadCompensateScroll, mJihadWorld._22);
|
||||
bool geometryChanged = false;
|
||||
changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &geometryChanged);
|
||||
JihadDiffScrollBegin(false, 1.0f);
|
||||
if (geometryChanged) mGeometryChanged = true;
|
||||
if (!mJihadWorld.IsIdentity() && !changed.IsEmpty()) {
|
||||
nsIntRegion scaled;
|
||||
for (auto iter = changed.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const gfx::IntRect& r = iter.Get();
|
||||
gfx::Rect fr = mJihadWorld.TransformBounds(gfx::Rect(r.x, r.y, r.width, r.height));
|
||||
fr.RoundOut();
|
||||
scaled.OrWith(gfx::IntRect(int32_t(fr.x), int32_t(fr.y), int32_t(fr.width), int32_t(fr.height)));
|
||||
}
|
||||
changed = Move(scaled);
|
||||
}
|
||||
} else {
|
||||
changed = mRenderBounds;
|
||||
}
|
||||
mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Jihad T-502 round 139 (JIHAD_GPU_PHASES=1): where a composite's CPU time goes, in ms:
|
||||
// 0 post-process, 1 transforms + diff, 2 begin frame, 3 prepare, 4 render layers, 5 end frame,
|
||||
// 6 shadow properties, 7 async transforms (CompositorBridgeParent), 8 draw calls' time, 9 draw
|
||||
// calls, 10 draws culled (CompositorOGL), 11 texture uploads' time (TextureHostOGL)
|
||||
double gJihadPhase[12];
|
||||
static bool
|
||||
JihadPhases()
|
||||
{
|
||||
static const bool sOn = getenv("JIHAD_GPU_PHASES") != nullptr;
|
||||
return sOn;
|
||||
}
|
||||
|
||||
void
|
||||
LayerManagerComposite::UpdateAndRender()
|
||||
{
|
||||
@@ -424,18 +483,38 @@ LayerManagerComposite::UpdateAndRender()
|
||||
|
||||
nsIntRegion opaque;
|
||||
LayerIntRegion visible;
|
||||
const TimeStamp jihadP0 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
PostProcessLayers(mRoot, opaque, visible, Nothing());
|
||||
if (JihadPhases()) gJihadPhase[0] += (TimeStamp::Now() - jihadP0).ToMilliseconds();
|
||||
const TimeStamp jihadP1 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
|
||||
if (mClonedLayerTreeProperties) {
|
||||
if (mClonedLayerTreeProperties && mJihadHoldBaseline) {
|
||||
mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld); // T-503: no diff
|
||||
didEffectiveTransforms = true;
|
||||
} else if (mClonedLayerTreeProperties) {
|
||||
// Effective transforms are needed by ComputeDifferences().
|
||||
mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
|
||||
mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld); // Jihad T-502: the identity unless set
|
||||
didEffectiveTransforms = true;
|
||||
|
||||
// We need to compute layer tree differences even if we're not going to
|
||||
// immediately use the resulting damage area, since ComputeDifferences
|
||||
// is also responsible for invalidates intermediate surfaces in
|
||||
// ContainerLayers.
|
||||
JihadDiffScrollBegin(mJihadCompensateScroll && !mTarget, mJihadWorld._22); // Jihad T-502 round 115
|
||||
nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged);
|
||||
JihadDiffScrollBegin(false, 1.0f);
|
||||
// Jihad T-502 round 115: the diff is in the root layer's space; the frame is the world
|
||||
// transform of it (the card's zoom), and the scissor and the rows read are frame pixels
|
||||
if (!mTarget && !mJihadWorld.IsIdentity() && !changed.IsEmpty()) {
|
||||
nsIntRegion scaled;
|
||||
for (auto iter = changed.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const gfx::IntRect& r = iter.Get();
|
||||
gfx::Rect fr = mJihadWorld.TransformBounds(gfx::Rect(r.x, r.y, r.width, r.height));
|
||||
fr.RoundOut();
|
||||
scaled.OrWith(gfx::IntRect(int32_t(fr.x), int32_t(fr.y), int32_t(fr.width), int32_t(fr.height)));
|
||||
}
|
||||
changed = Move(scaled);
|
||||
}
|
||||
|
||||
if (mTarget) {
|
||||
// Since we're composing to an external target, we're not going to use
|
||||
@@ -459,13 +538,49 @@ LayerManagerComposite::UpdateAndRender()
|
||||
|
||||
// Add any additional invalid rects from the window manager or previous
|
||||
// damage computed during ComposeToTarget().
|
||||
invalid.Or(invalid, mInvalidRegion);
|
||||
mInvalidRegion.SetEmpty();
|
||||
if (!mJihadHoldBaseline) { // T-503: a verify composite leaves it for the next frame
|
||||
invalid.Or(invalid, mInvalidRegion);
|
||||
mInvalidRegion.SetEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// Jihad T-502: what the tree changed is reported as such; the rows the daemon will read are
|
||||
// composed as well (a partial, scissored composite covers exactly the rows read back).
|
||||
if (mJihadIgnoreDiff && !mTarget) {
|
||||
invalid.SetEmpty();
|
||||
mJihadLastChanged = gfx::IntRect();
|
||||
} else {
|
||||
// round 124: clipped to the frame before the bounds (two strips off the frame are not a change)
|
||||
nsIntRegion onFrame;
|
||||
onFrame.And(invalid, mRenderBounds);
|
||||
mJihadLastChanged = onFrame.GetBounds();
|
||||
}
|
||||
mJihadPartialOn = false;
|
||||
if ((!mJihadBand.IsEmpty() || !mJihadBand2.IsEmpty() || !mJihadBand3.IsEmpty() || mJihadRegionN > 0) && !mTarget) {
|
||||
if (!mJihadBand.IsEmpty()) invalid.Or(invalid, mJihadBand);
|
||||
if (!mJihadBand2.IsEmpty()) invalid.Or(invalid, mJihadBand2);
|
||||
if (!mJihadBand3.IsEmpty()) invalid.Or(invalid, mJihadBand3);
|
||||
for (int k = 0; k < mJihadRegionN; k++) invalid.Or(invalid, mJihadRegionRect[k]); // T-517
|
||||
mJihadPartialOn = true;
|
||||
} else if (!mTarget && !mJihadLastChanged.IsEmpty() && mClonedLayerTreeProperties &&
|
||||
!invalid.Contains(mRenderBounds)) {
|
||||
if (mJihadWholeRows) {
|
||||
// T-505: a frame without its parts reads the changed rows whole (no column read, review D4): the
|
||||
// composite covers them whole too, or the columns beside the change keep what the target held
|
||||
// from its last composite (a frame with its parts: their pixels then pass for the page beneath
|
||||
// them, and a later shift shows them; found 2026-11-02 on the long page's first shifted frame)
|
||||
const gfx::IntRect b = invalid.GetBounds();
|
||||
const gfx::IntRect rows = gfx::IntRect(mRenderBounds.x, b.y, mRenderBounds.width, b.height).Intersect(mRenderBounds);
|
||||
invalid = nsIntRegion(rows);
|
||||
mJihadLastChanged = rows;
|
||||
}
|
||||
mJihadPartialOn = true; // only what changed, when that is less than the frame
|
||||
}
|
||||
if (invalid.IsEmpty() && !mWindowOverlayChanged) {
|
||||
// Composition requested, but nothing has changed. Don't do any work.
|
||||
mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
|
||||
if (!mJihadHoldBaseline) mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
|
||||
mJihadLastInvalid = mJihadLastChanged = gfx::IntRect(); // Jihad T-502: nothing to read back
|
||||
mJihadPartialOn = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -476,15 +591,17 @@ LayerManagerComposite::UpdateAndRender()
|
||||
if (!didEffectiveTransforms) {
|
||||
// The results of our drawing always go directly into a pixel buffer,
|
||||
// so we don't need to pass any global transform here.
|
||||
mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
|
||||
mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld); // Jihad T-502: the identity unless set
|
||||
}
|
||||
|
||||
mJihadLastInvalid = invalid.GetBounds(); // Jihad T-502
|
||||
if (JihadPhases()) gJihadPhase[1] += (TimeStamp::Now() - jihadP1).ToMilliseconds();
|
||||
Render(invalid, opaque);
|
||||
mGeometryChanged = false;
|
||||
mWindowOverlayChanged = false;
|
||||
|
||||
// Update cached layer tree information.
|
||||
mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
|
||||
if (!mJihadHoldBaseline) mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); // T-503
|
||||
}
|
||||
|
||||
already_AddRefed<DrawTarget>
|
||||
@@ -901,7 +1018,17 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegi
|
||||
CompositorBench(mCompositor, bounds);
|
||||
|
||||
MOZ_ASSERT(mRoot->GetOpacity() == 1);
|
||||
if (mRoot->GetClipRect()) {
|
||||
const TimeStamp jihadB0 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
if (mJihadPartialOn) {
|
||||
// Jihad T-502: a partial composite, clipped (and cleared) to what is being read back
|
||||
IntRect rect = aInvalidRegion.GetBounds().Intersect(bounds);
|
||||
if (mRoot->GetClipRect()) {
|
||||
const ParentLayerIntRect& rc = *mRoot->GetClipRect();
|
||||
rect = rect.Intersect(IntRect(rc.x, rc.y, rc.width, rc.height));
|
||||
}
|
||||
clipRect = ParentLayerIntRect(rect.x, rect.y, rect.width, rect.height);
|
||||
mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, aOpaqueRegion, nullptr, &actualBounds);
|
||||
} else if (mRoot->GetClipRect()) {
|
||||
clipRect = *mRoot->GetClipRect();
|
||||
IntRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
|
||||
mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, aOpaqueRegion, nullptr, &actualBounds);
|
||||
@@ -928,8 +1055,31 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegi
|
||||
}
|
||||
|
||||
// Render our layers.
|
||||
const TimeStamp jihadR0 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
if (JihadPhases()) gJihadPhase[2] += (jihadR0 - jihadB0).ToMilliseconds();
|
||||
if (mJihadRegionN > 0 && mJihadPartialOn) {
|
||||
// T-517: the band frame's regions, each at its own world and inside its own rect, in this one
|
||||
// frame: the transaction, the diff and BeginFrame/EndFrame once (a pass cost ~5 ms of CPU
|
||||
// whatever it drew; a Wikipedia pan frame took four or five of them)
|
||||
for (int k = 0; k < mJihadRegionN; k++) {
|
||||
IntRect r = mJihadRegionRect[k].Intersect(IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height));
|
||||
if (r.IsEmpty()) continue;
|
||||
mRoot->ComputeEffectiveTransforms(mJihadRegionWorld[k]);
|
||||
const ParentLayerIntRect rc(r.x, r.y, r.width, r.height);
|
||||
RootLayer()->Prepare(ViewAs<RenderTargetPixel>(rc, PixelCastJustification::RenderTargetIsParentLayerForRoot));
|
||||
RootLayer()->RenderLayer(r);
|
||||
}
|
||||
mRoot->ComputeEffectiveTransforms(mJihadWorld); // the state the frame's other users expect
|
||||
if (JihadPhases()) gJihadPhase[4] += (TimeStamp::Now() - jihadR0).ToMilliseconds();
|
||||
} else {
|
||||
RootLayer()->Prepare(ViewAs<RenderTargetPixel>(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot));
|
||||
const TimeStamp jihadR1 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
RootLayer()->RenderLayer(clipRect.ToUnknownRect());
|
||||
if (JihadPhases()) {
|
||||
gJihadPhase[3] += (jihadR1 - jihadR0).ToMilliseconds();
|
||||
gJihadPhase[4] += (TimeStamp::Now() - jihadR1).ToMilliseconds();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mRegionToClear.IsEmpty()) {
|
||||
for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) {
|
||||
@@ -955,7 +1105,9 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegi
|
||||
PROFILER_LABEL("LayerManagerComposite", "EndFrame",
|
||||
js::ProfileEntry::Category::GRAPHICS);
|
||||
|
||||
const TimeStamp jihadE0 = JihadPhases() ? TimeStamp::Now() : TimeStamp();
|
||||
mCompositor->EndFrame();
|
||||
if (JihadPhases()) gJihadPhase[5] += (TimeStamp::Now() - jihadE0).ToMilliseconds();
|
||||
|
||||
// Call after EndFrame()
|
||||
mCompositor->SetDispAcquireFence(mRoot);
|
||||
|
||||
@@ -115,6 +115,46 @@ public:
|
||||
MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)");
|
||||
return false;
|
||||
}
|
||||
// Jihad T-502: a transform applied to the whole tree at the root (the card's zoom and the
|
||||
// visual viewport's residual offset), through ComputeEffectiveTransforms so every clip and
|
||||
// scissor follows it.
|
||||
void JihadSetWorldTransform(const gfx::Matrix4x4& aMatrix) { mJihadWorld = aMatrix; }
|
||||
// Jihad T-502: the bounds of what the last UpdateAndRender found changed (empty when it drew
|
||||
// nothing), so the daemon reads back only the rows that moved.
|
||||
const gfx::IntRect& JihadLastInvalid() const { return mJihadLastInvalid; } // what was composed
|
||||
const gfx::IntRect& JihadLastChanged() const { return mJihadLastChanged; } // what the tree changed
|
||||
void JihadResetLastInvalid() { mJihadLastInvalid = mJihadLastChanged = gfx::IntRect(); }
|
||||
// The rows the daemon is about to read: composed (cleared and drawn, scissored) together
|
||||
// with what the tree changed, and nothing else. Empty: only what changed.
|
||||
// T-503: a verify composite keeps the diff baseline and the pending invalid region (the next
|
||||
// real frame must see the changes the verify composite also drew)
|
||||
void JihadHoldBaseline(bool aHold) { mJihadHoldBaseline = aHold; }
|
||||
void JihadSetBand(const gfx::IntRect& aBand, const gfx::IntRect& aBand2 = gfx::IntRect(), const gfx::IntRect& aBand3 = gfx::IntRect()) { mJihadBand = aBand; mJihadBand2 = aBand2; mJihadBand3 = aBand3; }
|
||||
// T-517: a band frame's regions composed in one frame (one transaction, one BeginFrame and
|
||||
// EndFrame): region k is the layer tree at world aWorlds[k], drawn only inside aRects[k] (target
|
||||
// pixels). aN 0 ends it. At most 12.
|
||||
void JihadSetRegions(const gfx::Matrix4x4* aWorlds, const gfx::IntRect* aRects, int aN) {
|
||||
mJihadRegionN = 0;
|
||||
for (int k = 0; k < aN && k < 12; k++) { mJihadRegionWorld[k] = aWorlds[k]; mJihadRegionRect[k] = aRects[k]; mJihadRegionN++; }
|
||||
}
|
||||
// the card's scroll alone moved (an async transform on the root scroll frame's content, which
|
||||
// the layer diff reports as the content's whole bounds): the diff is dropped, the bands asked
|
||||
// for are composed and read
|
||||
void JihadIgnoreDiff(bool aOn) { mJihadIgnoreDiff = aOn; }
|
||||
// T-505: a frame without its parts is read back in whole rows (no column read): a partial composite
|
||||
// of what the tree changed covers those rows across the frame's width, not the change's columns
|
||||
void JihadWholeRows(bool aOn) { mJihadWholeRows = aOn; }
|
||||
// round 115: the next diff treats the root-content layer's scroll as a shift (LayerTreeInvalidation.h)
|
||||
void JihadCompensateScroll(bool aOn) { mJihadCompensateScroll = aOn; }
|
||||
// round 119: the tree diff now, without a render (the stored tree refreshed as a composite
|
||||
// would), in frame pixels; the caller has begun a transaction and applied the async transforms
|
||||
nsIntRegion JihadDiffOnly();
|
||||
// T-505: the visible regions and effective transforms as a composite would make them, without the diff (the
|
||||
// stored tree untouched); the caller has begun a transaction and applied the async transforms
|
||||
void JihadPrepareOnly();
|
||||
void JihadEndTransactionNoComposite() { mInTransaction = false; }
|
||||
bool JihadPartial() const { return mJihadPartialOn; }
|
||||
|
||||
virtual void EndTransaction(DrawPaintedLayerCallback aCallback,
|
||||
void* aCallbackData,
|
||||
EndTransactionFlags aFlags = END_DEFAULT) override
|
||||
@@ -353,6 +393,17 @@ private:
|
||||
bool mUnusedApzTransformWarning;
|
||||
bool mDisabledApzWarning;
|
||||
RefPtr<Compositor> mCompositor;
|
||||
gfx::Matrix4x4 mJihadWorld; // Jihad T-502
|
||||
gfx::IntRect mJihadLastInvalid; // Jihad T-502
|
||||
bool mJihadHoldBaseline = false;
|
||||
gfx::IntRect mJihadLastChanged, mJihadBand, mJihadBand2, mJihadBand3; // Jihad T-502 (band 3: round 138's columns)
|
||||
gfx::Matrix4x4 mJihadRegionWorld[12]; // T-517: JihadSetRegions
|
||||
gfx::IntRect mJihadRegionRect[12];
|
||||
int mJihadRegionN = 0;
|
||||
bool mJihadIgnoreDiff = false;
|
||||
bool mJihadWholeRows = false; // T-505: the rows read back are whole rows
|
||||
bool mJihadCompensateScroll = false;
|
||||
bool mJihadPartialOn = false; // this Render is a partial composite
|
||||
UniquePtr<LayerProperties> mClonedLayerTreeProperties;
|
||||
|
||||
nsTArray<ImageCompositeNotification> mImageCompositeNotifications;
|
||||
|
||||
@@ -593,6 +593,25 @@ TiledContentHost::RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer,
|
||||
}
|
||||
|
||||
tileDrawRegion.ScaleRoundOut(resolution, resolution);
|
||||
{
|
||||
// Jihad (T-517): a tile wholly outside the clip is not locked, bound or given an effect only
|
||||
// for the compositor to drop its quad: a band frame's regions each met every tile of the page
|
||||
// (a Wikipedia pan frame: five regions, ~30 tiles). JIHAD_GPU_NO_TILE_CULL=1 for A/B
|
||||
static const bool sNoTileCull = getenv("JIHAD_GPU_NO_TILE_CULL") != nullptr;
|
||||
// The clip is the current render target's own coordinates, the transform's result is not:
|
||||
// the target's origin added, as the compositor's DrawQuad does (an intermediate surface, a
|
||||
// layer with opacity, starts away from 0,0)
|
||||
CompositingRenderTarget* target = mCompositor->GetCurrentRenderTarget();
|
||||
if (!sNoTileCull && target && aTransform.Is2D()) {
|
||||
const IntPoint origin = target->GetOrigin();
|
||||
const IntRect tb = tileDrawRegion.GetBounds();
|
||||
gfx::Rect onTarget = aTransform.TransformBounds(gfx::Rect(tb.x, tb.y, tb.width, tb.height));
|
||||
onTarget.Inflate(1.0f);
|
||||
if (!onTarget.Intersects(gfx::Rect(aClipRect.x + origin.x, aClipRect.y + origin.y, aClipRect.width, aClipRect.height))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderTile(tile, aEffectChain, aOpacity,
|
||||
aTransform, aSamplingFilter, aClipRect, tileDrawRegion,
|
||||
tileOffset * resolution, aLayerBuffer.GetTileSize(),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "CompositableTransactionParent.h"
|
||||
#include <atomic> // Jihad T-505
|
||||
#include "CompositableHost.h" // for CompositableParent, etc
|
||||
#include "CompositorBridgeParent.h" // for CompositorBridgeParent
|
||||
#include "GLContext.h" // for GLContext
|
||||
@@ -29,6 +30,9 @@
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
extern std::atomic<uint32_t> gJihadPartsGen; // Jihad T-505, CompositorBridgeParent.cpp
|
||||
void JihadReqNote(const char* aSrc); // Jihad T-505 diagnostic, CompositorBridgeParent.cpp
|
||||
|
||||
class ClientTiledLayerBuffer;
|
||||
class Compositor;
|
||||
|
||||
@@ -54,6 +58,7 @@ ScheduleComposition(CompositableHost* aCompositable)
|
||||
if (!cp) {
|
||||
return false;
|
||||
}
|
||||
JihadReqNote("a compositable's update");
|
||||
cp->ScheduleComposition();
|
||||
return true;
|
||||
}
|
||||
@@ -68,6 +73,11 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
|
||||
if (compositable->GetCompositor() && !compositable->GetCompositor()->IsValid()) {
|
||||
return true;
|
||||
}
|
||||
// Jihad T-505: an update to a layer under a fixed or sticky one moves the parts' generation (the daemon composes
|
||||
// the page's fixed and sticky parts alone again when it moves: they changed)
|
||||
for (Layer* l = compositable->GetLayer(); l; l = l->GetParent()) {
|
||||
if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
|
||||
}
|
||||
|
||||
switch (aEdit.detail().type()) {
|
||||
case CompositableOperationDetail::TOpPaintTextureRegion: {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -20,6 +20,7 @@
|
||||
#include "mozilla/Attributes.h" // for override
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Monitor.h" // for Monitor
|
||||
#include "mozilla/Atomics.h" // Jihad T-502
|
||||
#include "mozilla/RefPtr.h" // for RefPtr
|
||||
#include "mozilla/TimeStamp.h" // for TimeStamp
|
||||
#include "mozilla/dom/ipc/IdType.h"
|
||||
@@ -61,6 +62,7 @@ class APZCTreeManager;
|
||||
class APZCTreeManagerParent;
|
||||
class AsyncCompositionManager;
|
||||
class Compositor;
|
||||
struct JihadFrameRequest; // Jihad T-502, CompositorOGL.h
|
||||
class CompositorBridgeParent;
|
||||
class LayerManagerComposite;
|
||||
class LayerTransactionParent;
|
||||
@@ -108,6 +110,8 @@ public:
|
||||
bool NeedsComposite();
|
||||
void Composite(TimeStamp aVsyncTimestamp);
|
||||
void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect);
|
||||
// Jihad T-502 (pull mode): composites asked for and not run; the daemon pulls a frame when it moves.
|
||||
uint32_t JihadRequests() const { return mJihadRequests; }
|
||||
|
||||
const TimeStamp& GetLastComposeTime()
|
||||
{
|
||||
@@ -122,6 +126,7 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
mozilla::Atomic<uint32_t> mJihadRequests{0}; // Jihad T-502
|
||||
virtual ~CompositorVsyncScheduler();
|
||||
|
||||
void NotifyCompositeTaskExecuted();
|
||||
@@ -516,6 +521,40 @@ public:
|
||||
|
||||
void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr);
|
||||
|
||||
// Jihad T-502: the scheduler's request count (any thread), and the frame pulled on the
|
||||
// compositor thread: compose to the window (the pbuffer / renderbuffer) and read it into
|
||||
// aDest (B8G8R8A8, aStride bytes a row, the top-left aWidth x aHeight of the frame).
|
||||
uint32_t JihadRequests() const;
|
||||
// Reads back rows [aRowLo, aRowHi) (0,0 = none asked for) joined with the rows the composite
|
||||
// changed, which it reports in aInvLo/aInvHi (0,0 = nothing changed).
|
||||
// Composes into the free window target and hands the read of rows [aReq->lo, aReq->hi)
|
||||
// (0,0 = none asked for), joined with the rows the composite changed (left in lo/hi, the
|
||||
// change alone in invLo/invHi), to the read thread; aReq->done runs when the frame is in
|
||||
// aReq->dest. False: nothing was started.
|
||||
bool JihadComposeAndRead(JihadFrameRequest* aReq, float aScale, float aOffsetX, float aOffsetY);
|
||||
nsIntRegion JihadDiffNow(); // round 119: the tree diff without a render (frame pixels)
|
||||
gfx::Matrix4x4 mJihadLastWorld; // Jihad T-502: the view the last composite was made with
|
||||
float mJihadLastScrollX = 0.0f, mJihadLastScrollY = 0.0f;
|
||||
bool mJihadLastScrollValid = false;
|
||||
uint32_t mJihadSeqAtLast = 0; // the scheduler's request count when the last composite began
|
||||
int mJihadLastFixedLo = 0, mJihadLastFixedHi = 0; // the fixed rows as last computed on a whole composite
|
||||
// round 138: the fixed and stuck layers' rects on the frame, as last computed (at most four)
|
||||
gfx::IntRect mJihadLastFixedRects[4];
|
||||
int mJihadLastFixedN = 0;
|
||||
// round 141: per rect, the pixels an opaque layer covers (empty when not opaque), and the keeps
|
||||
// of the composite before the last (a keep must have held still)
|
||||
gfx::IntRect mJihadLastKeep[4], mJihadPrevKeep[4];
|
||||
int mJihadPrevKeepN = 0;
|
||||
void JihadHideForJob(); // Jihad T-504/T-505: the overview's and a content-only frame's layers hidden
|
||||
// Jihad T-505 (E2): the parts' signature (their layers, content, attributes and sizes); *aCovered when a content
|
||||
// layer is drawn above a part (the daemon cannot draw that part over the frame)
|
||||
uint32_t JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight);
|
||||
int JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect* aOut, gfx::IntRect* aKeep);
|
||||
void JihadUpdateLastFixed(int aWidth, int aHeight);
|
||||
bool mJihadSeqKnown = false;
|
||||
gfx::IntSize mJihadLastSize;
|
||||
bool mJihadWorldKnown = false;
|
||||
|
||||
PAPZCTreeManagerParent* AllocPAPZCTreeManagerParent(const uint64_t& aLayersId) override;
|
||||
bool DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor) override;
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* 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/. */
|
||||
|
||||
#include <sys/time.h> // Jihad T-517: the timeline's clock
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
namespace mozilla { namespace gl { extern std::atomic<uint32_t> gJihadUploads; extern std::atomic<uint64_t> gJihadUploadUs; } } // GLUploadHelpers.cpp
|
||||
#include "LayerTransactionParent.h"
|
||||
#include <vector> // for vector
|
||||
#include "apz/src/AsyncPanZoomController.h"
|
||||
@@ -265,6 +269,29 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
|
||||
PROFILER_LABEL("LayerTransactionParent", "RecvUpdate",
|
||||
js::ProfileEntry::Category::GRAPHICS);
|
||||
|
||||
// Jihad (T-517): with JIHAD_GPU_TIMELINE a transaction that holds the compositor thread 3 ms or
|
||||
// more is on the daemon's timeline (the express lane's frames queue behind it)
|
||||
struct JihadTxnTimer {
|
||||
mozilla::TimeStamp t0 = mozilla::TimeStamp::Now();
|
||||
size_t edits = 0;
|
||||
double compMs = 0, updatedMs = 0, endMs = 0; // the compositable ops, ShadowLayersUpdated, EndTransaction
|
||||
uint32_t up0 = gl::gJihadUploads;
|
||||
uint64_t upUs0 = gl::gJihadUploadUs;
|
||||
~JihadTxnTimer() {
|
||||
static const bool sOn = getenv("JIHAD_GPU_TIMELINE") != nullptr;
|
||||
if (!sOn) return;
|
||||
const double ms = (mozilla::TimeStamp::Now() - t0).ToMilliseconds();
|
||||
if (ms < 3.0) return;
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
fprintf(stderr, "[jihad-tl] t=%lld layer transaction on the compositor thread: %.1f ms, %zu edits "
|
||||
"(compositable ops %.1f, %u uploads %.1f; layers updated %.1f; end %.1f; the rest %.1f)\n",
|
||||
(long long)tv.tv_sec * 1000 + tv.tv_usec / 1000, ms, edits, compMs, gl::gJihadUploads - up0,
|
||||
(gl::gJihadUploadUs - upUs0) / 1000.0, updatedMs, endMs, ms - compMs - updatedMs - endMs);
|
||||
}
|
||||
} jihadTxnTimer;
|
||||
jihadTxnTimer.edits = cset.Length();
|
||||
|
||||
#ifdef COMPOSITOR_PERFORMANCE_WARNING
|
||||
TimeStamp updateStart = TimeStamp::Now();
|
||||
#endif
|
||||
@@ -625,10 +652,12 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
|
||||
break;
|
||||
}
|
||||
case Edit::TCompositableOperation: {
|
||||
const TimeStamp jihadC0 = TimeStamp::Now();
|
||||
if (!ReceiveCompositableUpdate(edit.get_CompositableOperation(),
|
||||
replyv)) {
|
||||
return false;
|
||||
}
|
||||
jihadTxnTimer.compMs += (TimeStamp::Now() - jihadC0).ToMilliseconds();
|
||||
break;
|
||||
}
|
||||
case Edit::TOpAttachCompositable: {
|
||||
@@ -669,15 +698,19 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
|
||||
}
|
||||
}
|
||||
|
||||
const TimeStamp jihadU0 = TimeStamp::Now();
|
||||
mCompositorBridge->ShadowLayersUpdated(this, aTransactionId, targetConfig,
|
||||
aPlugins, isFirstPaint, scheduleComposite,
|
||||
paintSequenceNumber, isRepeatTransaction,
|
||||
aPaintSyncId, updateHitTestingTree);
|
||||
const TimeStamp jihadU1 = TimeStamp::Now();
|
||||
jihadTxnTimer.updatedMs = (jihadU1 - jihadU0).ToMilliseconds();
|
||||
|
||||
{
|
||||
AutoResolveRefLayers resolve(mCompositorBridge->GetCompositionManager(this));
|
||||
layer_manager()->EndTransaction(TimeStamp(), LayerManager::END_NO_IMMEDIATE_REDRAW);
|
||||
}
|
||||
jihadTxnTimer.endMs = (TimeStamp::Now() - jihadU1).ToMilliseconds();
|
||||
|
||||
if (reply) {
|
||||
reply->SetCapacity(replyv.size());
|
||||
|
||||
@@ -19,7 +19,12 @@ CompositingRenderTargetOGL::~CompositingRenderTargetOGL()
|
||||
{
|
||||
if (mGL && mGL->MakeCurrent()) {
|
||||
mGL->fDeleteTextures(1, &mTextureHandle);
|
||||
mGL->fDeleteFramebuffers(1, &mFBO);
|
||||
// Jihad T-502: the window target made for each frame borrows the compositor's renderbuffer
|
||||
// FBO; deleting it here (harmless for FBO 0) killed the target after the first frame and
|
||||
// every later composite drew into nothing (black frames, rounds 6-16).
|
||||
if (!mJihadBorrowedFBO) {
|
||||
mGL->fDeleteFramebuffers(1, &mFBO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,10 +97,12 @@ public:
|
||||
*/
|
||||
static already_AddRefed<CompositingRenderTargetOGL>
|
||||
RenderTargetForWindow(CompositorOGL* aCompositor,
|
||||
const gfx::IntSize& aSize)
|
||||
const gfx::IntSize& aSize,
|
||||
GLuint aFBO = 0) // Jihad T-502: the renderbuffer FBO that stands for the window
|
||||
{
|
||||
RefPtr<CompositingRenderTargetOGL> result
|
||||
= new CompositingRenderTargetOGL(aCompositor, gfx::IntPoint(), 0, 0);
|
||||
= new CompositingRenderTargetOGL(aCompositor, gfx::IntPoint(), 0, aFBO);
|
||||
result->mJihadBorrowedFBO = (aFBO != 0); // the compositor's, not this target's to delete
|
||||
result->mInitParams = InitParams(aSize, aSize, 0, INIT_MODE_NONE);
|
||||
result->mInitParams.mStatus = InitParams::INITIALIZED;
|
||||
return result.forget();
|
||||
@@ -129,7 +131,9 @@ public:
|
||||
*/
|
||||
void BindRenderTarget();
|
||||
|
||||
bool IsWindow() { return GetFBO() == 0; }
|
||||
// Jihad T-502: the compositor's renderbuffer FBO stands for the window, with the window's
|
||||
// conventions (render offset, no y-flip of intermediate surfaces drawn back into it).
|
||||
bool IsWindow() { return GetFBO() == 0 || mJihadBorrowedFBO; }
|
||||
|
||||
GLuint GetFBO() const
|
||||
{
|
||||
@@ -187,6 +191,7 @@ private:
|
||||
RefPtr<GLContext> mGL;
|
||||
GLuint mTextureHandle;
|
||||
GLuint mFBO;
|
||||
bool mJihadBorrowedFBO = false; // Jihad T-502: a window FBO the compositor owns
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 263 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user