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

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

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

Powered by Google App Engine
This is Rietveld