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