Skip to content

Commit 94afb5e

Browse files
authored
Merge pull request #517 from brave/optimize-resource-storage-memory
Optimize resource storage memory
2 parents dc5ab3c + 7864b74 commit 94afb5e

File tree

1 file changed

+69
-16
lines changed

1 file changed

+69
-16
lines changed

src/resources/resource_storage.rs

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,45 @@ use thiserror::Error;
99

1010
use super::{PermissionMask, Resource, ResourceType};
1111

12+
#[derive(Clone)]
13+
enum ResourceContent {
14+
/// A valid utf8 string. Used for text/* mime types or for ResourceType:Template
15+
Text(String),
16+
17+
/// Raw content in the form of a byte array. Used for other mime types like
18+
/// "image/gif" or "audio/mp3"
19+
Raw(Vec<u8>),
20+
}
21+
22+
impl ResourceContent {
23+
fn text_from_base64(base64: &str) -> Result<Self, AddResourceError> {
24+
let decoded = BASE64_STANDARD.decode(base64)?;
25+
Ok(Self::Text(String::from_utf8(decoded)?))
26+
}
27+
28+
fn raw_from_base64(base64: &str) -> Result<Self, AddResourceError> {
29+
let decoded = BASE64_STANDARD.decode(base64)?;
30+
Ok(Self::Raw(decoded))
31+
}
32+
}
33+
34+
#[derive(Clone)]
35+
/// A internal representation of a Resource to store. Stores the content
36+
/// in the decoded form to use less memory.
37+
/// See [Resource] for details
38+
struct ResourceImpl {
39+
name: String,
40+
kind: ResourceType,
41+
content: ResourceContent,
42+
dependencies: Vec<String>,
43+
permission: PermissionMask,
44+
}
45+
1246
/// Unified resource storage for both redirects and scriptlets.
1347
#[derive(Default)]
1448
pub struct ResourceStorage {
1549
/// Stores each resource by its canonical name
16-
resources: HashMap<String, Resource>,
50+
resources: HashMap<String, ResourceImpl>,
1751
/// Stores mappings from aliases to their canonical resource names
1852
aliases: HashMap<String, String>,
1953
}
@@ -130,16 +164,20 @@ impl ResourceStorage {
130164

131165
/// Adds a resource to storage so that it can be retrieved later.
132166
pub fn add_resource(&mut self, resource: Resource) -> Result<(), AddResourceError> {
167+
let resource_content: ResourceContent;
168+
133169
if let ResourceType::Mime(content_type) = &resource.kind {
134170
if !resource.dependencies.is_empty() && !content_type.supports_dependencies() {
135171
return Err(AddResourceError::ContentTypeDoesNotSupportDependencies);
136172
}
137173

138-
// Ensure the resource contents are valid base64 (and utf8 if applicable)
139-
let decoded = BASE64_STANDARD.decode(&resource.content)?;
140174
if content_type.is_textual() {
141-
let _ = String::from_utf8(decoded)?;
175+
resource_content = ResourceContent::text_from_base64(&resource.content)?;
176+
} else {
177+
resource_content = ResourceContent::raw_from_base64(&resource.content)?;
142178
}
179+
} else {
180+
resource_content = ResourceContent::text_from_base64(&resource.content)?;
143181
}
144182

145183
for ident in std::iter::once(&resource.name).chain(resource.aliases.iter()) {
@@ -151,7 +189,14 @@ impl ResourceStorage {
151189
resource.aliases.iter().for_each(|alias| {
152190
self.aliases.insert(alias.clone(), resource.name.clone());
153191
});
154-
self.resources.insert(resource.name.clone(), resource);
192+
let resource_impl = ResourceImpl {
193+
name: resource.name.clone(),
194+
kind: resource.kind,
195+
content: resource_content,
196+
dependencies: resource.dependencies,
197+
permission: resource.permission,
198+
};
199+
self.resources.insert(resource.name, resource_impl);
155200

156201
Ok(())
157202
}
@@ -176,11 +221,9 @@ impl ResourceStorage {
176221
let mut result = String::new();
177222

178223
for dep in deps.iter() {
179-
if let Ok(decoded) = BASE64_STANDARD.decode(&dep.content) {
180-
if let Ok(dep) = core::str::from_utf8(&decoded) {
181-
result += dep;
182-
result += "\n";
183-
}
224+
if let ResourceContent::Text(content) = &dep.content {
225+
result += content;
226+
result += "\n";
184227
}
185228
}
186229

@@ -197,7 +240,7 @@ impl ResourceStorage {
197240
fn recursive_dependencies<'a: 'b, 'b>(
198241
&'a self,
199242
new_dep: &str,
200-
prev_deps: &mut Vec<&'b Resource>,
243+
prev_deps: &mut Vec<&'b ResourceImpl>,
201244
filter_permission: PermissionMask,
202245
) -> Result<(), ScriptletResourceError> {
203246
if prev_deps.iter().any(|dep| dep.name == new_dep) {
@@ -221,7 +264,7 @@ impl ResourceStorage {
221264
&'a self,
222265
scriptlet_args: &str,
223266
filter_permission: PermissionMask,
224-
required_deps: &mut Vec<&'b Resource>,
267+
required_deps: &mut Vec<&'b ResourceImpl>,
225268
) -> Result<String, ScriptletResourceError> {
226269
// `unwrap` is safe because these are guaranteed valid at filter parsing.
227270
let scriptlet_args = parse_scriptlet_args(scriptlet_args).unwrap();
@@ -247,7 +290,12 @@ impl ResourceStorage {
247290
self.recursive_dependencies(dep, required_deps, filter_permission)?;
248291
}
249292

250-
let template = String::from_utf8(BASE64_STANDARD.decode(&resource.content)?)?;
293+
let template = match &resource.content {
294+
ResourceContent::Raw(_content) => {
295+
return Err(ScriptletResourceError::ContentTypeNotInjectable);
296+
}
297+
ResourceContent::Text(content) => content.clone(),
298+
};
251299

252300
if let Some(function_name) = extract_function_name(&template) {
253301
// newer function-style resource: pass args using function call syntax
@@ -284,15 +332,20 @@ impl ResourceStorage {
284332
return None;
285333
}
286334
if let ResourceType::Mime(mime) = &resource.kind {
287-
Some(format!("data:{};base64,{}", mime, &resource.content))
335+
let bytes = match &resource.content {
336+
ResourceContent::Raw(content) => content,
337+
ResourceContent::Text(content) => content.as_bytes(),
338+
};
339+
let encoded = BASE64_STANDARD.encode(bytes);
340+
Some(format!("data:{};base64,{}", mime, encoded))
288341
} else {
289342
None
290343
}
291344
})
292345
}
293346

294347
/// Gets the resource associated with `resource_ident`, respecting aliases if necessary.
295-
fn get_internal_resource(&self, resource_ident: &str) -> Option<&Resource> {
348+
fn get_internal_resource(&self, resource_ident: &str) -> Option<&ResourceImpl> {
296349
let resource = if let Some(resource) = self.resources.get(resource_ident) {
297350
Some(resource)
298351
} else if let Some(canonical_name) = self.aliases.get(resource_ident) {
@@ -308,7 +361,7 @@ impl ResourceStorage {
308361
&self,
309362
scriptlet_name: &str,
310363
filter_permission: PermissionMask,
311-
) -> Result<&Resource, ScriptletResourceError> {
364+
) -> Result<&ResourceImpl, ScriptletResourceError> {
312365
let resource = self
313366
.get_internal_resource(scriptlet_name)
314367
.ok_or(ScriptletResourceError::NoMatchingScriptlet)?;

0 commit comments

Comments
 (0)