fix: close the review findings on the issue wave

Ported from d719fb77 (#1717); the hatch draw-order, PDF and
recovery-settle hunks stay out (main owns those paths).

- La16 was still billed at 4 B/px: the no-conversion special case
  keyed on native_bpp == 4, which La16 also is — but only Rgba8 is a
  real move in into_rgba8, so La16's decode peak is 8. The case now
  keys on the color type, and a table test pins every layout's peak
  (plus GIF's screen+frame 8) so the accounting can't silently drift.
- json_values_match: an integer beyond f64 precision no longer matches
  a float it merely rounds onto; a mixed pair matches only when the
  float is whole and converts back to exactly that integer (also the
  fuzz finding of 9262d63f).
- ocs-control.js resolves `ready` with the API object instead of
  window.ocsControl, which is still undefined when the bridge already
  exists as the script runs (built before the first settle, avoiding
  the temporal-dead-zone throw of a later const); DATALINK on web says
  the typed path is superseded by the picker instead of silently
  discarding it.

(cherry picked from commit d719fb77)
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:05 +03:00
1 parent db8db14006
commit 2d2444fcfa
26 files changed
+116 -7

No files matched your search

+10 -1
View File
@@ -1805,7 +1805,16 @@ impl OpenCADStudio {
// then runs from `DataLinkCsvPicked`.
#[cfg(target_arch = "wasm32")]
{
let _ = path;
// Say the typed argument is superseded — a silent
// redirect looks like the input was lost.
if !path.trim().is_empty() {
self.command_line.push_info(
crate::t!(
"The browser cannot open a typed path; pick the file instead."
)
.as_ref(),
);
}
return Some(Task::perform(
crate::io::pick_datalink_csv_web(),
Message::DataLinkCsvPicked,
+38 -1
View File
@@ -1158,7 +1158,7 @@ fn apply_updates(
fn json_values_match(actual: &Value, expected: &Value) -> bool {
match (actual, expected) {
(Value::Number(a), Value::Number(b)) if a.is_f64() || b.is_f64() => {
a.as_f64() == b.as_f64()
numbers_match(a, b)
}
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b).all(|(a, b)| json_values_match(a, b))
@@ -1172,6 +1172,28 @@ fn json_values_match(actual: &Value, expected: &Value) -> bool {
}
}
/// `10.0` matches `10`, but an integer past f64 precision never matches a
/// float it merely rounds onto: a mixed pair is equal only when the float is
/// whole and converts back to exactly that integer.
fn numbers_match(a: &serde_json::Number, b: &serde_json::Number) -> bool {
let (int, float) = match (a.as_f64(), b.as_f64()) {
(Some(x), Some(y)) if a.is_f64() && b.is_f64() => return x == y,
(_, Some(y)) if b.is_f64() => (a, y),
(Some(x), _) => (b, x),
_ => return false,
};
if !float.is_finite() || float.fract() != 0.0 {
return false;
}
if let Some(i) = int.as_i64() {
i as f64 == float && float as i64 == i
} else if let Some(u) = int.as_u64() {
u as f64 == float && float as u64 == u
} else {
false
}
}
fn patched_table_entry<T>(
table: &Table<T>,
request: &Value,
@@ -1645,6 +1667,21 @@ mod tests {
use super::OpenCADStudio;
/// Whole floats match their integer; integers past f64 precision never
/// match the neighbouring float they round onto.
#[test]
fn numbers_match_by_value_without_precision_loss() {
let number = |value: Value| match value {
Value::Number(number) => number,
_ => unreachable!(),
};
assert!(super::numbers_match(&number(json!(10)), &number(json!(10.0))));
assert!(!super::numbers_match(&number(json!(10)), &number(json!(10.5))));
let past_precision = number(json!(9_007_199_254_740_993_u64));
assert!(!super::numbers_match(&past_precision, &number(json!(9_007_199_254_740_992.0))));
assert!(super::numbers_match(&number(json!(-3)), &number(json!(-3.0))));
}
fn execute(app: &mut OpenCADStudio, mut request: Value, id: &str) -> Value {
let state = app.automation_op(r#"{"protocol":1,"op":"state"}"#);
let object = request.as_object_mut().unwrap();
+1
View File
@@ -4188,6 +4188,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Data Extraction Settings" => Some(("common", "data-extraction-settings")),
"Add Drawings" => Some(("common", "add-drawings")),
"CSV" => Some(("common", "csv")),
"The browser cannot open a typed path; pick the file instead." => Some(("common", "browser-cannot-open-typed-path")),
"CSV Files" => Some(("common", "csv-files")),
"Link a CSV data source" => Some(("common", "link-a-csv-data-source")),
"Pick the file again to load its contents in the browser." => Some(("file", "pick-the-file-again-to-load-its-contents")),
+40 -3
View File
@@ -773,9 +773,14 @@ fn native_footprint(format: Option<image::ImageFormat>, color: Option<image::Col
_ => 4,
});
// `DynamicImage::into_rgba8` only MOVES for Rgba8; every other layout
// allocates the RGBA destination while the native buffer is still
// alive, so both coexist (image 0.25 buffer.rs cast_in_color_space).
let peak_bpp = if native_bpp == 4 { 4 } else { native_bpp + 4 };
// — including the 4 B/px La16 — allocates the RGBA destination while
// the native buffer is still alive, so both coexist (image 0.25
// buffer.rs cast_in_color_space).
let peak_bpp = if matches!(color, Some(image::ColorType::Rgba8)) {
4
} else {
native_bpp + 4
};
// GIF decodes the frame into its own buffer while the screen-sized
// output buffer is live — two RGBA-sized allocations coexist.
let peak_bpp = match format {
@@ -1369,6 +1374,38 @@ pub use web_fetch::{
mod tests {
use super::*;
/// The decode peak per layout: into_rgba8 is a MOVE for Rgba8 only —
/// every other layout pays native + RGBA coexisting. GIF additionally
/// carries the screen-sized output beside a (clamped) frame buffer.
#[test]
fn native_footprint_peaks_match_the_coexistence_math() {
use image::ColorType;
let png = image::ImageFormat::Png;
let gif = image::ImageFormat::Gif;
let cases = [
(ColorType::L8, 5u64),
(ColorType::La8, 6),
(ColorType::Rgb8, 7),
(ColorType::Rgba8, 4),
(ColorType::L16, 6),
// 4 B/px but NOT a move — the La16 hole this test pins.
(ColorType::La16, 8),
(ColorType::Rgb16, 10),
(ColorType::Rgba16, 12),
(ColorType::Rgb32F, 16),
(ColorType::Rgba32F, 20),
];
for (color, expected) in cases {
assert_eq!(
native_footprint(Some(png), Some(color)).peak_bpp,
expected,
"{color:?}"
);
}
assert_eq!(native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, 8);
assert_eq!(native_footprint(None, None).peak_bpp, 8);
}
/// Encode a small deterministic PNG entirely in memory — the same codec
/// `decode_from_memory` reads, so no fixture file is needed.
fn png_bytes(w: u32, h: u32, tint: u8) -> Vec<u8> {