Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: libadblockplus-android-webview/src/org/adblockplus/libadblockplus/android/webview/AdblockWebView.java

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

Powered by Google App Engine
This is Rietveld