View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
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    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_STORAGE_PRESERVED_INCLUDED;
18  
19  import java.util.Arrays;
20  import java.util.HashSet;
21  import java.util.Map;
22  
23  import org.htmlunit.corejs.javascript.Scriptable;
24  import org.htmlunit.javascript.HtmlUnitScriptable;
25  import org.htmlunit.javascript.JavaScriptEngine;
26  import org.htmlunit.javascript.configuration.JsxClass;
27  import org.htmlunit.javascript.configuration.JsxConstructor;
28  import org.htmlunit.javascript.configuration.JsxFunction;
29  import org.htmlunit.javascript.configuration.JsxGetter;
30  import org.w3c.dom.DOMException;
31  
32  /**
33   * The JavaScript object that represents a Storage.
34   *
35   * @author Ahmed Ashour
36   * @author Marc Guillemot
37   * @author Ronald Brill
38   * @author Kanoko Yamamoto
39   */
40  @JsxClass
41  public class Storage extends HtmlUnitScriptable {
42  
43      private static final HashSet<String> RESERVED_NAMES_ = new HashSet<>(Arrays.asList(
44          "clear", "key", "getItem", "length", "removeItem",
45          "setItem", "constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "propertyIsEnumerable",
46          "isPrototypeOf", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__"));
47  
48      private static final long STORE_SIZE_KIMIT = 5_200_000;
49  
50      private final Map<String, String> store_;
51      private long storeSize_;
52  
53      /**
54       * Public default constructor only for the prototype.
55       */
56      public Storage() {
57          super();
58          store_ = null;
59      }
60  
61      /**
62       * JavaScript constructor.
63       */
64      @JsxConstructor
65      public void jsConstructor() {
66          // nothing to do
67      }
68  
69      /**
70       * Constructor.
71       * @param window the parent scope
72       * @param store the storage itself
73       */
74      public Storage(final Window window, final Map<String, String> store) {
75          super();
76          store_ = store;
77          storeSize_ = 0L;
78          setParentScope(getTopLevelScope(window.getParentScope()));
79          setPrototype(window.getPrototype(Storage.class));
80      }
81  
82      /**
83       * {@inheritDoc}
84       */
85      @Override
86      public void put(final String name, final Scriptable start, final Object value) {
87          final boolean isReserved = RESERVED_NAMES_.contains(name);
88          if (store_ == null || isReserved) {
89              super.put(name, start, value);
90          }
91          if (store_ != null && (!isReserved || getBrowserVersion().hasFeature(JS_STORAGE_PRESERVED_INCLUDED))) {
92              setItem(name, JavaScriptEngine.toString(value));
93          }
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      @Override
100     public Object get(final String name, final Scriptable start) {
101         if (store_ == null || RESERVED_NAMES_.contains(name)) {
102             return super.get(name, start);
103         }
104         final Object value = getItem(name);
105         if (value != null) {
106             return value;
107         }
108         return super.get(name, start);
109     }
110 
111     /**
112      * Returns the length property.
113      * @return the length property
114      */
115     @JsxGetter
116     public int getLength() {
117         return store_.size();
118     }
119 
120     /**
121      * Removes the specified key.
122      * @param key the item key
123      */
124     @JsxFunction
125     public void removeItem(final String key) {
126         final String removed = store_.remove(key);
127         if (removed != null) {
128             storeSize_ -= removed.length();
129         }
130     }
131 
132     /**
133      * Returns the key of the specified index.
134      * @param index the index
135      * @return the key
136      */
137     @JsxFunction
138     public String key(final int index) {
139         if (index >= 0) {
140             int counter = 0;
141             for (final String key : store_.keySet()) {
142                 if (counter == index) {
143                     return key;
144                 }
145                 counter++;
146             }
147         }
148         return null;
149     }
150 
151     /**
152      * Returns the value of the specified key.
153      * @param key the item key
154      * @return the value
155      */
156     @JsxFunction
157     public Object getItem(final String key) {
158         return store_.get(key);
159     }
160 
161     /**
162      * Sets the item value.
163      * @param key the item key
164      * @param data the value
165      */
166     @JsxFunction
167     public void setItem(final String key, final String data) {
168         final String existingData = store_.get(key);
169         final long storeSize = storeSize_ + data.length() - (existingData != null ? existingData.length() : 0);
170         if (storeSize > STORE_SIZE_KIMIT) {
171             throw JavaScriptEngine.throwAsScriptRuntimeEx(
172                     new DOMException((short) 22, "QuotaExceededError: Failed to execute 'setItem' on 'Storage': "
173                             + "Setting the value of '" + key + "' exceeded the quota."));
174         }
175         storeSize_ = storeSize;
176         store_.put(key, data);
177     }
178 
179     /**
180      * Clears all items.
181      */
182     @JsxFunction
183     public void clear() {
184         store_.clear();
185         storeSize_ = 0;
186     }
187 }