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