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