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

Side by Side Diff: libadblockplus-android-webview/src/org/adblockplus/android/AdblockWebView.java

Issue 29351744: Issue 4399 - Add WebView inheritor with ad blocking (Closed)
Patch Set: added enabled/disabled property Created Oct. 10, 2016, 2:04 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
OLDNEW
(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 import org.adblockplus.libadblockplus.Subscription;
25
26 import android.content.Context;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.graphics.Bitmap;
30 import android.graphics.Canvas;
31 import android.graphics.Color;
32 import android.net.http.SslError;
33 import android.os.Build;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.View;
40 import android.webkit.ConsoleMessage;
41 import android.webkit.GeolocationPermissions;
42 import android.webkit.HttpAuthHandler;
43 import android.webkit.JsPromptResult;
44 import android.webkit.JsResult;
45 import android.webkit.SslErrorHandler;
46 import android.webkit.ValueCallback;
47 import android.webkit.WebChromeClient;
48 import android.webkit.WebResourceRequest; // makes android min version to be 21
49 import android.webkit.WebResourceResponse;
50 import android.webkit.WebStorage;
51 import android.webkit.WebView;
52 import android.webkit.JavascriptInterface; // makes android min version to be 17
53 import android.webkit.WebViewClient;
54
55 import java.io.IOException;
56 import java.net.URISyntaxException;
57 import java.util.Collections;
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Locale;
61 import java.util.Map;
62 import java.util.concurrent.CountDownLatch;
63 import java.util.concurrent.atomic.AtomicBoolean;
64 import java.util.regex.Pattern;
65
66 /**
67 * WebView with ad blocking
68 */
69 public class AdblockWebView extends WebView
70 {
71 private static final String TAG = Utils.getTag(AdblockWebView.class);
72
73 private volatile boolean addDomListener = true;
74
75 /**
76 * Warning: do not rename (used in injected JS by method name)
77 * @param value set if one need to set DOM listener
78 */
79 @JavascriptInterface
80 public void setAddDomListener(boolean value)
81 {
82 this.addDomListener = value;
83 }
84
85 @JavascriptInterface
86 public boolean getAddDomListener()
87 {
88 return addDomListener;
89 }
90
91 private boolean adBlockEnabled = true;
92
93 public boolean isAdBlockEnabled()
94 {
95 return adBlockEnabled;
96 }
97
98 private void applyAdblockEnabled()
99 {
100 super.setWebViewClient(adBlockEnabled ? intWebViewClient : extWebViewClient) ;
101 super.setWebChromeClient(adBlockEnabled ? intWebChromeClient : extWebChromeC lient);
102 }
103
104 public void setAdBlockEnabled(boolean adBlockEnabled)
105 {
106 this.adBlockEnabled = adBlockEnabled;
107 applyAdblockEnabled();
108 }
109
110 private WebChromeClient extWebChromeClient;
111
112 @Override
113 public void setWebChromeClient(WebChromeClient client)
114 {
115 extWebChromeClient = client;
116 applyAdblockEnabled();
117 }
118
119 private boolean debugMode;
120
121 public boolean isDebugMode()
122 {
123 return debugMode;
124 }
125
126 /**
127 * Set to true to see debug log output int AdblockWebView and JS console
128 * @param debugMode is debug mode
129 */
130 public void setDebugMode(boolean debugMode)
131 {
132 this.debugMode = debugMode;
133 }
134
135 private void d(String message)
136 {
137 if (debugMode)
138 {
139 Log.d(TAG, message);
140 }
141 }
142
143 private void w(String message)
144 {
145 if (debugMode)
146 {
147 Log.w(TAG, message);
148 }
149 }
150
151 private void e(String message, Throwable t)
152 {
153 Log.e(TAG, message, t);
154 }
155
156 private void e(String message)
157 {
158 Log.e(TAG, message);
159 }
160
161 private static final String BRIDGE_TOKEN = "{{BRIDGE}}";
162 private static final String DEBUG_TOKEN = "{{DEBUG}}";
163 private static final String HIDE_TOKEN = "{{HIDE}}";
164 private static final String BRIDGE = "jsBridge";
165
166 private String readScriptFile(String filename) throws IOException
167 {
168 return Utils
169 .readAssetAsString(getContext(), filename)
170 .replace(BRIDGE_TOKEN, BRIDGE)
171 .replace(DEBUG_TOKEN, (debugMode ? "" : "//"));
172 }
173
174 private void runScript(String script)
175 {
176 if (Build.VERSION.SDK_INT >= 19)
177 {
178 evaluateJavascript(script, null);
179 }
180 else
181 {
182 loadUrl("javascript:" + script);
183 }
184 }
185
186 private Subscription acceptableAdsSubscription;
187
188 private boolean acceptableAdsEnabled = true;
189
190 public boolean isAcceptableAdsEnabled()
191 {
192 return acceptableAdsEnabled;
193 }
194
195 /**
196 * Enable or disable Acceptable Ads
197 * @param enabled enabled
198 */
199 public void setAcceptableAdsEnabled(boolean enabled)
200 {
201 this.acceptableAdsEnabled = enabled;
202
203 if (filterEngine != null)
204 {
205 applyAcceptableAds();
206 }
207 }
208
209 private final static String EXCEPTIONS_URL = "subscriptions_exceptionsurl";
210
211 private void applyAcceptableAds()
212 {
213 if (acceptableAdsEnabled)
214 {
215 if (acceptableAdsSubscription == null)
216 {
217 String url = filterEngine.getPref(EXCEPTIONS_URL).toString();
218 if (url == null)
219 {
220 w("no AA subscription url");
221 return;
222 }
223
224 acceptableAdsSubscription = filterEngine.getSubscription(url);
225 acceptableAdsSubscription.addToList();
226 d("AA subscription added (" + url + ")");
227 }
228 }
229 else
230 {
231 if (acceptableAdsSubscription != null)
232 {
233 removeAcceptableAdsSubscription();
234 }
235 }
236 }
237
238 private void removeAcceptableAdsSubscription()
239 {
240 acceptableAdsSubscription.removeFromList();
241 acceptableAdsSubscription = null;
242 d("AA subscription removed");
243 }
244
245 private boolean disposeFilterEngine;
246
247 private JsEngine jsEngine;
248
249 public JsEngine getJsEngine()
250 {
251 return jsEngine;
252 }
253
254 private FilterEngine filterEngine;
255
256 public FilterEngine getFilterEngine()
257 {
258 return filterEngine;
259 }
260
261 private Integer loadError;
262
263 /**
264 * Set external filter engine. A new (internal) is created automatically if no t set
265 * Don't forget to invoke {@link #dispose()} if not using external filter engi ne
266 * @param newFilterEngine external filter engine
267 */
268 public void setFilterEngine(FilterEngine newFilterEngine)
269 {
270 if (filterEngine != null && newFilterEngine != null && newFilterEngine == fi lterEngine)
271 {
272 return;
273 }
274
275 if (filterEngine != null)
276 {
277 if (acceptableAdsEnabled && acceptableAdsSubscription != null)
278 {
279 removeAcceptableAdsSubscription();
280 }
281
282 if (disposeFilterEngine)
283 {
284 filterEngine.dispose();
285 }
286 }
287
288 filterEngine = newFilterEngine;
289 disposeFilterEngine = false;
290
291 if (filterEngine != null)
292 {
293 applyAcceptableAds();
294
295 if (jsEngine != null)
296 {
297 jsEngine.dispose();
298 }
299 }
300
301 jsEngine = null;
302 }
303
304 private WebChromeClient intWebChromeClient = new WebChromeClient()
305 {
306 @Override
307 public void onReceivedTitle(WebView view, String title)
308 {
309 if (extWebChromeClient != null)
310 {
311 extWebChromeClient.onReceivedTitle(view, title);
312 }
313 }
314
315 @Override
316 public void onReceivedIcon(WebView view, Bitmap icon)
317 {
318 if (extWebChromeClient != null)
319 {
320 extWebChromeClient.onReceivedIcon(view, icon);
321 }
322 }
323
324 @Override
325 public void onReceivedTouchIconUrl(WebView view, String url, boolean precomp osed)
326 {
327 if (extWebChromeClient != null)
328 {
329 extWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
330 }
331 }
332
333 @Override
334 public void onShowCustomView(View view, CustomViewCallback callback)
335 {
336 if (extWebChromeClient != null)
337 {
338 extWebChromeClient.onShowCustomView(view, callback);
339 }
340 }
341
342 @Override
343 public void onShowCustomView(View view, int requestedOrientation, CustomView Callback callback)
344 {
345 if (extWebChromeClient != null)
346 {
347 extWebChromeClient.onShowCustomView(view, requestedOrientation, callback );
348 }
349 }
350
351 @Override
352 public void onHideCustomView()
353 {
354 if (extWebChromeClient != null)
355 {
356 extWebChromeClient.onHideCustomView();
357 }
358 }
359
360 @Override
361 public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUser Gesture,
362 Message resultMsg)
363 {
364 if (extWebChromeClient != null)
365 {
366 return extWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
367 }
368 else
369 {
370 return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
371 }
372 }
373
374 @Override
375 public void onRequestFocus(WebView view)
376 {
377 if (extWebChromeClient != null)
378 {
379 extWebChromeClient.onRequestFocus(view);
380 }
381 }
382
383 @Override
384 public void onCloseWindow(WebView window)
385 {
386 if (extWebChromeClient != null)
387 {
388 extWebChromeClient.onCloseWindow(window);
389 }
390 }
391
392 @Override
393 public boolean onJsAlert(WebView view, String url, String message, JsResult result)
394 {
395 if (extWebChromeClient != null)
396 {
397 return extWebChromeClient.onJsAlert(view, url, message, result);
398 }
399 else
400 {
401 return super.onJsAlert(view, url, message, result);
402 }
403 }
404
405 @Override
406 public boolean onJsConfirm(WebView view, String url, String message, JsResul t result)
407 {
408 if (extWebChromeClient != null)
409 {
410 return extWebChromeClient.onJsConfirm(view, url, message, result);
411 }
412 else
413 {
414 return super.onJsConfirm(view, url, message, result);
415 }
416 }
417
418 @Override
419 public boolean onJsPrompt(WebView view, String url, String message, String d efaultValue,
420 JsPromptResult result)
421 {
422 if (extWebChromeClient != null)
423 {
424 return extWebChromeClient.onJsPrompt(view, url, message, defaultValue, r esult);
425 }
426 else
427 {
428 return super.onJsPrompt(view, url, message, defaultValue, result);
429 }
430 }
431
432 @Override
433 public boolean onJsBeforeUnload(WebView view, String url, String message, Js Result result)
434 {
435 if (extWebChromeClient != null)
436 {
437 return extWebChromeClient.onJsBeforeUnload(view, url, message, result);
438 }
439 else
440 {
441 return super.onJsBeforeUnload(view, url, message, result);
442 }
443 }
444
445 @Override
446 public void onExceededDatabaseQuota(String url, String databaseIdentifier, l ong quota,
447 long estimatedDatabaseSize, long totalQu ota,
448 WebStorage.QuotaUpdater quotaUpdater)
449 {
450 if (extWebChromeClient != null)
451 {
452 extWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quot a,
453 estimatedDatabaseSize, totalQuota, quotaUpdater);
454 }
455 else
456 {
457 super.onExceededDatabaseQuota(url, databaseIdentifier, quota,
458 estimatedDatabaseSize, totalQuota, quotaUpdater);
459 }
460 }
461
462 @Override
463 public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
464 WebStorage.QuotaUpdater quotaUpdater)
465 {
466 if (extWebChromeClient != null)
467 {
468 extWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quot aUpdater);
469 }
470 else
471 {
472 super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
473 }
474 }
475
476 @Override
477 public void onGeolocationPermissionsShowPrompt(String origin,
478 GeolocationPermissions.Callba ck callback)
479 {
480 if (extWebChromeClient != null)
481 {
482 extWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
483 }
484 else
485 {
486 super.onGeolocationPermissionsShowPrompt(origin, callback);
487 }
488 }
489
490 @Override
491 public void onGeolocationPermissionsHidePrompt()
492 {
493 if (extWebChromeClient != null)
494 {
495 extWebChromeClient.onGeolocationPermissionsHidePrompt();
496 }
497 else
498 {
499 super.onGeolocationPermissionsHidePrompt();
500 }
501 }
502
503 @Override
504 public boolean onJsTimeout()
505 {
506 if (extWebChromeClient != null)
507 {
508 return extWebChromeClient.onJsTimeout();
509 }
510 else
511 {
512 return super.onJsTimeout();
513 }
514 }
515
516 @Override
517 public void onConsoleMessage(String message, int lineNumber, String sourceID )
518 {
519 if (extWebChromeClient != null)
520 {
521 extWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
522 }
523 else
524 {
525 super.onConsoleMessage(message, lineNumber, sourceID);
526 }
527 }
528
529 @Override
530 public boolean onConsoleMessage(ConsoleMessage consoleMessage)
531 {
532 if (extWebChromeClient != null)
533 {
534 return extWebChromeClient.onConsoleMessage(consoleMessage);
535 }
536 else
537 {
538 return super.onConsoleMessage(consoleMessage);
539 }
540 }
541
542 @Override
543 public Bitmap getDefaultVideoPoster()
544 {
545 if (extWebChromeClient != null)
546 {
547 return extWebChromeClient.getDefaultVideoPoster();
548 }
549 else
550 {
551 return super.getDefaultVideoPoster();
552 }
553 }
554
555 @Override
556 public View getVideoLoadingProgressView()
557 {
558 if (extWebChromeClient != null)
559 {
560 return extWebChromeClient.getVideoLoadingProgressView();
561 }
562 else
563 {
564 return super.getVideoLoadingProgressView();
565 }
566 }
567
568 @Override
569 public void getVisitedHistory(ValueCallback<String[]> callback)
570 {
571 if (extWebChromeClient != null)
572 {
573 extWebChromeClient.getVisitedHistory(callback);
574 }
575 else
576 {
577 super.getVisitedHistory(callback);
578 }
579 }
580
581 @Override
582 public void onProgressChanged(WebView view, int newProgress)
583 {
584 d("Loading progress=" + newProgress + "%");
585
586 // addDomListener is changed to 'false' in `setAddDomListener` invoked fro m injected JS
587 if (getAddDomListener() && loadError == null && injectJs != null)
588 {
589 d("Injecting script");
590 runScript(injectJs);
591
592 if (allowDraw && loading)
593 {
594 startPreventDrawing();
595 }
596 }
597
598 if (extWebChromeClient != null)
599 {
600 extWebChromeClient.onProgressChanged(view, newProgress);
601 }
602 }
603 };
604
605 /**
606 * Default (in some conditions) start redraw delay after DOM modified with inj ected JS (millis)
607 */
608 public static final int ALLOW_DRAW_DELAY = 200;
609 /*
610 The value could be different for devices and completely unclear why we need it and
611 how to measure actual value
612 */
613
614 private int allowDrawDelay = ALLOW_DRAW_DELAY;
615
616 public int getAllowDrawDelay()
617 {
618 return allowDrawDelay;
619 }
620
621 /**
622 * Set start redraw delay after DOM modified with injected JS
623 * (used to prevent flickering after 'DOM ready')
624 * @param allowDrawDelay delay (in millis)
625 */
626 public void setAllowDrawDelay(int allowDrawDelay)
627 {
628 if (allowDrawDelay < 0)
629 throw new IllegalArgumentException("Negative value is not allowed");
630
631 this.allowDrawDelay = allowDrawDelay;
632 }
633
634 private WebViewClient extWebViewClient;
635
636 @Override
637 public void setWebViewClient(WebViewClient client)
638 {
639 extWebViewClient = client;
640 applyAdblockEnabled();
641 }
642
643 private static final Pattern RE_JS = Pattern.compile("\\.js$", Pattern.CASE_IN SENSITIVE);
644 private static final Pattern RE_CSS = Pattern.compile("\\.css$", Pattern.CASE_ INSENSITIVE);
645 private static final Pattern RE_IMAGE = Pattern.compile("\\.(?:gif|png|jpe?g|b mp|ico)$", Pattern.CASE_INSENSITIVE);
646 private static final Pattern RE_FONT = Pattern.compile("\\.(?:ttf|woff)$", Pat tern.CASE_INSENSITIVE);
647 private static final Pattern RE_HTML = Pattern.compile("\\.html?$", Pattern.CA SE_INSENSITIVE);
648
649 private WebViewClient intWebViewClient;
650
651 /**
652 * WebViewClient for API pre 21
653 * (does not have Referers information)
654 */
655 class AdblockWebViewClient extends WebViewClient
656 {
657 @Override
658 public boolean shouldOverrideUrlLoading(WebView view, String url)
659 {
660 if (extWebViewClient != null)
661 {
662 return extWebViewClient.shouldOverrideUrlLoading(view, url);
663 }
664 else
665 {
666 return super.shouldOverrideUrlLoading(view, url);
667 }
668 }
669
670 @Override
671 public void onPageStarted(WebView view, String url, Bitmap favicon)
672 {
673 if (loading)
674 {
675 stopAbpLoading();
676 }
677
678 startAbpLoading(url);
679
680 if (extWebViewClient != null)
681 {
682 extWebViewClient.onPageStarted(view, url, favicon);
683 }
684 else
685 {
686 super.onPageStarted(view, url, favicon);
687 }
688 }
689
690 @Override
691 public void onPageFinished(WebView view, String url)
692 {
693 loading = false;
694 if (extWebViewClient != null)
695 {
696 extWebViewClient.onPageFinished(view, url);
697 }
698 else
699 {
700 super.onPageFinished(view, url);
701 }
702 }
703
704 @Override
705 public void onLoadResource(WebView view, String url)
706 {
707 if (extWebViewClient != null)
708 {
709 extWebViewClient.onLoadResource(view, url);
710 }
711 else
712 {
713 super.onLoadResource(view, url);
714 }
715 }
716
717 @Override
718 public void onTooManyRedirects(WebView view, Message cancelMsg, Message cont inueMsg)
719 {
720 if (extWebViewClient != null)
721 {
722 extWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg);
723 }
724 else
725 {
726 super.onTooManyRedirects(view, cancelMsg, continueMsg);
727 }
728 }
729
730 @Override
731 public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
732 {
733 e("Load error:" +
734 " code=" + errorCode +
735 " with description=" + description +
736 " for url=" + failingUrl);
737 loadError = errorCode;
738
739 stopAbpLoading();
740
741 if (extWebViewClient != null)
742 {
743 extWebViewClient.onReceivedError(view, errorCode, description, failingUr l);
744 }
745 else
746 {
747 super.onReceivedError(view, errorCode, description, failingUrl);
748 }
749 }
750
751 @Override
752 public void onFormResubmission(WebView view, Message dontResend, Message res end)
753 {
754 if (extWebViewClient != null)
755 {
756 extWebViewClient.onFormResubmission(view, dontResend, resend);
757 }
758 else
759 {
760 super.onFormResubmission(view, dontResend, resend);
761 }
762 }
763
764 @Override
765 public void doUpdateVisitedHistory(WebView view, String url, boolean isReloa d)
766 {
767 if (extWebViewClient != null)
768 {
769 extWebViewClient.doUpdateVisitedHistory(view, url, isReload);
770 }
771 else
772 {
773 super.doUpdateVisitedHistory(view, url, isReload);
774 }
775 }
776
777 @Override
778 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslErr or error)
779 {
780 if (extWebViewClient != null)
781 {
782 extWebViewClient.onReceivedSslError(view, handler, error);
783 }
784 else
785 {
786 super.onReceivedSslError(view, handler, error);
787 }
788 }
789
790 @Override
791 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
792 {
793 if (extWebViewClient != null)
794 {
795 extWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm);
796 }
797 else
798 {
799 super.onReceivedHttpAuthRequest(view, handler, host, realm);
800 }
801 }
802
803 @Override
804 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event)
805 {
806 if (extWebViewClient != null)
807 {
808 return extWebViewClient.shouldOverrideKeyEvent(view, event);
809 }
810 else
811 {
812 return super.shouldOverrideKeyEvent(view, event);
813 }
814 }
815
816 @Override
817 public void onUnhandledKeyEvent(WebView view, KeyEvent event)
818 {
819 if (extWebViewClient != null)
820 {
821 extWebViewClient.onUnhandledKeyEvent(view, event);
822 }
823 else
824 {
825 super.onUnhandledKeyEvent(view, event);
826 }
827 }
828
829 @Override
830 public void onScaleChanged(WebView view, float oldScale, float newScale)
831 {
832 if (extWebViewClient != null)
833 {
834 extWebViewClient.onScaleChanged(view, oldScale, newScale);
835 }
836 else
837 {
838 super.onScaleChanged(view, oldScale, newScale);
839 }
840 }
841
842 @Override
843 public void onReceivedLoginRequest(WebView view, String realm, String accoun t, String args)
844 {
845 if (extWebViewClient != null)
846 {
847 extWebViewClient.onReceivedLoginRequest(view, realm, account, args);
848 }
849 else
850 {
851 super.onReceivedLoginRequest(view, realm, account, args);
852 }
853 }
854
855 @Override
856 public WebResourceResponse shouldInterceptRequest(WebView view, String url)
857 {
858 // if dispose() was invoke, but the page is still loading then just let it go
859 if (filterEngine == null)
860 {
861 e("FilterEngine already disposed");
862 return null;
863 }
864
865 // Determine the content
866 FilterEngine.ContentType contentType = null;
867 if (RE_JS.matcher(url).find())
868 {
869 contentType = FilterEngine.ContentType.SCRIPT;
870 }
871 else if (RE_CSS.matcher(url).find())
872 {
873 contentType = FilterEngine.ContentType.STYLESHEET;
874 }
875 else if (RE_IMAGE.matcher(url).find())
876 {
877 contentType = FilterEngine.ContentType.IMAGE;
878 }
879 else if (RE_FONT.matcher(url).find())
880 {
881 contentType = FilterEngine.ContentType.FONT;
882 }
883 else if (RE_HTML.matcher(url).find())
884 {
885 contentType = FilterEngine.ContentType.SUBDOCUMENT;
886 }
887 else
888 {
889 contentType = FilterEngine.ContentType.OTHER;
890 }
891 // Check if we should block ... we sadly do not have the referrer chain he re,
892 // might also be hard to get as we do not have HTTP headers here
893 Filter filter = filterEngine.matches(url, contentType, new String[0]);
894 if (filter != null && filter.getType().equals(Filter.Type.BLOCKING))
895 {
896 w("Blocked loading " + url);
897 // If we should block, return empty response which results in a 404
898 return new WebResourceResponse("text/plain", "UTF-8", null);
899 }
900
901 d("Allowed loading " + url);
902
903 // Otherwise, continue by returning null
904 return null;
905 }
906 }
907
908 private void clearReferers()
909 {
910 d("Clearing referers");
911 url2Referer.clear();
912 }
913
914 /**
915 * WebViewClient for API 21 and newer
916 * (has Referer since it overrides `shouldInterceptRequest(..., request)` with referer)
917 */
918 class AdblockWebViewClient21 extends AdblockWebViewClient
919 {
920 @Override
921 // makes android min version to be 21
922 public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceR equest request)
923 {
924 // here we just trying to fill url -> referer map
925 // blocking/allowing loading will happen in `shouldInterceptRequest(WebVie w,String)`
926
927 String url = request.getUrl().toString();
928 String referer = request.getRequestHeaders().get("Referer");
929
930 if (referer != null)
931 {
932 d("Remember referer " + referer + " for " + url);
933 url2Referer.put(url, referer);
934 }
935 else
936 {
937 w("No referer for " + url);
938
939 if (request.isForMainFrame() && AdblockWebView.this.url != null)
940 {
941 d("Request is for main frame, adding current url " + AdblockWebView.th is.url + " as referer");
942 url2Referer.put(url, AdblockWebView.this.url);
943 }
944 }
945
946 return super.shouldInterceptRequest(view, request);
947 }
948 }
949
950 private Map<String, String> url2Referer = Collections.synchronizedMap(new Hash Map<String, String>());
951
952 private void initAbp()
953 {
954 addJavascriptInterface(this, BRIDGE);
955 initClients();
956 }
957
958 private void initClients()
959 {
960 if (Build.VERSION.SDK_INT >= 21)
961 {
962 intWebViewClient = new AdblockWebViewClient21();
963 }
964 else
965 {
966 intWebViewClient = new AdblockWebViewClient();
967 }
968 applyAdblockEnabled();
969 }
970
971 /**
972 * Build app info using Android package information
973 * @param context context
974 * @param developmentBuild if it's dev build
975 * @return app info required to build JsEngine
976 */
977 public static AppInfo buildAppInfo(final Context context, boolean developmentB uild)
978 {
979 String version = "0";
980 try
981 {
982 final PackageInfo info = context.getPackageManager().getPackageInfo(contex t.getPackageName(), 0);
983 version = info.versionName;
984 if (developmentBuild)
985 version += "." + info.versionCode;
986 }
987 catch (final PackageManager.NameNotFoundException e)
988 {
989 Log.e(TAG, "Failed to get the application version number", e);
990 }
991 final String sdkVersion = String.valueOf(Build.VERSION.SDK_INT);
992 final String locale = Locale.getDefault().toString().replace('_', '-');
993
994 return AppInfo.builder()
995 .setVersion(version)
996 .setApplicationVersion(sdkVersion)
997 .setLocale(locale)
998 .setDevelopmentBuild(developmentBuild)
999 .build();
1000 }
1001
1002 /**
1003 * Build JsEngine required to build FilterEngine
1004 * @param context context
1005 * @param developmentBuild if it's dev build
1006 * @return JsEngine
1007 */
1008 public static JsEngine buildJsEngine(Context context, boolean developmentBuild )
1009 {
1010 JsEngine jsEngine = new JsEngine(buildAppInfo(context, developmentBuild));
1011 jsEngine.setDefaultFileSystem(context.getCacheDir().getAbsolutePath());
1012 jsEngine.setWebRequest(new AndroidWebRequest(true)); // 'true' because we ne ed element hiding
1013 return jsEngine;
1014 }
1015
1016 private void createFilterEngine()
1017 {
1018 w("Creating FilterEngine");
1019 jsEngine = buildJsEngine(getContext(), debugMode);
1020 filterEngine = new FilterEngine(jsEngine);
1021 applyAcceptableAds();
1022 d("FilterEngine created");
1023 }
1024
1025 private String url;
1026 private String domain;
1027 private String injectJs;
1028 private CountDownLatch elemHideLatch;
1029 private String elemHideSelectorsString;
1030 private Object elemHideThreadLockObject = new Object();
1031 private ElemHideThread elemHideThread;
1032
1033 private class ElemHideThread extends Thread
1034 {
1035 private String selectorsString;
1036 private CountDownLatch finishedLatch;
1037 private AtomicBoolean isCancelled;
1038
1039 public ElemHideThread(CountDownLatch finishedLatch)
1040 {
1041 this.finishedLatch = finishedLatch;
1042 isCancelled = new AtomicBoolean(false);
1043 }
1044
1045 private String[] EMPTY_ARRAY = new String[] {};
1046
1047 @Override
1048 public void run()
1049 {
1050 try
1051 {
1052 if (filterEngine == null)
1053 {
1054 w("FilterEngine already disposed");
1055 selectorsString = EMPTY_ELEMHIDE_ARRAY_STRING;
1056 }
1057 else
1058 {
1059
1060 String[] referers = EMPTY_ARRAY;
1061 if (url != null)
1062 {
1063 String referer = url2Referer.get(url);
1064 if (referer != null)
1065 {
1066 referers = new String[] { referer };
1067 }
1068 }
1069
1070 d("Check whitelisting for " + url + " with " +
1071 (referers.length > 0
1072 ? "referer " + referers[0]
1073 : "no referer"));
1074
1075 if (filterEngine.isDocumentWhitelisted(url, referers) ||
1076 filterEngine.isElemhideWhitelisted(url, referers))
1077 {
1078 w("Whitelisted " + url);
1079 selectorsString = EMPTY_ELEMHIDE_ARRAY_STRING;
1080 }
1081 else
1082 {
1083 d("Listed subscriptions: " + filterEngine.getListedSubscriptions().s ize());
1084
1085 d("Requesting elemhide selectors from FilterEngine for " + domain);
1086 List<String> selectors = filterEngine.getElementHidingSelectors(doma in);
1087 d("Finished requesting elemhide selectors, got " + selectors.size()) ;
1088 selectorsString = Utils.stringListToJsonArray(selectors);
1089 }
1090 }
1091 }
1092 finally
1093 {
1094 if (!isCancelled.get())
1095 {
1096 finish(selectorsString);
1097 }
1098 else
1099 {
1100 w("This thread is cancelled, exiting silently " + this);
1101 }
1102 }
1103 }
1104
1105 private void onFinished()
1106 {
1107 finishedLatch.countDown();
1108 synchronized (finishedRunnableLockObject)
1109 {
1110 if (finishedRunnable != null)
1111 {
1112 finishedRunnable.run();
1113 }
1114 }
1115 }
1116
1117 private void finish(String result)
1118 {
1119 d("Setting elemhide string " + result.length() + " bytes");
1120 elemHideSelectorsString = result;
1121 onFinished();
1122 }
1123
1124 private Object finishedRunnableLockObject = new Object();
1125 private Runnable finishedRunnable;
1126
1127 public void setFinishedRunnable(Runnable runnable)
1128 {
1129 synchronized (finishedRunnableLockObject)
1130 {
1131 this.finishedRunnable = runnable;
1132 }
1133 }
1134
1135 public void cancel()
1136 {
1137 w("Cancelling elemhide thread " + this);
1138 isCancelled.set(true);
1139
1140 finish(EMPTY_ELEMHIDE_ARRAY_STRING);
1141 }
1142 }
1143
1144 private Runnable elemHideThreadFinishedRunnable = new Runnable()
1145 {
1146 @Override
1147 public void run()
1148 {
1149 synchronized (elemHideThreadLockObject)
1150 {
1151 w("elemHideThread set to null");
1152 elemHideThread = null;
1153 }
1154 }
1155 };
1156
1157 private boolean loading;
1158
1159 private void initAbpLoading()
1160 {
1161 getSettings().setJavaScriptEnabled(true);
1162 buildInjectJs();
1163
1164 if (filterEngine == null)
1165 {
1166 createFilterEngine();
1167 disposeFilterEngine = true;
1168 }
1169 }
1170
1171 private void startAbpLoading(String newUrl)
1172 {
1173 d("Start loading " + newUrl);
1174
1175 loading = true;
1176 addDomListener = true;
1177 elementsHidden = false;
1178 loadError = null;
1179 url = newUrl;
1180
1181 if (url != null)
1182 {
1183 try
1184 {
1185 domain = Utils.getDomain(url);
1186 }
1187 catch (URISyntaxException e)
1188 {
1189 domain = null;
1190 e("Failed to extract domain for " + url);
1191 }
1192
1193 elemHideLatch = new CountDownLatch(1);
1194 elemHideThread = new ElemHideThread(elemHideLatch);
1195 elemHideThread.setFinishedRunnable(elemHideThreadFinishedRunnable);
1196 elemHideThread.start();
1197 }
1198 else
1199 {
1200 elemHideLatch = null;
1201 }
1202 }
1203
1204 private void buildInjectJs()
1205 {
1206 try
1207 {
1208 if (injectJs == null)
1209 {
1210 injectJs = readScriptFile("inject.js").replace(HIDE_TOKEN, readScriptFil e("css.js"));
1211 }
1212 }
1213 catch (IOException e)
1214 {
1215 e("Failed to read script", e);
1216 }
1217 }
1218
1219 @Override
1220 public void goBack()
1221 {
1222 if (loading)
1223 {
1224 stopAbpLoading();
1225 }
1226
1227 super.goBack();
1228 }
1229
1230 @Override
1231 public void goForward()
1232 {
1233 if (loading)
1234 {
1235 stopAbpLoading();
1236 }
1237
1238 super.goForward();
1239 }
1240
1241 @Override
1242 public void loadUrl(String url)
1243 {
1244 initAbpLoading();
1245
1246 if (loading)
1247 {
1248 stopAbpLoading();
1249 }
1250
1251 super.loadUrl(url);
1252 }
1253
1254 @Override
1255 public void loadUrl(String url, Map<String, String> additionalHttpHeaders)
1256 {
1257 initAbpLoading();
1258
1259 if (loading)
1260 {
1261 stopAbpLoading();
1262 }
1263
1264 super.loadUrl(url, additionalHttpHeaders);
1265 }
1266
1267 @Override
1268 public void loadData(String data, String mimeType, String encoding)
1269 {
1270 initAbpLoading();
1271
1272 if (loading)
1273 {
1274 stopAbpLoading();
1275 }
1276
1277 super.loadData(data, mimeType, encoding);
1278 }
1279
1280 @Override
1281 public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding,
1282 String historyUrl)
1283 {
1284 initAbpLoading();
1285
1286 if (loading)
1287 {
1288 stopAbpLoading();
1289 }
1290
1291 super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
1292 }
1293
1294 @Override
1295 public void stopLoading()
1296 {
1297 stopAbpLoading();
1298 super.stopLoading();
1299 }
1300
1301 private void stopAbpLoading()
1302 {
1303 d("Stop abp loading");
1304
1305 loading = false;
1306 stopPreventDrawing();
1307 clearReferers();
1308
1309 synchronized (elemHideThreadLockObject)
1310 {
1311 if (elemHideThread != null)
1312 {
1313 elemHideThread.cancel();
1314 }
1315 }
1316 }
1317
1318 private volatile boolean elementsHidden = false;
1319
1320 // warning: do not rename (used in injected JS by method name)
1321 @JavascriptInterface
1322 public void setElementsHidden(boolean value)
1323 {
1324 // invoked with 'true' by JS callback when DOM is loaded
1325 elementsHidden = value;
1326
1327 // fired on worker thread, but needs to be invoked on main thread
1328 if (value)
1329 {
1330 // handler.post(allowDrawRunnable);
1331 // should work, but it's not working:
1332 // the user can see element visible even though it was hidden on dom event
1333
1334 if (allowDrawDelay > 0)
1335 {
1336 d("Scheduled 'allow drawing' invocation in " + allowDrawDelay + " ms");
1337 }
1338 handler.postDelayed(allowDrawRunnable, allowDrawDelay);
1339 }
1340 }
1341
1342 // warning: do not rename (used in injected JS by method name)
1343 @JavascriptInterface
1344 public boolean isElementsHidden()
1345 {
1346 return elementsHidden;
1347 }
1348
1349 @Override
1350 public void onPause()
1351 {
1352 handler.removeCallbacks(allowDrawRunnable);
1353 super.onPause();
1354 }
1355
1356 public AdblockWebView(Context context)
1357 {
1358 super(context);
1359 initAbp();
1360 }
1361
1362 public AdblockWebView(Context context, AttributeSet attrs)
1363 {
1364 super(context, attrs);
1365 initAbp();
1366 }
1367
1368 public AdblockWebView(Context context, AttributeSet attrs, int defStyle)
1369 {
1370 super(context, attrs, defStyle);
1371 initAbp();
1372 }
1373
1374 // used to prevent user see flickering for elements to hide
1375 // for some reason it's rendered even if element is hidden on 'dom ready' even t
1376 private volatile boolean allowDraw = true;
1377
1378 @Override
1379 protected void onDraw(Canvas canvas)
1380 {
1381 if (allowDraw)
1382 {
1383 super.onDraw(canvas);
1384 }
1385 else
1386 {
1387 w("Prevent drawing");
1388 drawEmptyPage(canvas);
1389 }
1390 }
1391
1392 private void drawEmptyPage(Canvas canvas)
1393 {
1394 // assuming default color is WHITE
1395 canvas.drawColor(Color.WHITE);
1396 }
1397
1398 private Handler handler = new Handler();
1399
1400 protected void startPreventDrawing()
1401 {
1402 w("Start prevent drawing");
1403
1404 allowDraw = false;
1405 }
1406
1407 protected void stopPreventDrawing()
1408 {
1409 d("Stop prevent drawing, invalidating");
1410
1411 allowDraw = true;
1412 invalidate();
1413 }
1414
1415 private Runnable allowDrawRunnable = new Runnable()
1416 {
1417 @Override
1418 public void run()
1419 {
1420 stopPreventDrawing();
1421 }
1422 };
1423
1424 private static final String EMPTY_ELEMHIDE_ARRAY_STRING = "[]";
1425
1426 // warning: do not rename (used in injected JS by method name)
1427 @JavascriptInterface
1428 public String getElemhideSelectors()
1429 {
1430 if (elemHideLatch == null)
1431 {
1432 return EMPTY_ELEMHIDE_ARRAY_STRING;
1433 }
1434 else
1435 {
1436 try
1437 {
1438 // elemhide selectors list getting is started in startAbpLoad() in backg round thread
1439 d("Waiting for elemhide selectors to be ready");
1440 elemHideLatch.await();
1441 d("Elemhide selectors ready, " + elemHideSelectorsString.length() + " by tes");
1442
1443 clearReferers();
1444
1445 return elemHideSelectorsString;
1446 }
1447 catch (InterruptedException e)
1448 {
1449 w("Interrupted, returning empty selectors list");
1450 return EMPTY_ELEMHIDE_ARRAY_STRING;
1451 }
1452 }
1453 }
1454
1455 private void doDispose()
1456 {
1457 w("Disposing jsEngine");
1458 jsEngine.dispose();
1459 jsEngine = null;
1460
1461 w("Disposing filterEngine");
1462 filterEngine.dispose();
1463 filterEngine = null;
1464
1465 disposeFilterEngine = false;
1466 }
1467
1468 public void dispose()
1469 {
1470 d("Dispose invoked");
1471
1472 removeJavascriptInterface(BRIDGE);
1473
1474 if (disposeFilterEngine)
1475 {
1476 synchronized (elemHideThreadLockObject)
1477 {
1478 if (elemHideThread != null)
1479 {
1480 w("Busy with elemhide selectors, delayed disposing scheduled");
1481 elemHideThread.setFinishedRunnable(new Runnable()
1482 {
1483 @Override
1484 public void run()
1485 {
1486 doDispose();
1487 }
1488 });
1489 }
1490 else
1491 {
1492 doDispose();
1493 }
1494 }
1495 }
1496 }
1497 }
OLDNEW
« no previous file with comments | « libadblockplus-android-webview/project.properties ('k') | libadblockplus-android-webviewapp/AndroidManifest.xml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld