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

Side by Side Diff: include.preload.js

Issue 29452181: Noissue - Merge current tip to Edge bookmark (Closed)
Patch Set: Created May 30, 2017, 3:49 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « icons/detailed/abp-48.png ('k') | inject.preload.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2017 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 /* globals ElemHideEmulation, splitSelector */
19
18 "use strict"; 20 "use strict";
19 21
20 const typeMap = { 22 // This variable is also used by our other content scripts.
21 "img": "IMAGE", 23 let elemhide;
22 "input": "IMAGE", 24
23 "picture": "IMAGE", 25 const typeMap = new Map([
24 "audio": "MEDIA", 26 ["img", "IMAGE"],
25 "video": "MEDIA", 27 ["input", "IMAGE"],
26 "frame": "SUBDOCUMENT", 28 ["picture", "IMAGE"],
27 "iframe": "SUBDOCUMENT", 29 ["audio", "MEDIA"],
28 "object": "OBJECT", 30 ["video", "MEDIA"],
29 "embed": "OBJECT" 31 ["frame", "SUBDOCUMENT"],
30 }; 32 ["iframe", "SUBDOCUMENT"],
33 ["object", "OBJECT"],
34 ["embed", "OBJECT"]
35 ]);
31 36
32 function getURLsFromObjectElement(element) 37 function getURLsFromObjectElement(element)
33 { 38 {
34 let url = element.getAttribute("data"); 39 let url = element.getAttribute("data");
35 if (url) 40 if (url)
36 return [url]; 41 return [url];
37 42
38 for (let child of element.children) 43 for (let child of element.children)
39 { 44 {
40 if (child.localName != "param") 45 if (child.localName != "param")
41 continue; 46 continue;
42 47
43 let name = child.getAttribute("name"); 48 let name = child.getAttribute("name");
44 if (name != "movie" && // Adobe Flash 49 if (name != "movie" && // Adobe Flash
45 name != "source" && // Silverlight 50 name != "source" && // Silverlight
46 name != "src" && // Real Media + Quicktime 51 name != "src" && // Real Media + Quicktime
47 name != "FileName") // Windows Media 52 name != "FileName") // Windows Media
48 continue; 53 continue;
49 54
50 let value = child.getAttribute("value"); 55 let value = child.getAttribute("value");
51 if (!value) 56 if (!value)
52 continue; 57 continue;
53 58
54 return [value]; 59 return [value];
55 } 60 }
56 61
(...skipping 20 matching lines...) Expand all
77 return urls; 82 return urls;
78 } 83 }
79 84
80 function getURLsFromMediaElement(element) 85 function getURLsFromMediaElement(element)
81 { 86 {
82 let urls = getURLsFromAttributes(element); 87 let urls = getURLsFromAttributes(element);
83 88
84 for (let child of element.children) 89 for (let child of element.children)
85 { 90 {
86 if (child.localName == "source" || child.localName == "track") 91 if (child.localName == "source" || child.localName == "track")
87 urls.push.apply(urls, getURLsFromAttributes(child)); 92 urls.push(...getURLsFromAttributes(child));
88 } 93 }
89 94
90 if (element.poster) 95 if (element.poster)
91 urls.push(element.poster); 96 urls.push(element.poster);
92 97
93 return urls; 98 return urls;
94 } 99 }
95 100
96 function getURLsFromElement(element) 101 function getURLsFromElement(element)
97 { 102 {
(...skipping 19 matching lines...) Expand all
117 { 122 {
118 if (/^(?!https?:)[\w-]+:/i.test(urls[i])) 123 if (/^(?!https?:)[\w-]+:/i.test(urls[i]))
119 urls.splice(i--, 1); 124 urls.splice(i--, 1);
120 } 125 }
121 126
122 return urls; 127 return urls;
123 } 128 }
124 129
125 function checkCollapse(element) 130 function checkCollapse(element)
126 { 131 {
127 let mediatype = typeMap[element.localName]; 132 let mediatype = typeMap.get(element.localName);
128 if (!mediatype) 133 if (!mediatype)
129 return; 134 return;
130 135
131 let urls = getURLsFromElement(element); 136 let urls = getURLsFromElement(element);
132 if (urls.length == 0) 137 if (urls.length == 0)
133 return; 138 return;
134 139
135 ext.backgroundPage.sendMessage( 140 ext.backgroundPage.sendMessage(
136 { 141 {
137 type: "filters.collapse", 142 type: "filters.collapse",
138 urls: urls, 143 urls,
139 mediatype: mediatype, 144 mediatype,
140 baseURL: document.location.href 145 baseURL: document.location.href
141 }, 146 },
142 147
143 collapse => 148 collapse =>
144 { 149 {
145 function collapseElement() 150 function collapseElement()
146 { 151 {
147 let propertyName = "display"; 152 let propertyName = "display";
148 let propertyValue = "none"; 153 let propertyValue = "none";
149 if (element.localName == "frame") 154 if (element.localName == "frame")
(...skipping 22 matching lines...) Expand all
172 ); 177 );
173 } 178 }
174 179
175 function checkSitekey() 180 function checkSitekey()
176 { 181 {
177 let attr = document.documentElement.getAttribute("data-adblockkey"); 182 let attr = document.documentElement.getAttribute("data-adblockkey");
178 if (attr) 183 if (attr)
179 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr}); 184 ext.backgroundPage.sendMessage({type: "filters.addKey", token: attr});
180 } 185 }
181 186
182 function getContentDocument(element) 187 function ElementHidingTracer()
183 { 188 {
184 try 189 this.selectors = [];
185 {
186 return element.contentDocument;
187 }
188 catch (e)
189 {
190 return null;
191 }
192 }
193
194 function ElementHidingTracer(selectors)
195 {
196 this.selectors = selectors;
197
198 this.changedNodes = []; 190 this.changedNodes = [];
199 this.timeout = null; 191 this.timeout = null;
200
201 this.observer = new MutationObserver(this.observe.bind(this)); 192 this.observer = new MutationObserver(this.observe.bind(this));
202 this.trace = this.trace.bind(this); 193 this.trace = this.trace.bind(this);
203 194
204 if (document.readyState == "loading") 195 if (document.readyState == "loading")
205 document.addEventListener("DOMContentLoaded", this.trace); 196 document.addEventListener("DOMContentLoaded", this.trace);
206 else 197 else
207 this.trace(); 198 this.trace();
208 } 199 }
209 ElementHidingTracer.prototype = { 200 ElementHidingTracer.prototype = {
210 checkNodes(nodes) 201 addSelectors(selectors, filters)
211 { 202 {
212 let matchedSelectors = []; 203 let pairs = selectors.map((sel, i) => [sel, filters && filters[i]]);
213 204
214 // Find all selectors that match any hidden element inside the given nodes. 205 if (document.readyState != "loading")
215 for (let selector of this.selectors) 206 this.checkNodes([document], pairs);
207
208 this.selectors.push(...pairs);
209 },
210
211 checkNodes(nodes, pairs)
212 {
213 let selectors = [];
214 let filters = [];
215
216 for (let [selector, filter] of pairs)
216 { 217 {
217 for (let node of nodes) 218 nodes: for (let node of nodes)
218 { 219 {
219 let elements = node.querySelectorAll(selector); 220 for (let element of node.querySelectorAll(selector))
220 let matched = false;
221
222 for (let element of elements)
223 { 221 {
224 // Only consider selectors that actually have an effect on the 222 // Only consider selectors that actually have an effect on the
225 // computed styles, and aren't overridden by rules with higher 223 // computed styles, and aren't overridden by rules with higher
226 // priority, or haven't been circumvented in a different way. 224 // priority, or haven't been circumvented in a different way.
227 if (getComputedStyle(element).display == "none") 225 if (getComputedStyle(element).display == "none")
228 { 226 {
229 matchedSelectors.push(selector); 227 // For regular element hiding, we don't know the exact filter,
230 matched = true; 228 // but the background page can find it with the given selector.
231 break; 229 // In case of element hiding emulation, the generated selector
230 // we got here is different from the selector part of the filter,
231 // but in this case we can send the whole filter text instead.
232 if (filter)
233 filters.push(filter);
234 else
235 selectors.push(selector);
236
237 break nodes;
232 } 238 }
233 } 239 }
234
235 if (matched)
236 break;
237 } 240 }
238 } 241 }
239 242
240 if (matchedSelectors.length > 0) 243 if (selectors.length > 0 || filters.length > 0)
244 {
241 ext.backgroundPage.sendMessage({ 245 ext.backgroundPage.sendMessage({
242 type: "devtools.traceElemHide", 246 type: "devtools.traceElemHide",
243 selectors: matchedSelectors 247 selectors, filters
244 }); 248 });
249 }
245 }, 250 },
246 251
247 onTimeout() 252 onTimeout()
248 { 253 {
249 this.checkNodes(this.changedNodes); 254 this.checkNodes(this.changedNodes, this.selectors);
250 this.changedNodes = []; 255 this.changedNodes = [];
251 this.timeout = null; 256 this.timeout = null;
252 }, 257 },
253 258
254 observe(mutations) 259 observe(mutations)
255 { 260 {
256 // Forget previously changed nodes that are no longer in the DOM. 261 // Forget previously changed nodes that are no longer in the DOM.
257 for (let i = 0; i < this.changedNodes.length; i++) 262 for (let i = 0; i < this.changedNodes.length; i++)
258 { 263 {
259 if (!document.contains(this.changedNodes[i])) 264 if (!document.contains(this.changedNodes[i]))
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
301 306
302 // Check only nodes whose descendants have changed, and not more often 307 // Check only nodes whose descendants have changed, and not more often
303 // than once a second. Otherwise large pages with a lot of DOM mutations 308 // than once a second. Otherwise large pages with a lot of DOM mutations
304 // (like YouTube) freeze when the devtools panel is active. 309 // (like YouTube) freeze when the devtools panel is active.
305 if (this.timeout == null) 310 if (this.timeout == null)
306 this.timeout = setTimeout(this.onTimeout.bind(this), 1000); 311 this.timeout = setTimeout(this.onTimeout.bind(this), 1000);
307 }, 312 },
308 313
309 trace() 314 trace()
310 { 315 {
311 this.checkNodes([document]); 316 this.checkNodes([document], this.selectors);
312 317
313 this.observer.observe( 318 this.observer.observe(
314 document, 319 document,
315 { 320 {
316 childList: true, 321 childList: true,
317 attributes: true, 322 attributes: true,
318 subtree: true 323 subtree: true
319 } 324 }
320 ); 325 );
321 }, 326 },
322 327
323 disconnect() 328 disconnect()
324 { 329 {
325 document.removeEventListener("DOMContentLoaded", this.trace); 330 document.removeEventListener("DOMContentLoaded", this.trace);
326 this.observer.disconnect(); 331 this.observer.disconnect();
327 clearTimeout(this.timeout); 332 clearTimeout(this.timeout);
328 } 333 }
329 }; 334 };
330 335
331 function runInPageContext(fn, arg)
332 {
333 let script = document.createElement("script");
334 script.type = "application/javascript";
335 script.async = false;
336 script.textContent = "(" + fn + ")(" + JSON.stringify(arg) + ");";
337 document.documentElement.appendChild(script);
338 document.documentElement.removeChild(script);
339 }
340
341 // Chrome doesn't allow us to intercept WebSockets[1], and therefore
342 // some ad networks are misusing them as a way to serve adverts and circumvent
343 // us. As a workaround we wrap WebSocket, preventing blocked WebSocket
344 // connections from being opened.
345 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=129353
346 function wrapWebSocket()
347 {
348 let eventName = "abpws-" + Math.random().toString(36).substr(2);
349
350 document.addEventListener(eventName, event =>
351 {
352 ext.backgroundPage.sendMessage({
353 type: "request.websocket",
354 url: event.detail.url
355 }, block =>
356 {
357 document.dispatchEvent(
358 new CustomEvent(eventName + "-" + event.detail.url, {detail: block})
359 );
360 });
361 });
362
363 runInPageContext(eventName =>
364 {
365 // As far as possible we must track everything we use that could be
366 // sabotaged by the website later in order to circumvent us.
367 let RealWebSocket = WebSocket;
368 let closeWebSocket = Function.prototype.call.bind(RealWebSocket.prototype.cl ose);
369 let addEventListener = document.addEventListener.bind(document);
370 let removeEventListener = document.removeEventListener.bind(document);
371 let dispatchEvent = document.dispatchEvent.bind(document);
372 let CustomEvent = window.CustomEvent;
373
374 function checkRequest(url, callback)
375 {
376 let incomingEventName = eventName + "-" + url;
377 function listener(event)
378 {
379 callback(event.detail);
380 removeEventListener(incomingEventName, listener);
381 }
382 addEventListener(incomingEventName, listener);
383
384 dispatchEvent(new CustomEvent(eventName, {
385 detail: {url: url}
386 }));
387 }
388
389 function WrappedWebSocket(url)
390 {
391 // Throw correct exceptions if the constructor is used improperly.
392 if (!(this instanceof WrappedWebSocket)) return RealWebSocket();
393 if (arguments.length < 1) return new RealWebSocket();
394
395 let websocket;
396 if (arguments.length == 1)
397 websocket = new RealWebSocket(url);
398 else
399 websocket = new RealWebSocket(url, arguments[1]);
400
401 checkRequest(websocket.url, blocked =>
402 {
403 if (blocked)
404 closeWebSocket(websocket);
405 });
406
407 return websocket;
408 }
409 WrappedWebSocket.prototype = RealWebSocket.prototype;
410 WebSocket = WrappedWebSocket.bind();
411 Object.defineProperties(WebSocket, {
412 CONNECTING: {value: RealWebSocket.CONNECTING, enumerable: true},
413 OPEN: {value: RealWebSocket.OPEN, enumerable: true},
414 CLOSING: {value: RealWebSocket.CLOSING, enumerable: true},
415 CLOSED: {value: RealWebSocket.CLOSED, enumerable: true},
416 prototype: {value: RealWebSocket.prototype}
417 });
418
419 RealWebSocket.prototype.constructor = WebSocket;
420 }, eventName);
421 }
422
423 function ElemHide() 336 function ElemHide()
424 { 337 {
425 this.shadow = this.createShadowTree(); 338 this.shadow = this.createShadowTree();
426 this.style = null; 339 this.style = null;
427 this.tracer = null; 340 this.tracer = null;
428 341
429 this.elemHideEmulation = new ElemHideEmulation( 342 this.elemHideEmulation = new ElemHideEmulation(
430 window, 343 window,
431 callback => 344 callback =>
432 { 345 {
(...skipping 22 matching lines...) Expand all
455 if (/\.(?:google|blogger)\.com$/.test(document.domain)) 368 if (/\.(?:google|blogger)\.com$/.test(document.domain))
456 return null; 369 return null;
457 370
458 // Finally since some users have both AdBlock and Adblock Plus installed we 371 // Finally since some users have both AdBlock and Adblock Plus installed we
459 // have to consider how the two extensions interact. For example we want to 372 // have to consider how the two extensions interact. For example we want to
460 // avoid creating the shadowRoot twice. 373 // avoid creating the shadowRoot twice.
461 let shadow = document.documentElement.shadowRoot || 374 let shadow = document.documentElement.shadowRoot ||
462 document.documentElement.createShadowRoot(); 375 document.documentElement.createShadowRoot();
463 shadow.appendChild(document.createElement("shadow")); 376 shadow.appendChild(document.createElement("shadow"));
464 377
465 // Stop the website from messing with our shadow root (#4191, #4298).
466 if ("shadowRoot" in Element.prototype)
467 {
468 runInPageContext(() =>
469 {
470 let ourShadowRoot = document.documentElement.shadowRoot;
471 if (!ourShadowRoot)
472 return;
473 let desc = Object.getOwnPropertyDescriptor(Element.prototype, "shadowRoo t");
474 let shadowRoot = Function.prototype.call.bind(desc.get);
475
476 Object.defineProperty(Element.prototype, "shadowRoot", {
477 configurable: true, enumerable: true, get()
478 {
479 let shadow = shadowRoot(this);
480 return shadow == ourShadowRoot ? null : shadow;
481 }
482 });
483 }, null);
484 }
485
486 return shadow; 378 return shadow;
487 }, 379 },
488 380
489 addSelectors(selectors) 381 addSelectors(selectors, filters)
490 { 382 {
491 if (selectors.length == 0) 383 if (selectors.length == 0)
492 return; 384 return;
493 385
494 if (!this.style) 386 if (!this.style)
495 { 387 {
496 // Create <style> element lazily, only if we add styles. Add it to 388 // Create <style> element lazily, only if we add styles. Add it to
497 // the shadow DOM if possible. Otherwise fallback to the <head> or 389 // the shadow DOM if possible. Otherwise fallback to the <head> or
498 // <html> element. If we have injected a style element before that 390 // <html> element. If we have injected a style element before that
499 // has been removed (the sheet property is null), create a new one. 391 // has been removed (the sheet property is null), create a new one.
500 this.style = document.createElement("style"); 392 this.style = document.createElement("style");
501 (this.shadow || document.head 393 (this.shadow || document.head ||
502 || document.documentElement).appendChild(this.style); 394 document.documentElement).appendChild(this.style);
503 395
504 // It can happen that the frame already navigated to a different 396 // It can happen that the frame already navigated to a different
505 // document while we were waiting for the background page to respond. 397 // document while we were waiting for the background page to respond.
506 // In that case the sheet property will stay null, after addind the 398 // In that case the sheet property will stay null, after addind the
507 // <style> element to the shadow DOM. 399 // <style> element to the shadow DOM.
508 if (!this.style.sheet) 400 if (!this.style.sheet)
509 return; 401 return;
510 } 402 }
511 403
512 // If using shadow DOM, we have to add the ::content pseudo-element 404 // If using shadow DOM, we have to add the ::content pseudo-element
513 // before each selector, in order to match elements within the 405 // before each selector, in order to match elements within the
514 // insertion point. 406 // insertion point.
407 let preparedSelectors = [];
515 if (this.shadow) 408 if (this.shadow)
516 { 409 {
517 let preparedSelectors = [];
518 for (let selector of selectors) 410 for (let selector of selectors)
519 { 411 {
520 let subSelectors = splitSelector(selector); 412 let subSelectors = splitSelector(selector);
521 for (let subSelector of subSelectors) 413 for (let subSelector of subSelectors)
522 preparedSelectors.push("::content " + subSelector); 414 preparedSelectors.push("::content " + subSelector);
523 } 415 }
524 selectors = preparedSelectors; 416 }
417 else
418 {
419 preparedSelectors = selectors;
525 } 420 }
526 421
527 // Safari only allows 8192 primitive selectors to be injected at once[1], we 422 // Safari only allows 8192 primitive selectors to be injected at once[1], we
528 // therefore chunk the inserted selectors into groups of 200 to be safe. 423 // therefore chunk the inserted selectors into groups of 200 to be safe.
529 // (Chrome also has a limit, larger... but we're not certain exactly what it 424 // (Chrome also has a limit, larger... but we're not certain exactly what it
530 // is! Edge apparently has no such limit.) 425 // is! Edge apparently has no such limit.)
531 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68 426 // [1] - https://github.com/WebKit/webkit/blob/1cb2227f6b2a1035f7bdc46e5ab69 debb75fc1de/Source/WebCore/css/RuleSet.h#L68
532 for (let i = 0; i < selectors.length; i += this.selectorGroupSize) 427 for (let i = 0; i < preparedSelectors.length; i += this.selectorGroupSize)
533 { 428 {
534 let selector = selectors.slice(i, i + this.selectorGroupSize).join(", "); 429 let selector = preparedSelectors.slice(
430 i, i + this.selectorGroupSize
431 ).join(", ");
535 this.style.sheet.insertRule(selector + "{display: none !important;}", 432 this.style.sheet.insertRule(selector + "{display: none !important;}",
536 this.style.sheet.cssRules.length); 433 this.style.sheet.cssRules.length);
537 } 434 }
435
436 if (this.tracer)
437 this.tracer.addSelectors(selectors, filters);
538 }, 438 },
539 439
540 apply() 440 apply()
541 { 441 {
542 let selectors = null; 442 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
543 let elemHideEmulationLoaded = false;
544
545 let checkLoaded = function()
546 { 443 {
547 if (!selectors || !elemHideEmulationLoaded)
548 return;
549
550 if (this.tracer) 444 if (this.tracer)
551 this.tracer.disconnect(); 445 this.tracer.disconnect();
552 this.tracer = null; 446 this.tracer = null;
553 447
554 if (this.style && this.style.parentElement) 448 if (this.style && this.style.parentElement)
555 this.style.parentElement.removeChild(this.style); 449 this.style.parentElement.removeChild(this.style);
556 this.style = null; 450 this.style = null;
557 451
558 this.addSelectors(selectors.selectors); 452 if (response.trace)
453 this.tracer = new ElementHidingTracer();
454
455 this.addSelectors(response.selectors);
559 this.elemHideEmulation.apply(); 456 this.elemHideEmulation.apply();
560
561 if (selectors.trace)
562 this.tracer = new ElementHidingTracer(selectors.selectors);
563 }.bind(this);
564
565 ext.backgroundPage.sendMessage({type: "get-selectors"}, response =>
566 {
567 selectors = response;
568 checkLoaded();
569 });
570
571 this.elemHideEmulation.load(() =>
572 {
573 elemHideEmulationLoaded = true;
574 checkLoaded();
575 }); 457 });
576 } 458 }
577 }; 459 };
578 460
579 if (document instanceof HTMLDocument) 461 if (document instanceof HTMLDocument)
580 { 462 {
581 checkSitekey(); 463 checkSitekey();
582 wrapWebSocket();
583 464
584 // This variable is also used by our other content scripts, outside of the 465 elemhide = new ElemHide();
585 // current scope.
586 var elemhide = new ElemHide();
587 elemhide.apply(); 466 elemhide.apply();
588 467
589 document.addEventListener("error", event => 468 document.addEventListener("error", event =>
590 { 469 {
591 checkCollapse(event.target); 470 checkCollapse(event.target);
592 }, true); 471 }, true);
593 472
594 document.addEventListener("load", event => 473 document.addEventListener("load", event =>
595 { 474 {
596 let element = event.target; 475 let element = event.target;
597 if (/^i?frame$/.test(element.localName)) 476 if (/^i?frame$/.test(element.localName))
598 checkCollapse(element); 477 checkCollapse(element);
599 }, true); 478 }, true);
600 } 479 }
OLDNEW
« no previous file with comments | « icons/detailed/abp-48.png ('k') | inject.preload.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld