Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: test/elemHide.js

Issue 29587914: Issue 5142 - Convert Element Hiding to C++ (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Left Patch Set: Fixed leaks in test. Extracted changes to string and string map. Created Jan. 22, 2018, 4:48 p.m.
Right Patch Set: mFiltersByDomain is now an OwnedStringMap Created Jan. 26, 2018, 8:41 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « meson.build ('k') | test/elemHideEmulation.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-present eyeo GmbH 3 * Copyright (C) 2006-present eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 const {createSandbox} = require("./_common"); 20 const {createSandbox} = require("./_common");
21 const {withNAD} = require("./_test-utils");
21 22
22 let ElemHide = null; 23 let ElemHide = null;
23 let Filter = null; 24 let Filter = null;
24 25
25 exports.setUp = function(callback) 26 exports.setUp = function(callback)
26 { 27 {
27 let sandboxedRequire = createSandbox(); 28 let sandboxedRequire = createSandbox();
28 ( 29 (
29 {ElemHide} = sandboxedRequire("../lib/elemHide"), 30 {ElemHide} = sandboxedRequire("../lib/elemHide"),
30 {Filter} = sandboxedRequire("../lib/filterClasses") 31 {Filter} = sandboxedRequire("../lib/filterClasses")
(...skipping 28 matching lines...) Expand all
59 let normalizedExpectedSelectors = normalizeSelectors(expectedSelectors); 60 let normalizedExpectedSelectors = normalizeSelectors(expectedSelectors);
60 61
61 test.deepEqual( 62 test.deepEqual(
62 normalizeSelectors(elemHide.getSelectorsForDomain(domain, criteria)), 63 normalizeSelectors(elemHide.getSelectorsForDomain(domain, criteria)),
63 normalizedExpectedSelectors 64 normalizedExpectedSelectors
64 ); 65 );
65 } 66 }
66 67
67 exports.testGetSelectorsForDomain = function(test) 68 exports.testGetSelectorsForDomain = function(test)
68 { 69 {
69 let elemHide = ElemHide.create(); 70 withNAD(0, elemHide =>
70 71 {
71 let addFilter = filterText => 72 let addFilter = filterText => withNAD(
72 { 73 0, filter => elemHide.add(filter))(Filter.fromText(filterText));
73 let filter = Filter.fromText(filterText); 74 let removeFilter = filterText => withNAD(
74 elemHide.add(filter); 75 0, filter => elemHide.remove(filter))(Filter.fromText(filterText));
75 filter.delete(); 76
76 }; 77 testResult(test, elemHide, "", []);
77 let removeFilter = filterText => 78
78 { 79 addFilter("~foo.example.com,example.com##foo");
79 let filter = Filter.fromText(filterText); 80 testResult(test, elemHide, "barfoo.example.com", ["foo"]);
80 elemHide.remove(filter); 81 testResult(test, elemHide, "bar.foo.example.com", []);
81 filter.delete(); 82 testResult(test, elemHide, "foo.example.com", []);
82 }; 83 testResult(test, elemHide, "example.com", ["foo"]);
83 84 testResult(test, elemHide, "com", []);
84 testResult(test, elemHide, "", []); 85 testResult(test, elemHide, "", []);
85 86
86 addFilter("~foo.example.com,example.com##foo"); 87 addFilter("foo.example.com##turnip");
87 testResult(test, elemHide, "barfoo.example.com", ["foo"]); 88 testResult(test, elemHide, "foo.example.com", ["turnip"]);
88 testResult(test, elemHide, "bar.foo.example.com", []); 89 testResult(test, elemHide, "example.com", ["foo"]);
89 testResult(test, elemHide, "foo.example.com", []); 90 testResult(test, elemHide, "com", []);
90 testResult(test, elemHide, "example.com", ["foo"]); 91 testResult(test, elemHide, "", []);
91 testResult(test, elemHide, "com", []); 92
92 testResult(test, elemHide, "", []); 93 addFilter("example.com#@#foo");
93 94 testResult(test, elemHide, "foo.example.com", ["turnip"]);
94 addFilter("foo.example.com##turnip"); 95 testResult(test, elemHide, "example.com", []);
95 testResult(test, elemHide, "foo.example.com", ["turnip"]); 96 testResult(test, elemHide, "com", []);
96 testResult(test, elemHide, "example.com", ["foo"]); 97 testResult(test, elemHide, "", []);
97 testResult(test, elemHide, "com", []); 98
98 testResult(test, elemHide, "", []); 99 addFilter("com##bar");
99 100 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]);
100 addFilter("example.com#@#foo"); 101 testResult(test, elemHide, "example.com", ["bar"]);
101 testResult(test, elemHide, "foo.example.com", ["turnip"]); 102 testResult(test, elemHide, "com", ["bar"]);
102 testResult(test, elemHide, "example.com", []); 103 testResult(test, elemHide, "", []);
103 testResult(test, elemHide, "com", []); 104
104 testResult(test, elemHide, "", []); 105 addFilter("example.com#@#bar");
105 106 testResult(test, elemHide, "foo.example.com", ["turnip"]);
106 addFilter("com##bar"); 107 testResult(test, elemHide, "example.com", []);
107 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]); 108 testResult(test, elemHide, "com", ["bar"]);
108 testResult(test, elemHide, "example.com", ["bar"]); 109 testResult(test, elemHide, "", []);
109 testResult(test, elemHide, "com", ["bar"]); 110
110 testResult(test, elemHide, "", []); 111 removeFilter("example.com#@#foo");
111 112 testResult(test, elemHide, "foo.example.com", ["turnip"]);
112 addFilter("example.com#@#bar"); 113 testResult(test, elemHide, "example.com", ["foo"]);
113 testResult(test, elemHide, "foo.example.com", ["turnip"]); 114 testResult(test, elemHide, "com", ["bar"]);
114 testResult(test, elemHide, "example.com", []); 115 testResult(test, elemHide, "", []);
115 testResult(test, elemHide, "com", ["bar"]); 116
116 testResult(test, elemHide, "", []); 117 removeFilter("example.com#@#bar");
117 118 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]);
118 removeFilter("example.com#@#foo"); 119 testResult(test, elemHide, "example.com", ["foo", "bar"]);
119 testResult(test, elemHide, "foo.example.com", ["turnip"]); 120 testResult(test, elemHide, "com", ["bar"]);
120 testResult(test, elemHide, "example.com", ["foo"]); 121 testResult(test, elemHide, "", []);
121 testResult(test, elemHide, "com", ["bar"]); 122
122 testResult(test, elemHide, "", []); 123 addFilter("##generic");
123 124 testResult(test, elemHide, "foo.example.com", ["turnip", "bar", "generic"]);
124 removeFilter("example.com#@#bar"); 125 testResult(test, elemHide, "example.com", ["foo", "bar", "generic"]);
125 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]); 126 testResult(test, elemHide, "com", ["bar", "generic"]);
126 testResult(test, elemHide, "example.com", ["foo", "bar"]); 127 testResult(test, elemHide, "", ["generic"]);
127 testResult(test, elemHide, "com", ["bar"]); 128 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"], ElemHide.SP ECIFIC_ONLY);
128 testResult(test, elemHide, "", []); 129 testResult(test, elemHide, "example.com", ["foo", "bar"], ElemHide.SPECIFIC_ ONLY);
129 130 testResult(test, elemHide, "com", ["bar"], ElemHide.SPECIFIC_ONLY);
130 addFilter("##generic"); 131 testResult(test, elemHide, "", [], ElemHide.SPECIFIC_ONLY);
131 testResult(test, elemHide, "foo.example.com", ["turnip", "bar", "generic"]); 132 removeFilter("##generic");
132 testResult(test, elemHide, "example.com", ["foo", "bar", "generic"]); 133
133 testResult(test, elemHide, "com", ["bar", "generic"]); 134 addFilter("~adblockplus.org##example");
134 testResult(test, elemHide, "", ["generic"]); 135 testResult(test, elemHide, "adblockplus.org", []);
135 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"], ElemHide.SPEC IFIC_ONLY); 136 testResult(test, elemHide, "", ["example"]);
136 testResult(test, elemHide, "example.com", ["foo", "bar"], ElemHide.SPECIFIC_ON LY); 137 testResult(test, elemHide, "foo.example.com", ["turnip", "bar", "example"]);
137 testResult(test, elemHide, "com", ["bar"], ElemHide.SPECIFIC_ONLY); 138 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"], ElemHide.SP ECIFIC_ONLY);
138 testResult(test, elemHide, "", [], ElemHide.SPECIFIC_ONLY); 139 removeFilter("~adblockplus.org##example");
139 removeFilter("##generic"); 140
140 141 removeFilter("~foo.example.com,example.com##foo");
141 addFilter("~adblockplus.org##example"); 142 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]);
142 testResult(test, elemHide, "adblockplus.org", []); 143 testResult(test, elemHide, "example.com", ["bar"]);
143 testResult(test, elemHide, "", ["example"]); 144 testResult(test, elemHide, "com", ["bar"]);
144 testResult(test, elemHide, "foo.example.com", ["turnip", "bar", "example"]); 145 testResult(test, elemHide, "", []);
145 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"], ElemHide.SPEC IFIC_ONLY); 146
146 removeFilter("~adblockplus.org##example"); 147 removeFilter("com##bar");
147 148 testResult(test, elemHide, "foo.example.com", ["turnip"]);
148 removeFilter("~foo.example.com,example.com##foo"); 149 testResult(test, elemHide, "example.com", []);
149 testResult(test, elemHide, "foo.example.com", ["turnip", "bar"]); 150 testResult(test, elemHide, "com", []);
150 testResult(test, elemHide, "example.com", ["bar"]); 151 testResult(test, elemHide, "", []);
151 testResult(test, elemHide, "com", ["bar"]); 152
152 testResult(test, elemHide, "", []); 153 removeFilter("foo.example.com##turnip");
153 154 testResult(test, elemHide, "foo.example.com", []);
154 removeFilter("com##bar"); 155 testResult(test, elemHide, "example.com", []);
155 testResult(test, elemHide, "foo.example.com", ["turnip"]); 156 testResult(test, elemHide, "com", []);
156 testResult(test, elemHide, "example.com", []); 157 testResult(test, elemHide, "", []);
157 testResult(test, elemHide, "com", []); 158
158 testResult(test, elemHide, "", []); 159 addFilter("example.com##dupe");
159 160 addFilter("example.com##dupe");
160 removeFilter("foo.example.com##turnip"); 161 testResult(test, elemHide, "example.com", ["dupe"]);
161 testResult(test, elemHide, "foo.example.com", []); 162 removeFilter("example.com##dupe");
162 testResult(test, elemHide, "example.com", []); 163 testResult(test, elemHide, "example.com", []);
163 testResult(test, elemHide, "com", []); 164 removeFilter("example.com##dupe");
164 testResult(test, elemHide, "", []); 165
165 166 addFilter("~foo.example.com,example.com##foo");
166 addFilter("example.com##dupe"); 167
167 addFilter("example.com##dupe"); 168 addFilter("##foo");
168 testResult(test, elemHide, "example.com", ["dupe"]); 169 testResult(test, elemHide, "foo.example.com", ["foo"]);
169 removeFilter("example.com##dupe"); 170 testResult(test, elemHide, "example.com", ["foo"]);
170 testResult(test, elemHide, "example.com", []); 171 testResult(test, elemHide, "com", ["foo"]);
171 removeFilter("example.com##dupe"); 172 testResult(test, elemHide, "", ["foo"]);
172 173 removeFilter("##foo");
173 addFilter("~foo.example.com,example.com##foo"); 174
174 175 addFilter("example.org##foo");
175 addFilter("##foo"); 176 testResult(test, elemHide, "foo.example.com", []);
176 testResult(test, elemHide, "foo.example.com", ["foo"]); 177 testResult(test, elemHide, "example.com", ["foo"]);
177 testResult(test, elemHide, "example.com", ["foo"]); 178 testResult(test, elemHide, "com", []);
178 testResult(test, elemHide, "com", ["foo"]); 179 testResult(test, elemHide, "", []);
179 testResult(test, elemHide, "", ["foo"]); 180 removeFilter("example.org##foo");
180 removeFilter("##foo"); 181
181 182 addFilter("~example.com##foo");
182 addFilter("example.org##foo"); 183 testResult(test, elemHide, "foo.example.com", []);
183 testResult(test, elemHide, "foo.example.com", []); 184 testResult(test, elemHide, "example.com", ["foo"]);
184 testResult(test, elemHide, "example.com", ["foo"]); 185 testResult(test, elemHide, "com", ["foo"]);
185 testResult(test, elemHide, "com", []); 186 testResult(test, elemHide, "", ["foo"]);
186 testResult(test, elemHide, "", []); 187 removeFilter("~example.com##foo");
187 removeFilter("example.org##foo"); 188
188 189 removeFilter("~foo.example.com,example.com##foo");
189 addFilter("~example.com##foo"); 190
190 testResult(test, elemHide, "foo.example.com", []); 191 // Test criteria
191 testResult(test, elemHide, "example.com", ["foo"]); 192 addFilter("##hello");
192 testResult(test, elemHide, "com", ["foo"]); 193 addFilter("~example.com##world");
193 testResult(test, elemHide, "", ["foo"]); 194 addFilter("foo.com##specific");
194 removeFilter("~example.com##foo"); 195 testResult(test, elemHide, "foo.com", ["specific"], ElemHide.SPECIFIC_ONLY);
195 196 testResult(test, elemHide, "foo.com", ["specific", "world"], ElemHide.NO_UNC ONDITIONAL);
196 removeFilter("~foo.example.com,example.com##foo"); 197 testResult(test, elemHide, "foo.com", ["hello", "specific", "world"], ElemHi de.ALL_MATCHING);
197 198 testResult(test, elemHide, "foo.com", ["hello", "specific", "world"]);
198 // Test criteria 199 removeFilter("foo.com##specific");
199 addFilter("##hello"); 200 removeFilter("~example.com##world");
200 addFilter("~example.com##world"); 201 removeFilter("##hello");
201 addFilter("foo.com##specific"); 202 testResult(test, elemHide, "foo.com", []);
202 testResult(test, elemHide, "foo.com", ["specific"], ElemHide.SPECIFIC_ONLY); 203
203 testResult(test, elemHide, "foo.com", ["specific", "world"], ElemHide.NO_UNCON DITIONAL); 204 addFilter("##hello");
204 testResult(test, elemHide, "foo.com", ["hello", "specific", "world"], ElemHide .ALL_MATCHING); 205 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY);
205 testResult(test, elemHide, "foo.com", ["hello", "specific", "world"]); 206 testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL);
206 removeFilter("foo.com##specific"); 207 testResult(test, elemHide, "foo.com", ["hello"], ElemHide.ALL_MATCHING);
207 removeFilter("~example.com##world"); 208 testResult(test, elemHide, "foo.com", ["hello"]);
208 removeFilter("##hello"); 209 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY);
209 testResult(test, elemHide, "foo.com", []); 210 testResult(test, elemHide, "bar.com", [], ElemHide.NO_UNCONDITIONAL);
210 211 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING);
211 addFilter("##hello"); 212 testResult(test, elemHide, "bar.com", ["hello"]);
212 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY); 213 addFilter("foo.com#@#hello");
213 testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL); 214 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY);
214 testResult(test, elemHide, "foo.com", ["hello"], ElemHide.ALL_MATCHING); 215 testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL);
215 testResult(test, elemHide, "foo.com", ["hello"]); 216 testResult(test, elemHide, "foo.com", [], ElemHide.ALL_MATCHING);
216 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY); 217 testResult(test, elemHide, "foo.com", []);
217 testResult(test, elemHide, "bar.com", [], ElemHide.NO_UNCONDITIONAL); 218 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY);
218 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING); 219 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.NO_UNCONDITIONAL);
219 testResult(test, elemHide, "bar.com", ["hello"]); 220 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING);
220 addFilter("foo.com#@#hello"); 221 testResult(test, elemHide, "bar.com", ["hello"]);
221 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY); 222 removeFilter("foo.com#@#hello");
222 testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL); 223 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY);
223 testResult(test, elemHide, "foo.com", [], ElemHide.ALL_MATCHING); 224 // Note: We don't take care to track conditional selectors which became
224 testResult(test, elemHide, "foo.com", []); 225 // unconditional when a filter was removed. This was too expensive.
225 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY); 226 // testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL);
226 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.NO_UNCONDITIONAL); 227 testResult(test, elemHide, "foo.com", ["hello"], ElemHide.ALL_MATCHING);
227 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING); 228 testResult(test, elemHide, "foo.com", ["hello"]);
228 testResult(test, elemHide, "bar.com", ["hello"]); 229 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY);
229 removeFilter("foo.com#@#hello"); 230 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.NO_UNCONDITIONAL);
230 testResult(test, elemHide, "foo.com", [], ElemHide.SPECIFIC_ONLY); 231 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING);
231 // Note: We don't take care to track conditional selectors which became 232 testResult(test, elemHide, "bar.com", ["hello"]);
232 // unconditional when a filter was removed. This was too expensive. 233 removeFilter("##hello");
233 // testResult(test, elemHide, "foo.com", [], ElemHide.NO_UNCONDITIONAL); 234 testResult(test, elemHide, "foo.com", []);
234 testResult(test, elemHide, "foo.com", ["hello"], ElemHide.ALL_MATCHING); 235 testResult(test, elemHide, "bar.com", []);
235 testResult(test, elemHide, "foo.com", ["hello"]); 236
236 testResult(test, elemHide, "bar.com", [], ElemHide.SPECIFIC_ONLY); 237 addFilter("##hello");
237 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.NO_UNCONDITIONAL); 238 addFilter("foo.com##hello");
238 testResult(test, elemHide, "bar.com", ["hello"], ElemHide.ALL_MATCHING); 239 testResult(test, elemHide, "foo.com", ["hello"]);
239 testResult(test, elemHide, "bar.com", ["hello"]); 240 removeFilter("foo.com##hello");
240 removeFilter("##hello"); 241 testResult(test, elemHide, "foo.com", ["hello"]);
241 testResult(test, elemHide, "foo.com", []); 242 removeFilter("##hello");
242 testResult(test, elemHide, "bar.com", []); 243 testResult(test, elemHide, "foo.com", []);
243 244
244 addFilter("##hello"); 245 addFilter("##hello");
245 addFilter("foo.com##hello"); 246 addFilter("foo.com##hello");
246 testResult(test, elemHide, "foo.com", ["hello"]); 247 testResult(test, elemHide, "foo.com", ["hello"]);
247 removeFilter("foo.com##hello"); 248 removeFilter("##hello");
248 testResult(test, elemHide, "foo.com", ["hello"]); 249 testResult(test, elemHide, "foo.com", ["hello"]);
249 removeFilter("##hello"); 250 removeFilter("foo.com##hello");
250 testResult(test, elemHide, "foo.com", []); 251 testResult(test, elemHide, "foo.com", []);
251 252 })(ElemHide.create());
252 addFilter("##hello"); 253
253 addFilter("foo.com##hello");
254 testResult(test, elemHide, "foo.com", ["hello"]);
255 removeFilter("##hello");
256 testResult(test, elemHide, "foo.com", ["hello"]);
257 removeFilter("foo.com##hello");
258 testResult(test, elemHide, "foo.com", []);
259
260 elemHide.delete();
261 test.done(); 254 test.done();
262 }; 255 };
263 256
264 exports.testZeroFilterKey = function(test) 257 exports.testFilterException = function(test)
265 { 258 {
266 let elemHide = ElemHide.create(); 259 withNAD([0, 1, 2], (elemHide, filter, exception) =>
267 260 {
268 let filter1 = Filter.fromText("##test"); 261 elemHide.add(filter);
269 let filter2 = Filter.fromText("foo.com#@#test"); 262 elemHide.add(exception);
270 elemHide.add(filter1); 263 testResult(test, elemHide, "foo.com", []);
271 elemHide.add(filter2); 264 testResult(test, elemHide, "bar.com", ["test"]);
272 testResult(test, elemHide, "foo.com", []); 265 })(ElemHide.create(), Filter.fromText("##test"), Filter.fromText("foo.com#@#te st"));
273 testResult(test, elemHide, "bar.com", ["test"]); 266
274
275 filter2.delete();
276 filter1.delete();
277 elemHide.delete();
278 test.done(); 267 test.done();
279 }; 268 };
LEFTRIGHT

Powered by Google App Engine
This is Rietveld