Compare commits
660
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adfaa7ff30 | ||
|
|
52ad9ab66e | ||
|
|
18775d0b8c | ||
|
|
572d6dcbfe | ||
|
|
e9d31c9919 | ||
|
|
bb9e5a712d | ||
|
|
3891e46584 | ||
|
|
a0d9757ff4 | ||
|
|
bfa288b78e | ||
|
|
41a3183d2c | ||
|
|
806f342676 | ||
|
|
6738f46f82 | ||
|
|
dd391a4b2a | ||
|
|
861ef0dfe7 | ||
|
|
c3ea919c89 | ||
|
|
02233abfcd | ||
|
|
ea0652b2ab | ||
|
|
624e502302 | ||
|
|
1575cf5732 | ||
|
|
7d0d7544cd | ||
|
|
81ac6f1fa7 | ||
|
|
3f37a6037a | ||
|
|
746573c9b4 | ||
|
|
23fdf006fa | ||
|
|
9637eb160d | ||
|
|
796a0c295b | ||
|
|
f676e50ea4 | ||
|
|
3b3430dd03 | ||
|
|
5a2313209d | ||
|
|
a8fd3994aa | ||
|
|
4ea10659e1 | ||
|
|
07bde00e35 | ||
|
|
028fa62d93 | ||
|
|
75ccf689ef | ||
|
|
225a1dc56d | ||
|
|
4aeadf6074 | ||
|
|
b2eee4371e | ||
|
|
3c4ae927bd | ||
|
|
50fd5c51a5 | ||
|
|
677d221de7 | ||
|
|
d1e51db6f4 | ||
|
|
4f21aa207d | ||
|
|
ebd7c20cb8 | ||
|
|
1a320c2822 | ||
|
|
b584f8f5ce | ||
|
|
48e5976b68 | ||
|
|
741962a225 | ||
|
|
03f0150408 | ||
|
|
20dba4a992 | ||
|
|
a7612a322e | ||
|
|
1b8430d5fc | ||
|
|
e4c5e03f09 | ||
|
|
fff77f0e79 | ||
|
|
83e7caff0f | ||
|
|
d0107d40dc | ||
|
|
50530fb7a7 | ||
|
|
e599a64797 | ||
|
|
5527809cc2 | ||
|
|
4fa666843f | ||
|
|
2600203649 | ||
|
|
ecce652ca9 | ||
|
|
ff28d1f764 | ||
|
|
9dcc5ecaf4 | ||
|
|
f396363bf4 | ||
|
|
99bce36d46 | ||
|
|
5c23f4cdd1 | ||
|
|
631e1c9513 | ||
|
|
97e18c8f69 | ||
|
|
33898c6e66 | ||
|
|
70cf00474c | ||
|
|
7cd48e47f9 | ||
|
|
44371932fc | ||
|
|
04fc08d37d | ||
|
|
6b18be917d | ||
|
|
037cc5f614 | ||
|
|
4a68bbbd53 | ||
|
|
5762097bc2 | ||
|
|
e86caa48f5 | ||
|
|
612a8991df | ||
|
|
5c991eadc0 | ||
|
|
38e09d2368 | ||
|
|
8fea2ad823 | ||
|
|
c4339b14dc | ||
|
|
bae7a6ec8f | ||
|
|
d42616bb12 | ||
|
|
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 /,_,$*)
|
||||
|
||||
|
||||
+17230
-8908
File diff suppressed because it is too large.
Load diff
+969
-295
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
|
||||
@@ -921,7 +921,7 @@ private:
|
||||
// List of nodes referred to by mEntryTable so we can sort them
|
||||
// For a specific pseudo element, we use its parent element as the
|
||||
// batch target, so they will be put in the same EntryArray.
|
||||
nsTArray<nsINode*> mBatchTargets;
|
||||
nsTArray<nsCOMPtr<nsINode>> mBatchTargets;
|
||||
};
|
||||
|
||||
inline
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1554,8 +1554,7 @@ FilterNodeTileSoftware::SetAttribute(uint32_t aIndex,
|
||||
const IntRect &aSourceRect)
|
||||
{
|
||||
MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT);
|
||||
mSourceRect = IntRect(int32_t(aSourceRect.x), int32_t(aSourceRect.y),
|
||||
int32_t(aSourceRect.width), int32_t(aSourceRect.height));
|
||||
mSourceRect = aSourceRect;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
@@ -1595,14 +1594,24 @@ FilterNodeTileSoftware::Render(const IntRect& aRect)
|
||||
typedef std::map<IntRect, RefPtr<DataSourceSurface>, CompareIntRects> InputMap;
|
||||
InputMap inputs;
|
||||
|
||||
auto IntRectToInt64Rect = [](const IntRect& aRect) {
|
||||
return RectTyped<UnknownUnits, int64_t>(aRect.X(), aRect.Y(), aRect.Width(),
|
||||
aRect.Height());
|
||||
};
|
||||
|
||||
IntPoint startIndex = TileIndex(mSourceRect, aRect.TopLeft());
|
||||
IntPoint endIndex = TileIndex(mSourceRect, aRect.BottomRight());
|
||||
for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) {
|
||||
for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) {
|
||||
IntPoint sourceToDestOffset(ix * mSourceRect.width,
|
||||
iy * mSourceRect.height);
|
||||
IntRect destRect = aRect.Intersect(mSourceRect + sourceToDestOffset);
|
||||
IntRect srcRect = destRect - sourceToDestOffset;
|
||||
auto sourceToDestOffset = PointTyped<UnknownUnits, int64_t>(
|
||||
ix * mSourceRect.Width(), iy * mSourceRect.Height());
|
||||
auto destRect64 = IntRectToInt64Rect(aRect).Intersect(
|
||||
IntRectToInt64Rect(mSourceRect) + sourceToDestOffset);
|
||||
auto srcRect64 = destRect64 - sourceToDestOffset;
|
||||
IntRect destRect, srcRect;
|
||||
if (!destRect64.ToIntRect(&destRect) || !srcRect64.ToIntRect(&srcRect)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (srcRect.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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,10 @@
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
|
||||
extern std::atomic<uint32_t> gJihadPartsGen; // Jihad T-505, CompositorBridgeParent.cpp
|
||||
void JihadNotePartPaint(Layer* aLayer); // Jihad T-505 (per part): that layer painted (its paint stamp)
|
||||
void JihadReqNote(const char* aSrc); // Jihad T-505 diagnostic, CompositorBridgeParent.cpp
|
||||
|
||||
class ClientTiledLayerBuffer;
|
||||
class Compositor;
|
||||
|
||||
@@ -54,6 +59,7 @@ ScheduleComposition(CompositableHost* aCompositable)
|
||||
if (!cp) {
|
||||
return false;
|
||||
}
|
||||
JihadReqNote("a compositable's update");
|
||||
cp->ScheduleComposition();
|
||||
return true;
|
||||
}
|
||||
@@ -68,6 +74,16 @@ 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)
|
||||
{
|
||||
// (per part, review F2) every layer's paint is stamped, part or not: a layer that becomes a part later carries it
|
||||
Layer* painted = compositable->GetLayer();
|
||||
if (painted) JihadNotePartPaint(painted);
|
||||
for (Layer* l = painted; 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,41 @@ 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), and each rect's own
|
||||
// (aRectSig, aN of them: per part); *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, uint32_t* aRectSig);
|
||||
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;
|
||||
|
||||
|
||||
Loaded 100 of 273 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user