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

Powered by Google App Engine
This is Rietveld