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

Powered by Google App Engine
This is Rietveld