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

Powered by Google App Engine
This is Rietveld