[Problem/Bug]: WebResourceRequested is not raised for subresources of a cross-origin iframe in a NewWindow WebView2

Đang mở
#5,787 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
54/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
backend, web-dev

Hướng nghiên cứu

Start by reproducing the issue with the Rust/wry sample in the issue, especially the NewWindowRequested setup and WebResourceRequested filter registration. Compare the logged requests for same-origin and cross-origin iframe subresources in the popup, including with REREGISTER=1. Done means the popup's cross-origin iframe scripts and fetch requests are raised through WebResourceRequested and served by the custom protocol.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

bug
What happened?

In a WebView2 that is passed to CoreWebView2NewWindowRequestedEventArgs.NewWindow (a window.open() popup), WebResourceRequested is not raised for the subresource requests of a cross-origin iframe. The request for the iframe document is raised, but its scripts and fetch() calls are not. When the content is served from WebResourceRequested (a custom host such as http://frame.localhost/), those requests go to the network and fail with net::ERR_CONNECTION_REFUSED.

Every WebView2 gets the filter
AddWebResourceRequestedFilterWithRequestSourceKinds(L"http://frame.localhost/*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL).

Request WebResourceRequested
Cross-origin iframe in a normally created WebView2: document and subresources raised
Same-origin iframe in the popup: document and subresources raised
Cross-origin script loaded by the popup's top-level document raised
Cross-origin iframe in the popup: document raised
Cross-origin iframe in the popup: subresources not raised

The last row is the same for an about:blank popup filled by the opener and for a popup that navigates to its own page.

Adding the filters and a second WebResourceRequested handler again after NewWindow is set, as suggested in #4689, does not change it: the second handler logs the iframe document but none of its subresources.

It looks like #2341 still applies to WebView2s created through NewWindowRequested. Related: #4689, #4181.

Found through Tauri / wry, which implement custom protocols this way, so a cross-origin iframe in any window.open() window of such an app cannot load its resources.

Importance

Moderate. My app's user experience is affected, but still usable.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

156.0.4314.50

SDK Version

1.0.3650.58

Framework

Win32

Operating System

Windows 11

OS Version

10.0.26300.9711

Repro steps

Rust, wry 0.55.1 with webview2-com 0.38.2 (SDK 1.0.3650.58). Sequence per WebView2, as wry does it:

  1. Create the controller with the opener's environment.
  2. AddWebResourceRequestedFilterWithRequestSourceKinds for http://app.localhost/* and http://frame.localhost/* (context ALL, source kinds ALL), then add_WebResourceRequested, which serves both hosts.
  3. In NewWindowRequested: take a deferral, create the popup WebView2 the same way (steps 1 and 2), set args.NewWindow, complete the deferral.

The main page (http://app.localhost/) opens three popups. Each iframe reports whether its script loaded and whether its fetch() succeeded.

cargo run:

WebView2 156.0.4314.50
1-cross-origin-iframe-in-main-window: frame.js loaded, fetch: ok
3-cross-origin-iframe-in-about-blank-popup: frame.js FAILED, fetch: error Failed to fetch
2-same-origin-iframe-in-about-blank-popup: frame.js loaded, fetch: ok
4-cross-origin-iframe-in-about-blank-popup-after-1s: frame.js FAILED, fetch: error Failed to fetch
5-popup-page-itself: cross-origin frame.js loaded
6-cross-origin-iframe-in-popup-page: frame.js FAILED, fetch: error Failed to fetch

With REREGISTER=1, the filters and a second handler that only logs are added again after NewWindow is set. It sees the documents of the cross-origin iframes and the subresources of the same-origin iframe, but never frame.localhost/frame.js or frame.localhost/data.txt from inside an iframe:

WebView2 156.0.4314.50
1-cross-origin-iframe-in-main-window: frame.js loaded, fetch: ok
  seen by the handler added after SetNewWindow: http://app.localhost/frame.html?2-same-origin-iframe-in-about-blank-popup
  seen by the handler added after SetNewWindow: http://frame.localhost/frame.html?3-cross-origin-iframe-in-about-blank-popup
  seen by the handler added after SetNewWindow: http://app.localhost/frame.js
  seen by the handler added after SetNewWindow: http://app.localhost/data.txt
3-cross-origin-iframe-in-about-blank-popup: frame.js FAILED, fetch: error Failed to fetch
2-same-origin-iframe-in-about-blank-popup: frame.js loaded, fetch: ok
  seen by the handler added after SetNewWindow: http://frame.localhost/frame.html?4-cross-origin-iframe-in-about-blank-popup-after-1s
4-cross-origin-iframe-in-about-blank-popup-after-1s: frame.js FAILED, fetch: error Failed to fetch
  seen by the handler added after SetNewWindow: http://app.localhost/popup.html
  seen by the handler added after SetNewWindow: http://frame.localhost/frame.js
5-popup-page-itself: cross-origin frame.js loaded
  seen by the handler added after SetNewWindow: http://frame.localhost/frame.html?6-cross-origin-iframe-in-popup-page
6-cross-origin-iframe-in-popup-page: frame.js FAILED, fetch: error Failed to fetch
  seen by the handler added after SetNewWindow: http://app.localhost/favicon.ico

(http://frame.localhost/frame.js in this log is the script of the popup's top-level page, case 5.)

Cargo.toml

[package]
name = "popup-iframe-protocol"
version = "0.1.0"
edition = "2021"

[dependencies]
wry = "=0.55.1"
tao = "0.35"
webview2-com = "=0.38.2"
windows-core = "0.61"

src/main.rs

use std::{borrow::Cow, cell::{Cell, RefCell}, rc::Rc, time::Duration};
use tao::{
    event::Event,
    event_loop::{ControlFlow, EventLoopBuilder},
    window::WindowBuilder,
};
use webview2_com::{take_pwstr, Microsoft::Web::WebView2::Win32::*, WebResourceRequestedEventHandler};
use windows_core::{Interface, HSTRING, PWSTR};
use wry::{
    http::{header::CONTENT_TYPE, Request, Response},
    NewWindowResponse, WebViewBuilder, WebViewBuilderExtWindows, WebViewExtWindows, WebViewId,
};

enum Ev {
    Exit,
    AfterSetNewWindow,
}

// REREGISTER=1: once wry has called SetNewWindow, add the filters again and a second handler that only logs.
unsafe fn reregister(webview: &ICoreWebView2) {
    let w22: ICoreWebView2_22 = webview.cast().unwrap();
    for f in ["http://app.localhost/*", "http://frame.localhost/*"] {
        w22.AddWebResourceRequestedFilterWithRequestSourceKinds(
            &HSTRING::from(f),
            COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL,
            COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL,
        )
        .unwrap();
    }
    let mut token = 0i64;
    webview
        .add_WebResourceRequested(
            &WebResourceRequestedEventHandler::create(Box::new(|_, args| {
                if let Some(args) = args {
                    let mut uri = PWSTR::null();
                    args.Request()?.Uri(&mut uri)?;
                    println!("  seen by the handler added after SetNewWindow: {}", take_pwstr(uri));
                }
                Ok(())
            })),
            &mut token,
        )
        .unwrap();
}

// The page is served by the `app` protocol (http://app.localhost/), the iframes by `frame`
// (http://frame.localhost/, cross-origin) or by `app` (same-origin). Both protocols serve the same files.
const MAIN: &str = r#"<!doctype html><title>main</title>
<iframe src="http://frame.localhost/frame.html?1-cross-origin-iframe-in-main-window"></iframe>
<script>
addEventListener('message', (e) => window.ipc.postMessage(String(e.data)));
const frame = (doc, origin, name) => {
  const f = doc.createElement('iframe');
  f.src = origin + '/frame.html?' + name;
  doc.body.append(f);
};
setTimeout(() => {
  // about:blank popup, filled by the opener right after window.open()
  const w = window.open('about:blank', 'a', 'popup,width=500,height=300');
  w.document.open(); w.document.write('<!doctype html><title>a</title>'); w.document.close();
  frame(w.document, 'http://app.localhost', '2-same-origin-iframe-in-about-blank-popup');
  frame(w.document, 'http://frame.localhost', '3-cross-origin-iframe-in-about-blank-popup');
}, 500);
setTimeout(() => {
  const w = window.open('about:blank', 'b', 'popup,width=500,height=300');
  setTimeout(() => frame(w.document, 'http://frame.localhost', '4-cross-origin-iframe-in-about-blank-popup-after-1s'), 1000);
}, 1500);
setTimeout(() => window.open('popup.html', 'c', 'popup,width=500,height=300'), 3500);
</script>"#;

const POPUP: &str = r#"<!doctype html><title>c</title>
<script src="http://frame.localhost/frame.js"></script>
<script>opener.postMessage('5-popup-page-itself: cross-origin frame.js ' + (window.frameJs ? 'loaded' : 'FAILED'), '*');</script>
<iframe src="http://frame.localhost/frame.html?6-cross-origin-iframe-in-popup-page"></iframe>"#;

const FRAME: &str = r#"<!doctype html>
<script src="frame.js"></script>
<script>
fetch('data.txt').then((r) => r.text(), (e) => 'error ' + e.message).then((f) =>
  (top.opener || top).postMessage(location.search.slice(1) + ': frame.js ' +
    (window.frameJs ? 'loaded' : 'FAILED') + ', fetch: ' + f, '*'));
</script>"#;

fn serve(_id: WebViewId, req: Request<Vec<u8>>) -> Response<Cow<'static, [u8]>> {
    let (mime, body) = match req.uri().path() {
        "/" => ("text/html", MAIN),
        "/popup.html" => ("text/html", POPUP),
        "/frame.html" => ("text/html", FRAME),
        "/frame.js" => ("text/javascript", "window.frameJs = true;"),
        "/data.txt" => ("text/plain", "ok"),
        _ => ("text/plain", ""),
    };
    Response::builder()
        .header(CONTENT_TYPE, mime)
        .header("Access-Control-Allow-Origin", "*")
        .body(Cow::Borrowed(body.as_bytes()))
        .unwrap()
}

fn main() -> wry::Result<()> {
    println!("WebView2 {}", wry::webview_version()?);
    let event_loop = EventLoopBuilder::<Ev>::with_user_event().build();
    let proxy = event_loop.create_proxy();
    let timeout = proxy.clone();
    let after = proxy.clone();
    std::thread::spawn(move || {
        std::thread::sleep(Duration::from_secs(10));
        let _ = timeout.send_event(Ev::Exit);
    });

    // Windows for the popups, created up front and shown when the popup is requested.
    let spare = Rc::new(RefCell::new(
        (0..3)
            .map(|_| WindowBuilder::new().with_visible(false).build(&event_loop).unwrap())
            .collect::<Vec<_>>(),
    ));
    let keep = Rc::new(RefCell::new(Vec::new()));
    let reports = Rc::new(Cell::new(0));

    let main_window = WindowBuilder::new().with_title("main").build(&event_loop).unwrap();
    let keep_ = keep.clone();
    let _main = WebViewBuilder::new()
        .with_custom_protocol("app".into(), serve)
        .with_custom_protocol("frame".into(), serve)
        .with_url("http://app.localhost/")
        .with_ipc_handler(move |req: Request<String>| {
            println!("{}", req.body());
            reports.set(reports.get() + 1);
            if reports.get() == 6 {
                let _ = proxy.send_event(Ev::Exit);
            }
        })
        .with_new_window_req_handler(move |_url, features| {
            let Some(window) = spare.borrow_mut().pop() else {
                return NewWindowResponse::Deny;
            };
            let webview = WebViewBuilder::new()
                .with_environment(features.opener.environment.clone())
                .with_custom_protocol("app".into(), serve)
                .with_custom_protocol("frame".into(), serve)
                .build(&window)
                .unwrap();
            window.set_visible(true);
            let core = webview.webview();
            keep_.borrow_mut().push((window, webview));
            if std::env::var_os("REREGISTER").is_some() {
                let _ = after.send_event(Ev::AfterSetNewWindow);
            }
            NewWindowResponse::Create { webview: core }
        })
        .build(&main_window)?;

    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Wait;
        match event {
            Event::UserEvent(Ev::Exit) => *control_flow = ControlFlow::Exit,
            Event::UserEvent(Ev::AfterSetNewWindow) => {
                if let Some((_, webview)) = keep.borrow().last() {
                    unsafe { reregister(&webview.webview()) };
                }
            }
            _ => {}
        }
    });
}
Repros in Edge Browser

No, issue does not reproduce in the corresponding Edge version

Regression

Don't know

Last working version (if regression)

No response

Ngôn ngữ chính
PowerShell
Star
526
Fork
68
Merge trung bình
15 ngày 5 giờ
Pull request đã merge (30 ngày)
1

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của MicrosoftEdge/WebView2Feedback

Tất cả issue của MicrosoftEdge/WebView2Feedback

Issue tương tự

Thêm issue về Backend & API Design

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.