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 @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
55
56 public Storage() {
57 super();
58 store_ = null;
59 }
60
61
62
63
64 @JsxConstructor
65 public void jsConstructor() {
66
67 }
68
69
70
71
72
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
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
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
113
114
115 @JsxGetter
116 public int getLength() {
117 return store_.size();
118 }
119
120
121
122
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
134
135
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
153
154
155
156 @JsxFunction
157 public Object getItem(final String key) {
158 return store_.get(key);
159 }
160
161
162
163
164
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
181
182 @JsxFunction
183 public void clear() {
184 store_.clear();
185 storeSize_ = 0;
186 }
187 }