1
2
3
4
5
6
7
8
9
10
11
12
13
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
34
35
36
37
38
39
40
41
42 @JsxClass
43 public class Storage extends HtmlUnitScriptable {
44
45 private static final HashSet<String> RESERVED_NAMES_ = new HashSet<>(Arrays.asList(
46 "clear", "key", "getItem", "length", "removeItem",
47 "setItem", "constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "propertyIsEnumerable",
48 "isPrototypeOf", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__"));
49
50 private static final long STORE_SIZE_KIMIT = 5_200_000;
51
52 private final Map<String, String> store_;
53 private long storeSize_;
54
55
56
57
58 public Storage() {
59 super();
60 store_ = null;
61 }
62
63
64
65
66 @JsxConstructor
67 public void jsConstructor() {
68
69 }
70
71
72
73
74
75
76
77 public Storage(final Window window, final Map<String, String> store) {
78 super();
79 store_ = store;
80 storeSize_ = 0L;
81 setParentScope(getTopLevelScope(window.getParentScope()));
82 setPrototype(window.getPrototype(Storage.class));
83 }
84
85
86
87
88 @Override
89 public void put(final String name, final Scriptable start, final Object value) {
90 final boolean isReserved = RESERVED_NAMES_.contains(name);
91 if (store_ == null || isReserved) {
92 super.put(name, start, value);
93 }
94 if (store_ != null && (!isReserved || getBrowserVersion().hasFeature(JS_STORAGE_PRESERVED_INCLUDED))) {
95 setItem(name, JavaScriptEngine.toString(value));
96 }
97 }
98
99
100
101
102 @Override
103 public Object get(final String name, final Scriptable start) {
104 if (store_ == null || RESERVED_NAMES_.contains(name)) {
105 return super.get(name, start);
106 }
107 final Object value = getItem(name);
108 if (value != null) {
109 return value;
110 }
111 return super.get(name, start);
112 }
113
114
115
116
117
118
119 @JsxGetter
120 public int getLength() {
121 return store_.size();
122 }
123
124
125
126
127
128
129 @JsxFunction
130 public void removeItem(final String key) {
131 final String removed = store_.remove(key);
132 if (removed != null) {
133 storeSize_ -= removed.length();
134 }
135 }
136
137
138
139
140
141
142
143 @JsxFunction
144 public String key(final int index) {
145 if (index >= 0) {
146 int counter = 0;
147 for (final String key : store_.keySet()) {
148 if (counter == index) {
149 return key;
150 }
151 counter++;
152 }
153 }
154 return null;
155 }
156
157
158
159
160
161
162
163 @JsxFunction
164 public Object getItem(final String key) {
165 return store_.get(key);
166 }
167
168
169
170
171
172
173
174 @JsxFunction
175 public void setItem(final String key, final String data) {
176 final String existingData = store_.get(key);
177 final long storeSize = storeSize_ + data.length() - (existingData != null ? existingData.length() : 0);
178 if (storeSize > STORE_SIZE_KIMIT) {
179 throw JavaScriptEngine.throwAsScriptRuntimeEx(
180 new DOMException((short) 22, "QuotaExceededError: Failed to execute 'setItem' on 'Storage': "
181 + "Setting the value of '" + key + "' exceeded the quota."));
182 }
183 storeSize_ = storeSize;
184 store_.put(key, data);
185 }
186
187
188
189
190 @JsxFunction
191 public void clear() {
192 store_.clear();
193 storeSize_ = 0;
194 }
195 }