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