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