Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2016 Eyeo GmbH | |
4 * | |
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 | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
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/>. | |
16 */ | |
17 | |
18 package org.adblockplus.android; | |
19 | |
20 import org.adblockplus.libadblockplus.AppInfo; | |
21 import org.adblockplus.libadblockplus.Filter; | |
22 import org.adblockplus.libadblockplus.FilterEngine; | |
23 import org.adblockplus.libadblockplus.JsEngine; | |
24 | |
25 import android.content.Context; | |
26 import android.content.pm.PackageInfo; | |
27 import android.content.pm.PackageManager; | |
28 import android.graphics.Bitmap; | |
29 import android.graphics.Canvas; | |
30 import android.graphics.Color; | |
31 import android.net.http.SslError; | |
32 import android.os.Build; | |
33 import android.os.Handler; | |
34 import android.os.Message; | |
35 import android.util.AttributeSet; | |
36 import android.util.Log; | |
37 import android.view.KeyEvent; | |
38 import android.view.View; | |
39 import android.webkit.ConsoleMessage; | |
40 import android.webkit.GeolocationPermissions; | |
41 import android.webkit.HttpAuthHandler; | |
42 import android.webkit.JsPromptResult; | |
43 import android.webkit.JsResult; | |
44 import android.webkit.SslErrorHandler; | |
45 import android.webkit.ValueCallback; | |
46 import android.webkit.WebChromeClient; | |
47 import android.webkit.WebResourceResponse; | |
48 import android.webkit.WebStorage; | |
49 import android.webkit.WebView; | |
50 import android.webkit.JavascriptInterface; // makes android min version to be 17 | |
51 import android.webkit.WebViewClient; | |
52 | |
53 import java.io.IOException; | |
54 import java.net.URISyntaxException; | |
55 import java.util.List; | |
56 import java.util.Locale; | |
57 import java.util.Map; | |
58 import java.util.concurrent.CountDownLatch; | |
59 import java.util.regex.Pattern; | |
60 | |
61 /** | |
62 * WebView with ad blocking | |
63 */ | |
64 public class AdblockWebView extends WebView | |
65 { | |
66 private static final String TAG = Utils.getTag(AdblockWebView.class); | |
67 | |
68 private volatile boolean addDomListener = true; | |
69 | |
70 /** | |
71 * Warning: do not rename (used in injected JS by method name) | |
72 * @param value set if one need to set DOM listener | |
73 */ | |
74 @JavascriptInterface | |
anton
2016/09/08 13:00:11
makes android 17 minimum to be compiled
| |
75 public void setAddDomListener(boolean value) { | |
76 this.addDomListener = value; | |
77 } | |
78 | |
79 @JavascriptInterface | |
80 public boolean getAddDomListener() | |
81 { | |
82 return addDomListener; | |
83 } | |
84 | |
85 private WebChromeClient extWebChromeClient; | |
86 | |
87 @Override | |
88 public void setWebChromeClient(WebChromeClient client) | |
89 { | |
90 extWebChromeClient = client; | |
91 } | |
92 | |
93 private boolean debugMode; | |
94 | |
95 public boolean isDebugMode() | |
96 { | |
97 return debugMode; | |
98 } | |
99 | |
100 /** | |
101 * Set to true to see debug log output int AdblockWebView and JS console | |
102 * @param debugMode is debug mode | |
103 */ | |
104 public void setDebugMode(boolean debugMode) | |
105 { | |
106 this.debugMode = debugMode; | |
107 } | |
108 | |
109 private void d(String message) | |
110 { | |
111 if (debugMode) | |
112 { | |
113 Log.d(TAG, message); | |
114 } | |
115 } | |
116 | |
117 private void w(String message) | |
118 { | |
119 if (debugMode) | |
120 { | |
121 Log.w(TAG, message); | |
122 } | |
123 } | |
124 | |
125 private void e(String message, Throwable t) | |
126 { | |
127 Log.e(TAG, message, t); | |
128 } | |
129 | |
130 private void e(String message) | |
131 { | |
132 Log.e(TAG, message); | |
133 } | |
134 | |
135 private static final String BRIDGE_TOKEN = "{{BRIDGE}}"; | |
136 private static final String DEBUG_TOKEN = "{{DEBUG}}"; | |
137 private static final String HIDE_TOKEN = "{{HIDE}}"; | |
138 private static final String BRIDGE = "jsBridge"; | |
139 | |
140 private String readScriptFile(String filename) throws IOException | |
141 { | |
142 return Utils | |
143 .readAssetAsString(getContext(), filename) | |
144 .replace(BRIDGE_TOKEN, BRIDGE) | |
145 .replace(DEBUG_TOKEN, (debugMode ? "" : "//")); | |
146 } | |
147 | |
148 private void runScript(String script) { | |
149 if (Build.VERSION.SDK_INT >= 19) | |
150 { | |
151 evaluateJavascript(script, null); | |
anton
2016/09/08 13:00:12
`evaluateJavascript` in `AdblockWebView` requires
| |
152 } | |
153 else | |
154 { | |
155 loadUrl("javascript:" + script); | |
156 } | |
157 } | |
158 | |
159 private boolean disposeFilterEngine; | |
160 | |
161 private JsEngine jsEngine; | |
162 | |
163 public JsEngine getJsEngine() | |
164 { | |
165 return jsEngine; | |
166 } | |
167 | |
168 private FilterEngine filterEngine; | |
169 | |
170 public FilterEngine getFilterEngine() | |
171 { | |
172 return filterEngine; | |
173 } | |
174 | |
175 private Integer loadError; | |
176 | |
177 /** | |
178 * Set external filter engine. A new (internal) is created automatically if no t set | |
179 * Don't forget to invoke {@link #dispose()} if not using external filter engi ne | |
180 * @param newFilterEngine external filter engine | |
181 */ | |
182 public void setFilterEngine(FilterEngine newFilterEngine) | |
183 { | |
184 if (filterEngine != null && newFilterEngine != null && newFilterEngine == fi lterEngine) | |
185 { | |
186 return; | |
187 } | |
188 | |
189 if (filterEngine != null && disposeFilterEngine) | |
190 { | |
191 filterEngine.dispose(); | |
192 } | |
193 | |
194 filterEngine = newFilterEngine; | |
195 disposeFilterEngine = false; | |
196 | |
197 if (newFilterEngine != null && jsEngine != null) | |
198 { | |
199 jsEngine.dispose(); | |
200 } | |
201 jsEngine = null; | |
202 } | |
203 | |
204 private WebChromeClient intWebChromeClient = new WebChromeClient() | |
205 { | |
206 @Override | |
207 public void onReceivedTitle(WebView view, String title) | |
208 { | |
209 if (extWebChromeClient != null) | |
210 { | |
211 extWebChromeClient.onReceivedTitle(view, title); | |
212 } | |
213 } | |
214 | |
215 @Override | |
216 public void onReceivedIcon(WebView view, Bitmap icon) | |
217 { | |
218 if (extWebChromeClient != null) | |
219 { | |
220 extWebChromeClient.onReceivedIcon(view, icon); | |
221 } | |
222 } | |
223 | |
224 @Override | |
225 public void onReceivedTouchIconUrl(WebView view, String url, boolean precomp osed) | |
226 { | |
227 if (extWebChromeClient != null) | |
228 { | |
229 extWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed); | |
230 } | |
231 } | |
232 | |
233 @Override | |
234 public void onShowCustomView(View view, CustomViewCallback callback) | |
235 { | |
236 if (extWebChromeClient != null) | |
237 { | |
238 extWebChromeClient.onShowCustomView(view, callback); | |
239 } | |
240 } | |
241 | |
242 @Override | |
243 public void onShowCustomView(View view, int requestedOrientation, CustomView Callback callback) | |
244 { | |
245 if (extWebChromeClient != null) | |
246 { | |
247 extWebChromeClient.onShowCustomView(view, requestedOrientation, callback ); | |
248 } | |
249 } | |
250 | |
251 @Override | |
252 public void onHideCustomView() | |
253 { | |
254 if (extWebChromeClient != null) | |
255 { | |
256 extWebChromeClient.onHideCustomView(); | |
257 } | |
258 } | |
259 | |
260 @Override | |
261 public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUser Gesture, | |
262 Message resultMsg) | |
263 { | |
264 if (extWebChromeClient != null) | |
265 { | |
266 return extWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg); | |
267 } | |
268 else | |
269 { | |
270 return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); | |
271 } | |
272 } | |
273 | |
274 @Override | |
275 public void onRequestFocus(WebView view) | |
276 { | |
277 if (extWebChromeClient != null) | |
278 { | |
279 extWebChromeClient.onRequestFocus(view); | |
280 } | |
281 } | |
282 | |
283 @Override | |
284 public void onCloseWindow(WebView window) | |
285 { | |
286 if (extWebChromeClient != null) | |
287 { | |
288 extWebChromeClient.onCloseWindow(window); | |
289 } | |
290 } | |
291 | |
292 @Override | |
293 public boolean onJsAlert(WebView view, String url, String message, JsResult result) | |
294 { | |
295 if (extWebChromeClient != null) | |
296 { | |
297 return extWebChromeClient.onJsAlert(view, url, message, result); | |
298 } | |
299 else | |
300 { | |
301 return super.onJsAlert(view, url, message, result); | |
302 } | |
303 } | |
304 | |
305 @Override | |
306 public boolean onJsConfirm(WebView view, String url, String message, JsResul t result) | |
307 { | |
308 if (extWebChromeClient != null) | |
309 { | |
310 return extWebChromeClient.onJsConfirm(view, url, message, result); | |
311 } | |
312 else | |
313 { | |
314 return super.onJsConfirm(view, url, message, result); | |
315 } | |
316 } | |
317 | |
318 @Override | |
319 public boolean onJsPrompt(WebView view, String url, String message, String d efaultValue, | |
320 JsPromptResult result) | |
321 { | |
322 if (extWebChromeClient != null) | |
323 { | |
324 return extWebChromeClient.onJsPrompt(view, url, message, defaultValue, r esult); | |
325 } | |
326 else | |
327 { | |
328 return super.onJsPrompt(view, url, message, defaultValue, result); | |
329 } | |
330 } | |
331 | |
332 @Override | |
333 public boolean onJsBeforeUnload(WebView view, String url, String message, Js Result result) | |
334 { | |
335 if (extWebChromeClient != null) | |
336 { | |
337 return extWebChromeClient.onJsBeforeUnload(view, url, message, result); | |
338 } | |
339 else | |
340 { | |
341 return super.onJsBeforeUnload(view, url, message, result); | |
342 } | |
343 } | |
344 | |
345 @Override | |
346 public void onExceededDatabaseQuota(String url, String databaseIdentifier, l ong quota, | |
347 long estimatedDatabaseSize, long totalQu ota, | |
348 WebStorage.QuotaUpdater quotaUpdater) | |
349 { | |
350 if (extWebChromeClient != null) | |
351 { | |
352 extWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quot a, | |
353 estimatedDatabaseSize, totalQuota, quotaUpdater); | |
354 } | |
355 else | |
356 { | |
357 super.onExceededDatabaseQuota(url, databaseIdentifier, quota, | |
358 estimatedDatabaseSize, totalQuota, quotaUpdater); | |
359 } | |
360 } | |
361 | |
362 @Override | |
363 public void onReachedMaxAppCacheSize(long requiredStorage, long quota, | |
364 WebStorage.QuotaUpdater quotaUpdater) | |
365 { | |
366 if (extWebChromeClient != null) | |
367 { | |
368 extWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quot aUpdater); | |
369 } | |
370 else | |
371 { | |
372 super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); | |
373 } | |
374 } | |
375 | |
376 @Override | |
377 public void onGeolocationPermissionsShowPrompt(String origin, | |
378 GeolocationPermissions.Callba ck callback) | |
379 { | |
380 if (extWebChromeClient != null) | |
381 { | |
382 extWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); | |
383 } | |
384 else | |
385 { | |
386 super.onGeolocationPermissionsShowPrompt(origin, callback); | |
387 } | |
388 } | |
389 | |
390 @Override | |
391 public void onGeolocationPermissionsHidePrompt() | |
392 { | |
393 if (extWebChromeClient != null) | |
394 { | |
395 extWebChromeClient.onGeolocationPermissionsHidePrompt(); | |
396 } | |
397 else | |
398 { | |
399 super.onGeolocationPermissionsHidePrompt(); | |
400 } | |
401 } | |
402 | |
403 @Override | |
404 public boolean onJsTimeout() | |
405 { | |
406 if (extWebChromeClient != null) | |
407 { | |
408 return extWebChromeClient.onJsTimeout(); | |
409 } | |
410 else | |
411 { | |
412 return super.onJsTimeout(); | |
413 } | |
414 } | |
415 | |
416 @Override | |
417 public void onConsoleMessage(String message, int lineNumber, String sourceID ) | |
418 { | |
419 if (extWebChromeClient != null) | |
420 { | |
421 extWebChromeClient.onConsoleMessage(message, lineNumber, sourceID); | |
422 } | |
423 else | |
424 { | |
425 super.onConsoleMessage(message, lineNumber, sourceID); | |
426 } | |
427 } | |
428 | |
429 @Override | |
430 public boolean onConsoleMessage(ConsoleMessage consoleMessage) | |
431 { | |
432 if (extWebChromeClient != null) | |
433 { | |
434 return extWebChromeClient.onConsoleMessage(consoleMessage); | |
435 } | |
436 else | |
437 { | |
438 return super.onConsoleMessage(consoleMessage); | |
439 } | |
440 } | |
441 | |
442 @Override | |
443 public Bitmap getDefaultVideoPoster() | |
444 { | |
445 if (extWebChromeClient != null) | |
446 { | |
447 return extWebChromeClient.getDefaultVideoPoster(); | |
448 } | |
449 else | |
450 { | |
451 return super.getDefaultVideoPoster(); | |
452 } | |
453 } | |
454 | |
455 @Override | |
456 public View getVideoLoadingProgressView() | |
457 { | |
458 if (extWebChromeClient != null) | |
459 { | |
460 return extWebChromeClient.getVideoLoadingProgressView(); | |
461 } | |
462 else | |
463 { | |
464 return super.getVideoLoadingProgressView(); | |
465 } | |
466 } | |
467 | |
468 @Override | |
469 public void getVisitedHistory(ValueCallback<String[]> callback) | |
470 { | |
471 if (extWebChromeClient != null) | |
472 { | |
473 extWebChromeClient.getVisitedHistory(callback); | |
474 } | |
475 else | |
476 { | |
477 super.getVisitedHistory(callback); | |
478 } | |
479 } | |
480 | |
481 @Override | |
482 public void onProgressChanged(WebView view, int newProgress) | |
483 { | |
484 d("Loading progress=" + newProgress + "%"); | |
485 | |
486 // addDomListener is changed to 'false' in `setAddDomListener` invoked fro m injected JS | |
487 if (getAddDomListener() && loadError == null && injectJs != null) | |
488 { | |
489 runScript(injectJs); | |
490 | |
491 if (allowDraw) | |
492 { | |
493 startPreventDrawing(); | |
494 } | |
495 } | |
496 | |
497 if (extWebChromeClient != null) | |
498 { | |
499 extWebChromeClient.onProgressChanged(view, newProgress); | |
500 } | |
501 } | |
502 }; | |
503 | |
504 /** | |
505 * Default (in some conditions) start redraw delay after DOM modified with inj ected JS (millis) | |
506 */ | |
507 public static final int ALLOW_DRAW_DELAY = 200; | |
508 /* | |
509 The value could be different for devices and completely unclear why we need it and | |
510 how to measure actual value | |
511 */ | |
512 | |
513 private int allowDrawDelay = ALLOW_DRAW_DELAY; | |
514 | |
515 public int getAllowDrawDelay() | |
516 { | |
517 return allowDrawDelay; | |
518 } | |
519 | |
520 /** | |
521 * Set start redraw delay after DOM modified with injected JS | |
522 * (used to prevent flickering after 'DOM ready') | |
523 * @param allowDrawDelay delay (in millis) | |
524 */ | |
525 public void setAllowDrawDelay(int allowDrawDelay) | |
526 { | |
527 if (allowDrawDelay < 0) | |
528 throw new IllegalArgumentException("Negative value is not allowed"); | |
529 | |
530 this.allowDrawDelay = allowDrawDelay; | |
531 } | |
532 | |
533 private WebViewClient extWebViewClient; | |
534 | |
535 @Override | |
536 public void setWebViewClient(WebViewClient client) | |
537 { | |
538 extWebViewClient = client; | |
539 } | |
540 | |
541 private static final Pattern RE_JS = Pattern.compile("\\.js$", Pattern.CASE_IN SENSITIVE); | |
542 private static final Pattern RE_CSS = Pattern.compile("\\.css$", Pattern.CASE_ INSENSITIVE); | |
543 private static final Pattern RE_IMAGE = Pattern.compile("\\.(?:gif|png|jpe?g|b mp|ico)$", Pattern.CASE_INSENSITIVE); | |
544 private static final Pattern RE_FONT = Pattern.compile("\\.(?:ttf|woff)$", Pat tern.CASE_INSENSITIVE); | |
545 private static final Pattern RE_HTML = Pattern.compile("\\.html?$", Pattern.CA SE_INSENSITIVE); | |
546 | |
547 private WebViewClient intWebViewClient = new WebViewClient() | |
548 { | |
549 @Override | |
550 public boolean shouldOverrideUrlLoading(WebView view, String url) | |
551 { | |
552 if (extWebViewClient != null) | |
553 { | |
554 return extWebViewClient.shouldOverrideUrlLoading(view, url); | |
555 } | |
556 else | |
557 { | |
558 return super.shouldOverrideUrlLoading(view, url); | |
559 } | |
560 } | |
561 | |
562 @Override | |
563 public void onPageStarted(WebView view, String url, Bitmap favicon) | |
564 { | |
565 if (extWebViewClient != null) | |
566 { | |
567 extWebViewClient.onPageStarted(view, url, favicon); | |
568 } | |
569 else | |
570 { | |
571 super.onPageStarted(view, url, favicon); | |
572 } | |
573 } | |
574 | |
575 @Override | |
576 public void onPageFinished(WebView view, String url) | |
577 { | |
578 if (extWebViewClient != null) | |
579 { | |
580 extWebViewClient.onPageFinished(view, url); | |
581 } | |
582 else | |
583 { | |
584 super.onPageFinished(view, url); | |
585 } | |
586 } | |
587 | |
588 @Override | |
589 public void onLoadResource(WebView view, String url) | |
590 { | |
591 if (extWebViewClient != null) | |
592 { | |
593 extWebViewClient.onLoadResource(view, url); | |
594 } | |
595 else | |
596 { | |
597 super.onLoadResource(view, url); | |
598 } | |
599 } | |
600 | |
601 @Override | |
602 public void onTooManyRedirects(WebView view, Message cancelMsg, Message cont inueMsg) | |
603 { | |
604 if (extWebViewClient != null) | |
605 { | |
606 extWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg); | |
607 } | |
608 else | |
609 { | |
610 super.onTooManyRedirects(view, cancelMsg, continueMsg); | |
611 } | |
612 } | |
613 | |
614 @Override | |
615 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) | |
616 { | |
617 e("Load error:" + | |
618 " code=" + errorCode + | |
619 " with description=" + description + | |
620 " for url=" + failingUrl); | |
621 loadError = errorCode; | |
622 | |
623 stopAbpLoading(); | |
624 | |
625 if (extWebViewClient != null) | |
626 { | |
627 extWebViewClient.onReceivedError(view, errorCode, description, failingUr l); | |
628 } | |
629 else | |
630 { | |
631 super.onReceivedError(view, errorCode, description, failingUrl); | |
632 } | |
633 } | |
634 | |
635 @Override | |
636 public void onFormResubmission(WebView view, Message dontResend, Message res end) | |
637 { | |
638 if (extWebViewClient != null) | |
639 { | |
640 extWebViewClient.onFormResubmission(view, dontResend, resend); | |
641 } | |
642 else | |
643 { | |
644 super.onFormResubmission(view, dontResend, resend); | |
645 } | |
646 } | |
647 | |
648 @Override | |
649 public void doUpdateVisitedHistory(WebView view, String url, boolean isReloa d) | |
650 { | |
651 if (extWebViewClient != null) | |
652 { | |
653 extWebViewClient.doUpdateVisitedHistory(view, url, isReload); | |
654 } | |
655 else | |
656 { | |
657 super.doUpdateVisitedHistory(view, url, isReload); | |
658 } | |
659 } | |
660 | |
661 @Override | |
662 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslErr or error) | |
663 { | |
664 if (extWebViewClient != null) | |
665 { | |
666 extWebViewClient.onReceivedSslError(view, handler, error); | |
667 } | |
668 else | |
669 { | |
670 super.onReceivedSslError(view, handler, error); | |
671 } | |
672 } | |
673 | |
674 @Override | |
675 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) | |
676 { | |
677 if (extWebViewClient != null) | |
678 { | |
679 extWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm); | |
680 } | |
681 else | |
682 { | |
683 super.onReceivedHttpAuthRequest(view, handler, host, realm); | |
684 } | |
685 } | |
686 | |
687 @Override | |
688 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) | |
689 { | |
690 if (extWebViewClient != null) | |
691 { | |
692 return extWebViewClient.shouldOverrideKeyEvent(view, event); | |
693 } | |
694 else | |
695 { | |
696 return super.shouldOverrideKeyEvent(view, event); | |
697 } | |
698 } | |
699 | |
700 @Override | |
701 public void onUnhandledKeyEvent(WebView view, KeyEvent event) | |
702 { | |
703 if (extWebViewClient != null) | |
704 { | |
705 extWebViewClient.onUnhandledKeyEvent(view, event); | |
706 } | |
707 else | |
708 { | |
709 super.onUnhandledKeyEvent(view, event); | |
710 } | |
711 } | |
712 | |
713 @Override | |
714 public void onScaleChanged(WebView view, float oldScale, float newScale) | |
715 { | |
716 if (extWebViewClient != null) | |
717 { | |
718 extWebViewClient.onScaleChanged(view, oldScale, newScale); | |
719 } | |
720 else | |
721 { | |
722 super.onScaleChanged(view, oldScale, newScale); | |
723 } | |
724 } | |
725 | |
726 @Override | |
727 public void onReceivedLoginRequest(WebView view, String realm, String accoun t, String args) | |
728 { | |
729 if (extWebViewClient != null) | |
730 { | |
731 extWebViewClient.onReceivedLoginRequest(view, realm, account, args); | |
732 } | |
733 else | |
734 { | |
735 super.onReceivedLoginRequest(view, realm, account, args); | |
736 } | |
737 } | |
738 | |
739 @Override | |
740 public WebResourceResponse shouldInterceptRequest(WebView view, String url) { | |
741 // if dispose() was invoke, but the page is still loading then just let it go | |
742 if (filterEngine == null) | |
743 { | |
744 e("FilterEngine already disposed"); | |
745 return null; | |
746 } | |
747 | |
748 // Determine the content | |
749 FilterEngine.ContentType contentType = null; | |
750 if (RE_JS.matcher(url).find()) | |
751 { | |
752 contentType = FilterEngine.ContentType.SCRIPT; | |
753 } | |
754 else if (RE_CSS.matcher(url).find()) | |
755 { | |
756 contentType = FilterEngine.ContentType.STYLESHEET; | |
757 } | |
758 else if (RE_IMAGE.matcher(url).find()) | |
759 { | |
760 contentType = FilterEngine.ContentType.IMAGE; | |
761 } | |
762 else if (RE_FONT.matcher(url).find()) | |
763 { | |
764 contentType = FilterEngine.ContentType.FONT; | |
765 } | |
766 else if (RE_HTML.matcher(url).find()) | |
767 { | |
768 contentType = FilterEngine.ContentType.SUBDOCUMENT; | |
769 } | |
770 else | |
771 { | |
772 contentType = FilterEngine.ContentType.OTHER; | |
773 } | |
774 // Check if we should block ... we sadly do not have the referrer chain he re, | |
775 // might also be hard to get as we do not have HTTP headers here | |
776 Filter filter = filterEngine.matches(url, contentType, new String[0]); | |
777 if (filter != null && filter.getType().equals(Filter.Type.BLOCKING)) | |
778 { | |
779 w("Blocked loading " + url); | |
780 // If we should block, return empty response which results in a 404 | |
781 return new WebResourceResponse("text/plain", "UTF-8", null); | |
782 } | |
783 | |
784 d("Allowed loading " + url); | |
785 | |
786 // Otherwise, continue by returning null | |
787 return null; | |
788 } | |
789 }; | |
790 | |
791 private void initAbp() | |
792 { | |
793 addJavascriptInterface(this, BRIDGE); | |
794 | |
795 super.setWebChromeClient(intWebChromeClient); | |
796 super.setWebViewClient(intWebViewClient); | |
797 } | |
798 | |
799 /** | |
800 * Build app info using Android package information | |
801 * @param context context | |
802 * @param developmentBuild if it's dev build | |
803 * @return app info required to build JsEngine | |
804 */ | |
805 public static AppInfo buildAppInfo(final Context context, boolean developmentB uild) | |
806 { | |
807 String version = "0"; | |
808 try | |
809 { | |
810 final PackageInfo info = context.getPackageManager().getPackageInfo(contex t.getPackageName(), 0); | |
811 version = info.versionName; | |
812 if (developmentBuild) | |
813 version += "." + info.versionCode; | |
814 } | |
815 catch (final PackageManager.NameNotFoundException e) | |
816 { | |
817 Log.e(TAG, "Failed to get the application version number", e); | |
818 } | |
819 final String sdkVersion = String.valueOf(Build.VERSION.SDK_INT); | |
820 final String locale = Locale.getDefault().toString().replace('_', '-'); | |
821 | |
822 return AppInfo.builder() | |
823 .setVersion(version) | |
824 .setApplicationVersion(sdkVersion) | |
825 .setLocale(locale) | |
826 .setDevelopmentBuild(developmentBuild) | |
827 .build(); | |
828 } | |
829 | |
830 /** | |
831 * Build JsEngine required to build FilterEngine | |
832 * @param context context | |
833 * @param developmentBuild if it's dev build | |
834 * @return JsEngine | |
835 */ | |
836 public static JsEngine buildJsEngine(Context context, boolean developmentBuild ) | |
837 { | |
838 JsEngine jsEngine = new JsEngine(buildAppInfo(context, developmentBuild)); | |
839 jsEngine.setDefaultFileSystem(context.getCacheDir().getAbsolutePath()); | |
840 jsEngine.setWebRequest(new AndroidWebRequest(true)); // 'true' because we ne ed element hiding | |
841 return jsEngine; | |
842 } | |
843 | |
844 private void createFilterEngine() | |
845 { | |
846 w("Creating FilterEngine"); | |
847 jsEngine = buildJsEngine(getContext(), debugMode); | |
848 filterEngine = new FilterEngine(jsEngine); | |
849 d("FilterEngine created"); | |
850 } | |
851 | |
852 private String url; | |
853 private String domain; | |
854 private String injectJs; | |
855 | |
856 private void startAbpLoading(String newUrl) | |
857 { | |
858 d("Start loading " + newUrl); | |
859 | |
860 addDomListener = true; | |
861 elementsHidden = false; | |
862 loadError = null; | |
863 url = newUrl; | |
864 | |
865 try | |
866 { | |
867 domain = Utils.getDomain(url); | |
868 } | |
869 catch (URISyntaxException e) | |
870 { | |
871 domain = null; | |
872 e("Failed to extract domain for " + url); | |
873 } | |
874 | |
875 if (filterEngine == null) | |
876 { | |
877 createFilterEngine(); | |
878 disposeFilterEngine = true; | |
879 } | |
880 | |
881 getSettings().setJavaScriptEnabled(true); | |
882 buildInjectJs(); | |
883 } | |
884 | |
885 private void buildInjectJs() | |
886 { | |
887 try | |
888 { | |
889 injectJs = readScriptFile("inject.js").replace(HIDE_TOKEN, readScriptFile( "css.js")); | |
890 } | |
891 catch (IOException e) | |
892 { | |
893 e("Failed to read script", e); | |
894 } | |
895 } | |
896 | |
897 @Override | |
898 public void loadUrl(String url) | |
899 { | |
900 startAbpLoading(url); | |
901 super.loadUrl(url); | |
902 } | |
903 | |
904 @Override | |
905 public void loadUrl(String url, Map<String, String> additionalHttpHeaders) | |
906 { | |
907 startAbpLoading(url); | |
908 super.loadUrl(url, additionalHttpHeaders); | |
909 } | |
910 | |
911 @Override | |
912 public void loadData(String data, String mimeType, String encoding) | |
913 { | |
914 startAbpLoading(null); | |
915 super.loadData(data, mimeType, encoding); | |
916 } | |
917 | |
918 @Override | |
919 public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, | |
920 String historyUrl) | |
921 { | |
922 startAbpLoading(null); | |
923 super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); | |
924 } | |
925 | |
926 @Override | |
927 public void stopLoading() | |
928 { | |
929 stopAbpLoading(); | |
930 super.stopLoading(); | |
931 } | |
932 | |
933 private void stopAbpLoading() | |
934 { | |
935 stopPreventDrawing(); | |
936 } | |
937 | |
938 private volatile boolean elementsHidden = false; | |
939 | |
940 // warning: do not rename (used in injected JS by method name) | |
941 @JavascriptInterface | |
942 public void setElementsHidden(boolean value) { | |
943 // invoked with 'true' by JS callback when DOM is loaded | |
944 elementsHidden = value; | |
945 | |
946 // fired on worker thread, but needs to be invoked on main thread | |
947 if (value) { | |
948 // handler.post(allowDrawRunnable); | |
949 // should work, but it's not working: | |
950 // the user can see element visible even though it was hidden on dom event | |
951 | |
952 if (allowDrawDelay > 0) | |
953 { | |
954 d("Scheduled 'allow drawing' invocation in " + allowDrawDelay + " ms"); | |
955 } | |
956 handler.postDelayed(allowDrawRunnable, allowDrawDelay); | |
957 } | |
958 } | |
959 | |
960 // warning: do not rename (used in injected JS by method name) | |
961 @JavascriptInterface | |
962 public boolean isElementsHidden() { | |
963 return elementsHidden; | |
964 } | |
965 | |
966 @Override | |
967 public void onPause() | |
968 { | |
969 handler.removeCallbacks(allowDrawRunnable); | |
970 super.onPause(); | |
971 } | |
972 | |
973 public AdblockWebView(Context context) | |
974 { | |
975 super(context); | |
976 initAbp(); | |
977 } | |
978 | |
979 public AdblockWebView(Context context, AttributeSet attrs) | |
980 { | |
981 super(context, attrs); | |
982 initAbp(); | |
983 } | |
984 | |
985 public AdblockWebView(Context context, AttributeSet attrs, int defStyle) | |
986 { | |
987 super(context, attrs, defStyle); | |
988 initAbp(); | |
989 } | |
990 | |
991 // used to prevent user see flickering for elements to hide | |
992 // for some reason it's rendered even if element is hidden on 'dom ready' even t | |
993 private volatile boolean allowDraw = true; | |
994 | |
995 @Override | |
996 protected void onDraw(Canvas canvas) | |
997 { | |
998 if (allowDraw) | |
999 { | |
1000 super.onDraw(canvas); | |
1001 } | |
1002 else | |
1003 { | |
1004 w("Prevent drawing"); | |
1005 drawEmptyPage(canvas); | |
1006 } | |
1007 } | |
1008 | |
1009 private void drawEmptyPage(Canvas canvas) | |
1010 { | |
1011 // assuming default color is WHITE | |
1012 canvas.drawColor(Color.WHITE); | |
1013 } | |
1014 | |
1015 private Handler handler = new Handler(); | |
1016 | |
1017 protected void startPreventDrawing() | |
1018 { | |
1019 w("Start prevent drawing"); | |
1020 | |
1021 allowDraw = false; | |
1022 } | |
1023 | |
1024 protected void stopPreventDrawing() | |
1025 { | |
1026 d("Stop prevent drawing, invalidating"); | |
1027 | |
1028 allowDraw = true; | |
1029 invalidate(); | |
1030 } | |
1031 | |
1032 private Runnable allowDrawRunnable = new Runnable() { | |
1033 @Override | |
1034 public void run() { | |
1035 stopPreventDrawing(); | |
1036 } | |
1037 }; | |
1038 | |
1039 protected List<String> getElemhideSelectors(String url, String domain) | |
1040 { | |
1041 try | |
1042 { | |
1043 d("Requesting elemhide selectors from FilterEngine for " + domain); | |
1044 return filterEngine.getElementHidingSelectors(domain); | |
1045 } | |
1046 finally | |
1047 { | |
1048 d("Finished requesting elemhide selectors from FilterEngine"); | |
1049 } | |
1050 } | |
1051 | |
1052 private static final String EMPTY_ELEMHIDE_ARRAY = "[]"; | |
1053 | |
1054 // warning: do not rename (used in injected JS by method name) | |
1055 @JavascriptInterface | |
1056 public String getElemhideSelectors() | |
1057 { | |
1058 if (filterEngine == null) | |
1059 { | |
1060 w("FilterEngine already disposed"); | |
1061 return EMPTY_ELEMHIDE_ARRAY; | |
1062 } | |
1063 | |
1064 if ( | |
1065 filterEngine.isDocumentWhitelisted(url, null) || | |
1066 filterEngine.isElemhideWhitelisted(url, null) | |
1067 ) | |
1068 { | |
1069 d(url + " whitelisted"); | |
1070 return EMPTY_ELEMHIDE_ARRAY; | |
1071 } | |
1072 else | |
1073 { | |
1074 // url and domain are prepared in startAbpLoading(); | |
1075 List<String> selectors = getElemhideSelectors(url, domain); | |
1076 d(String.valueOf(selectors.size()) + " elemhide selectors returned for " + domain); | |
1077 return Utils.stringListToJsonArray(selectors); | |
1078 } | |
1079 } | |
1080 | |
1081 public void dispose() { | |
1082 removeJavascriptInterface(BRIDGE); | |
1083 | |
1084 if (disposeFilterEngine) | |
1085 { | |
1086 w("Disposing jsEngine"); | |
1087 jsEngine.dispose(); | |
1088 jsEngine = null; | |
1089 | |
1090 w("Disposing filterEngine"); | |
1091 filterEngine.dispose(); | |
1092 filterEngine = null; | |
1093 | |
1094 disposeFilterEngine = false; | |
1095 } | |
1096 } | |
1097 } | |
OLD | NEW |