Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.webkit;
     18 
     19 import android.os.Handler;
     20 import android.os.Message;
     21 import android.util.Log;
     22 
     23 import java.util.Collection;
     24 import java.util.Map;
     25 import java.util.HashMap;
     26 import java.util.HashSet;
     27 import java.util.Iterator;
     28 import java.util.Set;
     29 
     30 /**
     31  * Functionality for manipulating the webstorage databases.
     32  */
     33 public final class WebStorage {
     34 
     35     /**
     36      * Encapsulates a callback function to be executed when a new quota is made
     37      * available. We primarily want this to allow us to call back the sleeping
     38      * WebCore thread from outside the WebViewCore class (as the native call
     39      * is private). It is imperative that this the setDatabaseQuota method is
     40      * executed once a decision to either allow or deny new quota is made,
     41      * otherwise the WebCore thread will remain asleep.
     42      */
     43     public interface QuotaUpdater {
     44         public void updateQuota(long newQuota);
     45     };
     46 
     47     // Log tag
     48     private static final String TAG = "webstorage";
     49 
     50     // Global instance of a WebStorage
     51     private static WebStorage sWebStorage;
     52 
     53     // Message ids
     54     static final int UPDATE = 0;
     55     static final int SET_QUOTA_ORIGIN = 1;
     56     static final int DELETE_ORIGIN = 2;
     57     static final int DELETE_ALL = 3;
     58     static final int GET_ORIGINS = 4;
     59     static final int GET_USAGE_ORIGIN = 5;
     60     static final int GET_QUOTA_ORIGIN = 6;
     61 
     62     // Message ids on the UI thread
     63     static final int RETURN_ORIGINS = 0;
     64     static final int RETURN_USAGE_ORIGIN = 1;
     65     static final int RETURN_QUOTA_ORIGIN = 2;
     66 
     67     private static final String ORIGINS = "origins";
     68     private static final String ORIGIN = "origin";
     69     private static final String CALLBACK = "callback";
     70     private static final String USAGE = "usage";
     71     private static final String QUOTA = "quota";
     72 
     73     private Map <String, Origin> mOrigins;
     74 
     75     private Handler mHandler = null;
     76     private Handler mUIHandler = null;
     77 
     78     /**
     79      * Class containing the HTML5 database quota and usage for an origin.
     80      */
     81     public static class Origin {
     82         private String mOrigin = null;
     83         private long mQuota = 0;
     84         private long mUsage = 0;
     85 
     86         private Origin(String origin, long quota, long usage) {
     87             mOrigin = origin;
     88             mQuota = quota;
     89             mUsage = usage;
     90         }
     91 
     92         private Origin(String origin, long quota) {
     93             mOrigin = origin;
     94             mQuota = quota;
     95         }
     96 
     97         private Origin(String origin) {
     98             mOrigin = origin;
     99         }
    100 
    101         /**
    102          * An origin string is created using WebCore::SecurityOrigin::toString().
    103          * Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
    104          * the port if the port is the default for the protocol. Eg
    105          * http://www.google.com and http://www.google.com:80 both record a port
    106          * of 0 and hence toString() == 'http://www.google.com' for both.
    107          * @return The origin string.
    108          */
    109         public String getOrigin() {
    110             return mOrigin;
    111         }
    112 
    113         /**
    114          * Returns the quota for this origin's HTML5 database.
    115          * @return The quota in bytes.
    116          */
    117         public long getQuota() {
    118             return mQuota;
    119         }
    120 
    121         /**
    122          * Returns the usage for this origin's HTML5 database.
    123          * @return The usage in bytes.
    124          */
    125         public long getUsage() {
    126             return mUsage;
    127         }
    128     }
    129 
    130     /**
    131      * @hide
    132      * Message handler, UI side
    133      */
    134     public void createUIHandler() {
    135         if (mUIHandler == null) {
    136             mUIHandler = new Handler() {
    137                 @Override
    138                 public void handleMessage(Message msg) {
    139                     switch (msg.what) {
    140                         case RETURN_ORIGINS: {
    141                             Map values = (Map) msg.obj;
    142                             Map origins = (Map) values.get(ORIGINS);
    143                             ValueCallback<Map> callback = (ValueCallback<Map>) values.get(CALLBACK);
    144                             callback.onReceiveValue(origins);
    145                             } break;
    146 
    147                         case RETURN_USAGE_ORIGIN: {
    148                             Map values = (Map) msg.obj;
    149                             ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
    150                             callback.onReceiveValue((Long)values.get(USAGE));
    151                             } break;
    152 
    153                         case RETURN_QUOTA_ORIGIN: {
    154                             Map values = (Map) msg.obj;
    155                             ValueCallback<Long> callback = (ValueCallback<Long>) values.get(CALLBACK);
    156                             callback.onReceiveValue((Long)values.get(QUOTA));
    157                             } break;
    158                     }
    159                 }
    160             };
    161         }
    162     }
    163 
    164     /**
    165      * @hide
    166      * Message handler, webcore side
    167      */
    168     public synchronized void createHandler() {
    169         if (mHandler == null) {
    170             mHandler = new Handler() {
    171                 @Override
    172                 public void handleMessage(Message msg) {
    173                     switch (msg.what) {
    174                         case SET_QUOTA_ORIGIN: {
    175                             Origin website = (Origin) msg.obj;
    176                             nativeSetQuotaForOrigin(website.getOrigin(),
    177                                                     website.getQuota());
    178                             } break;
    179 
    180                         case DELETE_ORIGIN: {
    181                             Origin website = (Origin) msg.obj;
    182                             nativeDeleteOrigin(website.getOrigin());
    183                             } break;
    184 
    185                         case DELETE_ALL:
    186                             nativeDeleteAllData();
    187                             break;
    188 
    189                         case GET_ORIGINS: {
    190                             syncValues();
    191                             ValueCallback callback = (ValueCallback) msg.obj;
    192                             Map origins = new HashMap(mOrigins);
    193                             Map values = new HashMap<String, Object>();
    194                             values.put(CALLBACK, callback);
    195                             values.put(ORIGINS, origins);
    196                             postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
    197                             } break;
    198 
    199                         case GET_USAGE_ORIGIN: {
    200                             syncValues();
    201                             Map values = (Map) msg.obj;
    202                             String origin = (String) values.get(ORIGIN);
    203                             ValueCallback callback = (ValueCallback) values.get(CALLBACK);
    204                             Origin website = mOrigins.get(origin);
    205                             Map retValues = new HashMap<String, Object>();
    206                             retValues.put(CALLBACK, callback);
    207                             if (website != null) {
    208                                 long usage = website.getUsage();
    209                                 retValues.put(USAGE, new Long(usage));
    210                             }
    211                             postUIMessage(Message.obtain(null, RETURN_USAGE_ORIGIN, retValues));
    212                             } break;
    213 
    214                         case GET_QUOTA_ORIGIN: {
    215                             syncValues();
    216                             Map values = (Map) msg.obj;
    217                             String origin = (String) values.get(ORIGIN);
    218                             ValueCallback callback = (ValueCallback) values.get(CALLBACK);
    219                             Origin website = mOrigins.get(origin);
    220                             Map retValues = new HashMap<String, Object>();
    221                             retValues.put(CALLBACK, callback);
    222                             if (website != null) {
    223                                 long quota = website.getQuota();
    224                                 retValues.put(QUOTA, new Long(quota));
    225                             }
    226                             postUIMessage(Message.obtain(null, RETURN_QUOTA_ORIGIN, retValues));
    227                             } break;
    228 
    229                         case UPDATE:
    230                             syncValues();
    231                             break;
    232                     }
    233                 }
    234             };
    235         }
    236     }
    237 
    238     /*
    239      * When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
    240      * we need to get the values from webcore, but we cannot block while doing so
    241      * as we used to do, as this could result in a full deadlock (other webcore
    242      * messages received while we are still blocked here, see http://b/2127737).
    243      *
    244      * We have to do everything asynchronously, by providing a callback function.
    245      * We post a message on the webcore thread (mHandler) that will get the result
    246      * from webcore, and we post it back on the UI thread (using mUIHandler).
    247      * We can then use the callback function to return the value.
    248      */
    249 
    250     /**
    251      * Returns a list of origins having a database. The Map is of type
    252      * Map<String, Origin>.
    253      */
    254     public void getOrigins(ValueCallback<Map> callback) {
    255         if (callback != null) {
    256             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    257                 syncValues();
    258                 callback.onReceiveValue(mOrigins);
    259             } else {
    260                 postMessage(Message.obtain(null, GET_ORIGINS, callback));
    261             }
    262         }
    263     }
    264 
    265     /**
    266      * Returns a list of origins having a database
    267      * should only be called from WebViewCore.
    268      */
    269     Collection<Origin> getOriginsSync() {
    270         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    271             update();
    272             return mOrigins.values();
    273         }
    274         return null;
    275     }
    276 
    277     /**
    278      * Returns the use for a given origin
    279      */
    280     public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
    281         if (callback == null) {
    282             return;
    283         }
    284         if (origin == null) {
    285             callback.onReceiveValue(null);
    286             return;
    287         }
    288         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    289             syncValues();
    290             Origin website = mOrigins.get(origin);
    291             callback.onReceiveValue(new Long(website.getUsage()));
    292         } else {
    293             HashMap values = new HashMap<String, Object>();
    294             values.put(ORIGIN, origin);
    295             values.put(CALLBACK, callback);
    296             postMessage(Message.obtain(null, GET_USAGE_ORIGIN, values));
    297         }
    298     }
    299 
    300     /**
    301      * Returns the quota for a given origin
    302      */
    303     public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
    304         if (callback == null) {
    305             return;
    306         }
    307         if (origin == null) {
    308             callback.onReceiveValue(null);
    309             return;
    310         }
    311         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    312             syncValues();
    313             Origin website = mOrigins.get(origin);
    314             callback.onReceiveValue(new Long(website.getUsage()));
    315         } else {
    316             HashMap values = new HashMap<String, Object>();
    317             values.put(ORIGIN, origin);
    318             values.put(CALLBACK, callback);
    319             postMessage(Message.obtain(null, GET_QUOTA_ORIGIN, values));
    320         }
    321     }
    322 
    323     /**
    324      * Set the quota for a given origin
    325      */
    326     public void setQuotaForOrigin(String origin, long quota) {
    327         if (origin != null) {
    328             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    329                 nativeSetQuotaForOrigin(origin, quota);
    330             } else {
    331                 postMessage(Message.obtain(null, SET_QUOTA_ORIGIN,
    332                     new Origin(origin, quota)));
    333             }
    334         }
    335     }
    336 
    337     /**
    338      * Delete a given origin
    339      */
    340     public void deleteOrigin(String origin) {
    341         if (origin != null) {
    342             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    343                 nativeDeleteOrigin(origin);
    344             } else {
    345                 postMessage(Message.obtain(null, DELETE_ORIGIN,
    346                     new Origin(origin)));
    347             }
    348         }
    349     }
    350 
    351     /**
    352      * Delete all databases
    353      */
    354     public void deleteAllData() {
    355         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    356             nativeDeleteAllData();
    357         } else {
    358             postMessage(Message.obtain(null, DELETE_ALL));
    359         }
    360     }
    361 
    362     /**
    363      * Sets the maximum size of the ApplicationCache.
    364      * This should only ever be called on the WebKit thread.
    365      * @hide Pending API council approval
    366      */
    367     public void setAppCacheMaximumSize(long size) {
    368         nativeSetAppCacheMaximumSize(size);
    369     }
    370 
    371     /**
    372      * Utility function to send a message to our handler
    373      */
    374     private synchronized void postMessage(Message msg) {
    375         if (mHandler != null) {
    376             mHandler.sendMessage(msg);
    377         }
    378     }
    379 
    380     /**
    381      * Utility function to send a message to the handler on the UI thread
    382      */
    383     private void postUIMessage(Message msg) {
    384         if (mUIHandler != null) {
    385             mUIHandler.sendMessage(msg);
    386         }
    387     }
    388 
    389     /**
    390      * Get the global instance of WebStorage.
    391      * @return A single instance of WebStorage.
    392      */
    393     public static WebStorage getInstance() {
    394       if (sWebStorage == null) {
    395           sWebStorage = new WebStorage();
    396       }
    397       return sWebStorage;
    398     }
    399 
    400     /**
    401      * @hide
    402      * Post a Sync request
    403      */
    404     public void update() {
    405         if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
    406             syncValues();
    407         } else {
    408             postMessage(Message.obtain(null, UPDATE));
    409         }
    410     }
    411 
    412     /**
    413      * Run on the webcore thread
    414      * set the local values with the current ones
    415      */
    416     private void syncValues() {
    417         Set<String> tmp = nativeGetOrigins();
    418         mOrigins = new HashMap<String, Origin>();
    419         for (String origin : tmp) {
    420             Origin website = new Origin(origin,
    421                                  nativeGetQuotaForOrigin(origin),
    422                                  nativeGetUsageForOrigin(origin));
    423             mOrigins.put(origin, website);
    424         }
    425     }
    426 
    427     // Native functions
    428     private static native Set nativeGetOrigins();
    429     private static native long nativeGetUsageForOrigin(String origin);
    430     private static native long nativeGetQuotaForOrigin(String origin);
    431     private static native void nativeSetQuotaForOrigin(String origin, long quota);
    432     private static native void nativeDeleteOrigin(String origin);
    433     private static native void nativeDeleteAllData();
    434     private static native void nativeSetAppCacheMaximumSize(long size);
    435 }
    436