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