fix(build): preserve non-render compatibility
This commit is contained in:
@@ -75,7 +75,7 @@ jobs:
|
||||
"LIBCLANG_PATH=C:\Program Files\LLVM\bin" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
|
||||
- name: Build no-render release binary
|
||||
run: cargo build --release --target ${{ matrix.target }} --no-default-features
|
||||
run: cargo build --release --target ${{ matrix.target }} -p obscura-cli --bins --no-default-features
|
||||
|
||||
- name: Stage no-render release binary
|
||||
shell: bash
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build no-render stealth release binary
|
||||
run: cargo build --release --target ${{ matrix.target }} --no-default-features --features stealth
|
||||
run: cargo build --release --target ${{ matrix.target }} -p obscura-cli --bins --no-default-features --features stealth
|
||||
|
||||
- name: Stage no-render stealth release binary
|
||||
shell: bash
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build render-enabled release binary
|
||||
run: cargo build --release --target ${{ matrix.target }} --features render
|
||||
run: cargo build --release --target ${{ matrix.target }} -p obscura-cli --bins --features render
|
||||
|
||||
- name: Stage default release binary
|
||||
shell: bash
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build render-enabled stealth release binary
|
||||
run: cargo build --release --target ${{ matrix.target }} --features render,stealth
|
||||
run: cargo build --release --target ${{ matrix.target }} -p obscura-cli --bins --features render,stealth
|
||||
|
||||
- name: Stage stealth release binary
|
||||
shell: bash
|
||||
|
||||
@@ -13,14 +13,14 @@ capabilities. It targets web scraping and AI-agent automation.
|
||||
## Build
|
||||
|
||||
```bash
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --features render
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --features render
|
||||
|
||||
# Rendering and stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --features render,stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
|
||||
# No rendering, with rustls or stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --no-default-features
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --no-default-features --features stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --no-default-features
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --no-default-features --features stealth
|
||||
```
|
||||
|
||||
- The first build compiles V8 from source: ~5 minutes and a few GB of disk.
|
||||
|
||||
@@ -201,16 +201,16 @@ git clone https://github.com/h4ckf0r0day/obscura.git
|
||||
cd obscura
|
||||
|
||||
# Rendering
|
||||
cargo build --release --features render
|
||||
cargo build --release -p obscura-cli --bins --features render
|
||||
|
||||
# Rendering and stealth
|
||||
cargo build --release --features render,stealth
|
||||
cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
|
||||
# No rendering
|
||||
cargo build --release --no-default-features
|
||||
cargo build --release -p obscura-cli --bins --no-default-features
|
||||
|
||||
# No rendering, with stealth
|
||||
cargo build --release --no-default-features --features stealth
|
||||
cargo build --release -p obscura-cli --bins --no-default-features --features stealth
|
||||
```
|
||||
|
||||
Requires Rust 1.75+ ([rustup.rs](https://rustup.rs)). First build takes ~5 min (V8 compiles from source, cached after).
|
||||
|
||||
@@ -4124,16 +4124,19 @@ mod tests {
|
||||
"an inline import must fetch exactly once as a stylesheet"
|
||||
);
|
||||
}
|
||||
for path in ["/local.svg", "/imported.svg"] {
|
||||
assert_eq!(
|
||||
observed_requests
|
||||
.iter()
|
||||
.filter(|(request_path, _)| request_path == path)
|
||||
.map(|(_, resource_type)| *resource_type)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![obscura_net::ResourceType::Image],
|
||||
"ordinary rule assets must remain in render warmup"
|
||||
);
|
||||
#[cfg(feature = "render")]
|
||||
{
|
||||
for path in ["/local.svg", "/imported.svg"] {
|
||||
assert_eq!(
|
||||
observed_requests
|
||||
.iter()
|
||||
.filter(|(request_path, _)| request_path == path)
|
||||
.map(|(_, resource_type)| *resource_type)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![obscura_net::ResourceType::Image],
|
||||
"ordinary rule assets must remain in render warmup"
|
||||
);
|
||||
}
|
||||
}
|
||||
drop(observed_requests);
|
||||
|
||||
@@ -4165,19 +4168,22 @@ mod tests {
|
||||
assert!(styles[0].2.starts_with("@media print {\n"));
|
||||
assert!(styles[1].2.starts_with("@media print {\n"));
|
||||
|
||||
let pdf = page
|
||||
.raster_pdf(crate::RasterPdfOptions {
|
||||
print_background: true,
|
||||
paper_width_in: 100.0 / 72.0,
|
||||
paper_height_in: 80.0 / 72.0,
|
||||
margin_top_in: 0.0,
|
||||
margin_bottom_in: 0.0,
|
||||
margin_left_in: 0.0,
|
||||
margin_right_in: 0.0,
|
||||
..crate::RasterPdfOptions::default()
|
||||
})
|
||||
.expect("inline-import print PDF");
|
||||
assert!(pdf.starts_with(b"%PDF-1.4"));
|
||||
#[cfg(feature = "render")]
|
||||
{
|
||||
let pdf = page
|
||||
.raster_pdf(crate::RasterPdfOptions {
|
||||
print_background: true,
|
||||
paper_width_in: 100.0 / 72.0,
|
||||
paper_height_in: 80.0 / 72.0,
|
||||
margin_top_in: 0.0,
|
||||
margin_bottom_in: 0.0,
|
||||
margin_left_in: 0.0,
|
||||
margin_right_in: 0.0,
|
||||
..crate::RasterPdfOptions::default()
|
||||
})
|
||||
.expect("inline-import print PDF");
|
||||
assert!(pdf.starts_with(b"%PDF-1.4"));
|
||||
}
|
||||
}
|
||||
|
||||
fn client_replacement_page(name: &str, deferred: bool) -> super::Page {
|
||||
|
||||
Vendored
+12
-2
@@ -5540,7 +5540,11 @@ class HTMLImageElement extends Element {
|
||||
}
|
||||
|
||||
_imageSourceChanged() {
|
||||
this._adoptImageCandidate("");
|
||||
// The lightweight build has no retained render-resource cache. It still
|
||||
// preserves the historical non-blocking Image lifecycle so preloaders do
|
||||
// not hang while rendering is disabled.
|
||||
const hasMetadataLoader = typeof Deno.core.ops.op_load_image_metadata === "function";
|
||||
this._adoptImageCandidate(hasMetadataLoader ? "" : this.src);
|
||||
this._imageCompletionDeferred = true;
|
||||
this._refreshImageFromCache(true);
|
||||
if (!this._imageComplete) this._queueImageRequest();
|
||||
@@ -7761,7 +7765,13 @@ globalThis.getComputedStyle = (el) => {
|
||||
// Non-render builds and properties outside the renderer snapshot retain
|
||||
// the lightweight inline CSSOM behavior.
|
||||
const inlineVal = target.getPropertyValue ? target.getPropertyValue(rawProp) : '';
|
||||
if (inlineVal) return inlineVal;
|
||||
if (inlineVal) {
|
||||
if (kebab === 'opacity') {
|
||||
const value = Number(inlineVal);
|
||||
if (Number.isFinite(value)) return String(Math.min(1, Math.max(0, value)));
|
||||
}
|
||||
return inlineVal;
|
||||
}
|
||||
const dim = dimensionFor(kebab);
|
||||
if (dim != null) return dim;
|
||||
if (defaultsKebab[rawProp]) return defaultsKebab[rawProp];
|
||||
|
||||
@@ -5475,6 +5475,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn raf_geometry_mutation_reaches_settled_intersection_before_next_frame() {
|
||||
let mut rt = setup_runtime(
|
||||
|
||||
@@ -18465,6 +18465,7 @@ mod tests {
|
||||
assert_eq!(laid.rects.get(&b).unwrap().y, 50.0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn text_transform_applies_to_word_leaves_in_flex_ui() {
|
||||
let tree = parse_html(
|
||||
@@ -18481,6 +18482,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn word_leaves_use_the_computed_line_height() {
|
||||
let tree = parse_html(
|
||||
@@ -18989,6 +18991,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn forced_break_in_pseudo_joined_inline_run_has_no_phantom_line() {
|
||||
let tree = parse_html(
|
||||
@@ -19184,6 +19187,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn ordinary_inline_fragment_uses_font_box_while_line_keeps_line_height() {
|
||||
let tree = parse_html(
|
||||
@@ -19233,6 +19237,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn nested_inline_uses_final_shaping_for_its_canonical_fragment() {
|
||||
let tree = parse_html(
|
||||
@@ -19364,6 +19369,7 @@ mod tests {
|
||||
assert!(fragments.iter().all(|fragment| fragment.width <= 70.01));
|
||||
}
|
||||
|
||||
#[cfg(feature = "paint")]
|
||||
#[test]
|
||||
fn empty_decorated_inline_has_a_box_and_advances_following_text() {
|
||||
let tree = parse_html(
|
||||
|
||||
@@ -141,6 +141,14 @@ pub mod inline {
|
||||
use obscura_dom::tree::{DomTree, NodeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WebFont {
|
||||
pub data: Vec<u8>,
|
||||
pub family: Option<String>,
|
||||
pub weight: Option<(u16, u16)>,
|
||||
pub italic: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextEngine;
|
||||
|
||||
@@ -148,6 +156,27 @@ pub mod inline {
|
||||
pub fn new() -> Self {
|
||||
TextEngine
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_web_fonts(_fonts: &[WebFont]) -> Self {
|
||||
TextEngine
|
||||
}
|
||||
|
||||
pub fn register_replaced(
|
||||
&mut self,
|
||||
_width: f32,
|
||||
_height: f32,
|
||||
_style: &crate::LayoutStyle,
|
||||
) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub(crate) fn register_replaced_intrinsic(
|
||||
&mut self,
|
||||
_intrinsic: crate::ReplacedIntrinsic,
|
||||
_style: &crate::LayoutStyle,
|
||||
) -> usize {
|
||||
0
|
||||
}
|
||||
/// Layout-only builds have no shaper, so no container is ever treated
|
||||
/// as a cosmic-text inline formatting context: the word-split path
|
||||
/// handles text geometry for `getBoundingClientRect`.
|
||||
@@ -202,6 +231,142 @@ pub mod inline {
|
||||
(0.0, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn used_line_height(style: &crate::LayoutStyle) -> f32 {
|
||||
TextEngine.selected_line_height(style)
|
||||
}
|
||||
|
||||
pub(crate) fn is_replaced(local: &str) -> bool {
|
||||
matches!(
|
||||
local,
|
||||
"img"
|
||||
| "svg"
|
||||
| "canvas"
|
||||
| "video"
|
||||
| "audio"
|
||||
| "iframe"
|
||||
| "embed"
|
||||
| "object"
|
||||
| "input"
|
||||
| "textarea"
|
||||
| "select"
|
||||
| "button"
|
||||
| "progress"
|
||||
| "meter"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn has_replaced_sizing(local: &str) -> bool {
|
||||
matches!(
|
||||
local,
|
||||
"img"
|
||||
| "canvas"
|
||||
| "video"
|
||||
| "audio"
|
||||
| "iframe"
|
||||
| "embed"
|
||||
| "object"
|
||||
| "progress"
|
||||
| "meter"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn default_replaced_intrinsic_size(
|
||||
local: &str,
|
||||
font_size: f32,
|
||||
has_controls: bool,
|
||||
has_resource: bool,
|
||||
) -> Option<(f32, f32)> {
|
||||
match local {
|
||||
"canvas" | "video" | "iframe" | "object" => Some((300.0, 150.0)),
|
||||
"embed" if has_resource => Some((300.0, 150.0)),
|
||||
"audio" if has_controls => Some((300.0, 54.0)),
|
||||
"progress" => Some((font_size * 10.0, font_size)),
|
||||
"meter" => Some((font_size * 5.0, font_size)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn constrained_auto_replaced_size(
|
||||
width: f32,
|
||||
height: f32,
|
||||
style: &crate::LayoutStyle,
|
||||
) -> taffy::Size<f32> {
|
||||
let px = |dimension| match dimension {
|
||||
crate::Dimension::Px(value) => Some(value.max(0.0)),
|
||||
_ => None,
|
||||
};
|
||||
let ratio = style
|
||||
.aspect_ratio
|
||||
.filter(|ratio| ratio.is_finite() && *ratio > 0.0)
|
||||
.unwrap_or_else(|| {
|
||||
if width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0 {
|
||||
width / height
|
||||
} else {
|
||||
2.0
|
||||
}
|
||||
});
|
||||
let preferred_width = px(style.width);
|
||||
let preferred_height = px(style.height);
|
||||
let min_width = px(style.min_width).unwrap_or(0.0);
|
||||
let min_height = px(style.min_height).unwrap_or(0.0);
|
||||
let max_width = px(style.max_width)
|
||||
.unwrap_or(f32::INFINITY)
|
||||
.max(min_width);
|
||||
let max_height = px(style.max_height)
|
||||
.unwrap_or(f32::INFINITY)
|
||||
.max(min_height);
|
||||
|
||||
let (width, height) = match (preferred_width, preferred_height) {
|
||||
(Some(width), Some(height)) => (
|
||||
width.min(max_width).max(min_width),
|
||||
height.min(max_height).max(min_height),
|
||||
),
|
||||
(Some(width), None) => {
|
||||
let width = width.min(max_width).max(min_width);
|
||||
(width, (width / ratio).min(max_height).max(min_height))
|
||||
}
|
||||
(None, Some(height)) => {
|
||||
let height = height.min(max_height).max(min_height);
|
||||
((height * ratio).min(max_width).max(min_width), height)
|
||||
}
|
||||
(None, None) => {
|
||||
let height_at_max_width = (max_width / ratio).max(min_height);
|
||||
let height_at_min_width = (min_width / ratio).min(max_height);
|
||||
let width_at_max_height = (max_height * ratio).max(min_width);
|
||||
let width_at_min_height = (min_height * ratio).min(max_width);
|
||||
if width > max_width {
|
||||
if height > max_height {
|
||||
if max_width * height <= max_height * width {
|
||||
(max_width, height_at_max_width)
|
||||
} else {
|
||||
(width_at_max_height, max_height)
|
||||
}
|
||||
} else {
|
||||
(max_width, height_at_max_width)
|
||||
}
|
||||
} else if width < min_width {
|
||||
if height < min_height {
|
||||
if min_width * height <= min_height * width {
|
||||
(width_at_min_height, min_height)
|
||||
} else {
|
||||
(min_width, height_at_min_width)
|
||||
}
|
||||
} else {
|
||||
(min_width, height_at_min_width)
|
||||
}
|
||||
} else if height > max_height {
|
||||
(width_at_max_height, max_height)
|
||||
} else if height < min_height {
|
||||
(width_at_min_height, min_height)
|
||||
} else {
|
||||
(width, height)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
taffy::Size { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
/// An axis-aligned rectangle in CSS pixels, relative to the containing block.
|
||||
|
||||
@@ -10,6 +10,7 @@ license = "Apache-2.0"
|
||||
[features]
|
||||
default = ["api"]
|
||||
api = ["obscura-browser", "obscura-net", "tokio"]
|
||||
render = ["api", "obscura-browser/render"]
|
||||
|
||||
[dependencies]
|
||||
obscura-browser = { path = "../obscura-browser", optional = true }
|
||||
|
||||
@@ -113,6 +113,7 @@ async fn new_image_still_emulates_load_when_src_is_configurable() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
#[tokio::test]
|
||||
async fn invalid_image_bytes_emit_error_like_chromium() {
|
||||
std::env::set_var("OBSCURA_ALLOW_PRIVATE_NETWORK", "1");
|
||||
|
||||
@@ -11,7 +11,7 @@ First build takes about 5 minutes. Incremental builds are seconds.
|
||||
```bash
|
||||
git clone https://github.com/h4ckf0r0day/obscura.git
|
||||
cd obscura
|
||||
cargo build --release --features render
|
||||
cargo build --release -p obscura-cli --bins --features render
|
||||
```
|
||||
|
||||
Binary is at `./target/release/obscura`.
|
||||
@@ -22,7 +22,7 @@ and PDF export.
|
||||
## Rendering and stealth
|
||||
|
||||
```bash
|
||||
cargo build --release --features render,stealth
|
||||
cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
```
|
||||
|
||||
This is the complete rendering build with the stealth wreq/BoringSSL transport,
|
||||
@@ -32,8 +32,8 @@ blocklist. See [Configure stealth and proxies](Configure-stealth-and-proxies.md)
|
||||
## Without rendering
|
||||
|
||||
```bash
|
||||
cargo build --release --no-default-features
|
||||
cargo build --release --no-default-features --features stealth
|
||||
cargo build --release -p obscura-cli --bins --no-default-features
|
||||
cargo build --release -p obscura-cli --bins --no-default-features --features stealth
|
||||
```
|
||||
|
||||
The second command keeps stealth while excluding layout, screenshots,
|
||||
@@ -58,7 +58,7 @@ libc++ while compiling BoringSSL. Use the active SDK for that build:
|
||||
```bash
|
||||
SDK_PATH="$(xcrun --show-sdk-path)"
|
||||
SDKROOT="$SDK_PATH" CXXFLAGS="-isystem $SDK_PATH/usr/include/c++/v1" \
|
||||
cargo build --release --features render,stealth
|
||||
cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
```
|
||||
|
||||
## OpenSSL on older systems
|
||||
@@ -66,7 +66,7 @@ SDKROOT="$SDK_PATH" CXXFLAGS="-isystem $SDK_PATH/usr/include/c++/v1" \
|
||||
If the build fails on the vendored OpenSSL with an AVX-512 assembler error (common on older VPS hosts):
|
||||
|
||||
```bash
|
||||
OPENSSL_NO_VENDOR=1 cargo build --release --features render
|
||||
OPENSSL_NO_VENDOR=1 cargo build --release -p obscura-cli --bins --features render
|
||||
```
|
||||
|
||||
Uses the system OpenSSL instead.
|
||||
|
||||
@@ -20,10 +20,10 @@ with rendering or a `-no-render-stealth` archive without it. To build the
|
||||
rendering variant yourself:
|
||||
|
||||
```bash
|
||||
cargo build --release --features render,stealth
|
||||
cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
```
|
||||
|
||||
Omit rendering with `cargo build --release --no-default-features --features stealth`.
|
||||
Omit rendering with `cargo build --release -p obscura-cli --bins --no-default-features --features stealth`.
|
||||
|
||||
## What stealth handles
|
||||
|
||||
|
||||
@@ -16,21 +16,21 @@ Official release archives and Docker images include rendering. For a source
|
||||
checkout, build release mode with the render feature:
|
||||
|
||||
```bash
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --features render
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --features render
|
||||
```
|
||||
|
||||
Build rendering and stealth together for the wreq/BoringSSL transport,
|
||||
browser-identity protections, and tracker blocking:
|
||||
|
||||
```bash
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --features render,stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --features render,stealth
|
||||
```
|
||||
|
||||
Build without rendering when only DOM, extraction, or CDP automation is needed:
|
||||
|
||||
```bash
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --no-default-features
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --no-default-features --features stealth
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --no-default-features
|
||||
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --no-default-features --features stealth
|
||||
```
|
||||
|
||||
Use `./target/release/obscura` in the commands below when working from source.
|
||||
|
||||
Reference in New Issue
Block a user