Add support for MimeHandlerViewInCrossProcessFrame

Issue #2727 resolved
Thomas Treutlein created an issue

There are different html tags to embed PDFs into html pages.

With the latest cefclient.exe application (75.0.13 or 75.0.14) the PDFs are not shown anymore if embedded via <embed> or <object> element.

It works with cefclient.exe --disable-features=NetworkService and it works with version 74.X and lower.

Open https://pdfobject.com/static/ in cefclient.exe to reproduce. Not sure if this is Chromium or CEF related.

Comments (17)

  1. Marshall Greenblatt

    Approach 1

    Chrome uses SubframeNavigationFilteringThrottle, created via this call stack, to handle these tags:

    >   chrome.dll!subresource_filter::SubframeNavigationFilteringThrottle::SubframeNavigationFilteringThrottle(content::NavigationHandle * handle, subresource_filter::AsyncDocumentSubresourceFilter * parent_frame_filter, subresource_filter::SubframeNavigationFilteringThrottle::Delegate * delegate) Line 33 C++
        chrome.dll!std::__1::make_unique<subresource_filter::SubframeNavigationFilteringThrottle,content::NavigationHandle *&,subresource_filter::AsyncDocumentSubresourceFilter *&,subresource_filter::ContentSubresourceFilterThrottleManager *>(content::NavigationHandle * & __args, subresource_filter::AsyncDocumentSubresourceFilter * & __args, subresource_filter::ContentSubresourceFilterThrottleManager * && __args) Line 3131  C++
        chrome.dll!subresource_filter::ContentSubresourceFilterThrottleManager::MaybeCreateSubframeNavigationFilteringThrottle(content::NavigationHandle * navigation_handle) Line 292  C++
        chrome.dll!subresource_filter::ContentSubresourceFilterThrottleManager::MaybeAppendNavigationThrottles(content::NavigationHandle * navigation_handle, std::__1::vector<std::__1::unique_ptr<content::NavigationThrottle,std::__1::default_delete<content::NavigationThrottle> >,std::__1::allocator<std::__1::unique_ptr<content::NavigationThrottle,std::__1::default_delete<content::NavigationThrottle> > > > * throttles) Line 253  C++
        chrome.dll!ChromeSubresourceFilterClient::MaybeAppendNavigationThrottles(content::NavigationHandle * navigation_handle, std::__1::vector<std::__1::unique_ptr<content::NavigationThrottle,std::__1::default_delete<content::NavigationThrottle> >,std::__1::allocator<std::__1::unique_ptr<content::NavigationThrottle,std::__1::default_delete<content::NavigationThrottle> > > > * throttles) Line 85 C++
        chrome.dll!ChromeContentBrowserClient::CreateThrottlesForNavigation(content::NavigationHandle * handle) Line 4271   C++
    

    The request for the PDF file then continues asynchronously via this code path:

    • SubframeNavigationFilteringThrottle::DeferToCalculateLoadPolicy
    • NavigationURLLoaderImpl::NavigationURLLoaderImpl
    • NavigationURLLoaderImpl::URLLoaderRequestController::Start
    • NavigationURLLoaderImpl::URLLoaderRequestController::MaybeStartLoader
    • ThrottlingURLLoader::CreateLoaderAndStart
    • ThrottlingURLLoader::OnReceiveResponse
    • PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse

    Then proceeding with the plugin load as documented in CEF’s extension_system.cc.

    Approach 2

    Running Chrome with --disable-features=SubresourceFilter the SubframeNavigationFilteringThrottle class is not used and the PDF request is initiated via RenderFrameHostImpl::BeginNavigation instead. This call originates from the renderer process with the following call stack:

    >   content.dll!content::RenderFrameImpl::BeginNavigationInternal(std::__1::unique_ptr<blink::WebNavigationInfo,std::__1::default_delete<blink::WebNavigationInfo> > info, bool is_history_navigation_in_new_child_frame) Line 7161 C++
        content.dll!content::RenderFrameImpl::BeginNavigation(std::__1::unique_ptr<blink::WebNavigationInfo,std::__1::default_delete<blink::WebNavigationInfo> > info) Line 6525    C++
        blink_core.dll!blink::LocalFrameClientImpl::BeginNavigation(const blink::ResourceRequest & request, network::mojom::RequestContextFrameType frame_type, blink::Document * origin_document, blink::DocumentLoader * document_loader, blink::WebNavigationType type, blink::NavigationPolicy policy, bool has_transient_activation, blink::WebFrameLoadType frame_load_type, bool is_client_redirect, blink::WebTriggeringEventInfo triggering_event_info, blink::HTMLFormElement * form, blink::ContentSecurityPolicyDisposition should_check_main_world_content_security_policy, mojo::InterfacePtr<blink::mojom::blink::BlobURLToken> blob_url_token, base::TimeTicks input_start_time, const WTF::String & href_translate, blink::WebContentSecurityPolicyList initiator_csp, mojo::InterfacePtr<blink::mojom::blink::NavigationInitiator> navigation_initiator) Line 614 C++
        blink_core.dll!blink::FrameLoader::StartNavigation(const blink::FrameLoadRequest & passed_request, blink::WebFrameLoadType frame_load_type) Line 786    C++
        blink_core.dll!blink::HTMLFrameOwnerElement::LoadOrRedirectSubframe(const blink::KURL & url, const WTF::AtomicString & frame_name, bool replace_current_item) Line 495  C++
        blink_core.dll!blink::HTMLPlugInElement::RequestObjectInternal(const blink::PluginParameters & plugin_params) Line 208  C++
        blink_core.dll!blink::HTMLPlugInElement::RequestObject(const blink::PluginParameters & plugin_params) Line 622  C++
        blink_core.dll!blink::HTMLEmbedElement::UpdatePluginInternal() Line 178 C++
        blink_core.dll!blink::HTMLPlugInElement::UpdatePlugin() Line 304    C++
        blink_core.dll!blink::LocalFrameView::UpdatePlugins() Line 1736 C++
        blink_core.dll!blink::LocalFrameView::UpdatePluginsTimerFired(blink::TimerBase *) Line 1750 C++
    

    HTMLPlugInElement::RequestObjectInternal calls GetObjectContentType which returns kExternalPlugin in Chrome and kPlugin in CEF (because PluginData::IsExternalPluginMimeType and PluginInfo::MayUseExternalHandler returns false).

    So far, the fix is as follows:

    1. Implement ContentBrowserClient::GetPluginMimeTypesWithExternalHandlers.
    2. Implement ContentRendererClient::IsPluginHandledExternally
    3. Run with --enable-features=MimeHandlerViewInCrossProcessFrame

    That causes the PDF extension to load in CEF but the PDF plugin still fails to load. This may be due to a dependency on cross-process iframe support (see issue #2498).

    Approach 3

    Running Chrome with --disable-features=SubresourceFilter,MimeHandlerViewInCrossProcessFrame causes GetObjectContentType to return kPlugin like in CEF. However, in Chrome the PDF resource is routed to PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse via ThrottlingURLLoader::OnReceiveResponse while in CEF the PDF resource is routed to ResourceLoader::DidReceiveData and MimeHandlerViewContainerBase::DidReceiveData in the renderer process.

    Chrome has 4 throttles registered in ThrottlingURLLoader:

    • safe_browsing::RendererURLLoaderThrottle
    • extensions::MimeHandlerViewContainerBase::PluginResourceThrottle
    • GoogleURLLoaderThrottle
    • content::MimeSniffingThrottle

    CEF only has the MimeSniffingThrottle.

    So, the fix is to implement ContentRendererClient::CreateURLLoaderThrottleProvider and return a MimeHandlerViewContainerBase::PluginResourceThrottle.

  2. Marshall Greenblatt
    • changed status to open

    We can use this issue to track support for MimeHandlerViewInCrossProcessFrame (which is currently disabled).

  3. Marshall Greenblatt

    Removal of the flag means that we will now need to support this feature instead of disabling it.

  4. Marshall Greenblatt

    The problem with Approach 2 above is that MimeHandlerViewEmbedder::ReadyToCreateMimeHandlerView is not being called, which causes MimeHandlerViewEmbedder::RenderFrameCreated to bail out early instead of creating the MimeHandlerViewGuest. MimeHandlerViewEmbedder::ReadyToCreateMimeHandlerView should be called by ExtensionsGuestViewMessageFilter::ReadyToCreateMimeHandlerView, which originates from MimeHandlerViewContainerManager::IsManagedByContainerManager in the renderer process. However, that method is skipping the ReadyToCreateMimeHandlerView call in CEF because internal_id_ is empty.

    The internal_id_ value should arrive via MimeHandlerViewContainerManager::SetInternalId which is called from MimeHandlerViewEmbedder::ReadyToCommitNavigation in the browser process. However, in CEF the SetInternalId call never arrives.

    Root problem: In chrome the MimeHandlerViewContainerManager is created via ChromeRenderFrameObserver::OnAssociatedInterfaceRequestForFrame before SetInternalId is called whereas whereas in CEF the MimeHandlerViewContainerManager is created via ChromeExtensionsRendererClient::MaybeCreateMimeHandlerView after SetInternalId is called.IsManagedByContainerManager is then called later on by the same call stack:

    >   libcef.dll!extensions::MimeHandlerViewContainerManager::IsManagedByContainerManager(const blink::WebElement & plugin_element) Line 322  C++
        libcef.dll!extensions::MimeHandlerViewContainerManager::CreateFrameContainer(const blink::WebElement & plugin_element, const GURL & resource_url, const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & mime_type, const content::WebPluginInfo & plugin_info) Line 87 C++
        libcef.dll!ChromeExtensionsRendererClient::MaybeCreateMimeHandlerView(const blink::WebElement & plugin_element, const GURL & resource_url, const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & mime_type, const content::WebPluginInfo & plugin_info) Line 281   C++
        libcef.dll!CefContentRendererClient::IsPluginHandledExternally(content::RenderFrame * render_frame, const blink::WebElement & plugin_element, const GURL & original_url, const std::__1::basic_string<char,std::__1::char_traits<char>,std::__1::allocator<char>> & mime_type) Line 550 C++
        content.dll!content::RenderFrameImpl::IsPluginHandledExternally(const blink::WebElement & plugin_element, const blink::WebURL & url, const blink::WebString & suggested_mime_type) Line 4085    C++
        blink_core.dll!blink::LocalFrameClientImpl::IsPluginHandledExternally(blink::HTMLPlugInElement & plugin_element, const blink::KURL & resource_url, const WTF::String & suggesed_mime_type) Line 1192    C++
        blink_core.dll!blink::HTMLPlugInElement::RequestObject(const blink::PluginParameters & plugin_params) Line 574  C++
        blink_core.dll!blink::HTMLEmbedElement::UpdatePluginInternal() Line 177 C++
        blink_core.dll!blink::HTMLPlugInElement::UpdatePlugin() Line 245    C++
        blink_core.dll!blink::LocalFrameView::UpdatePlugins() Line 1762 C++
        blink_core.dll!blink::LocalFrameView::UpdatePluginsTimerFired(blink::TimerBase *) Line 1776 C++
    

  5. Marshall Greenblatt

    Another problem with debugging this issue. When the debugger is attached to the renderer process on Windows (e.g. with --wait-for-debugger-children=renderer) the extension LoadURL initiated by MimeHandlerViewGuest::DidAttachToEmbedder (which calls DispatchBeforeUnload in NavigatorImpl::Navigate) will hang. This is because the WebContentsImpl::ShouldIgnoreUnresponsiveRenderer method returns true and NavigatorImpl::OnBeforeUnloadACK is never called. Normally, a navigation should result in a call to RenderFrameHostImpl::OnBeforeUnloadACK. However, for some reason this ACK is never received for the extension load (in CEF or chrome) and RenderFrameHostImpl::BeforeUnloadTimeout is instead called to trigger a timeout, allowing the load to proceed with ExtensionURLLoaderFactory::CreateLoaderAndStart.

  6. Marshall Greenblatt

    The next issue is that we no longer are notified of guest view routes via CefMimeHandlerViewGuestDelegate::OnGuestAttached which was previously called before CefBrowserInfoManager::OnGetNewBrowserInfo. This causes CefContentRendererClient::MaybeCreateBrowser to hang on the sync CefProcessHostMsg_GetNewBrowserInfo message in the extension renderer process, because information for that browser never becomes available.

  7. Marshall Greenblatt

    It doesn’t appear that SubresourceFilter is required for the MimeHandlerViewInCrossProcessFrame implementation. Since SubresourceFilter has a dependency on the component updater service (via RegisterSubresourceFilterComponent) it will bail out early in ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation with result mojom::ActivationLevel::kDisabled if the data hasn’t already been downloaded.

  8. Marshall Greenblatt

    The attached patches to CEF and Chromium add support for SubresourceFilter and sufficient component installer support to download the filter data on startup. This is for future reference since the changes were not required to resolve this issue.

    Not included in this patch is SubresourceRedirectURLLoaderThrottle which probably also needs to be added in CefURLLoaderThrottleProviderImpl::CreateThrottles.

  9. Marshall Greenblatt

    Add support for MimeHandlerViewInCrossProcessFrame (fixes issue #2727)

    The PDF loading documentation in extension_system.cc has be updated to describe the new code paths.

    To support delivery of input events to the mime handler renderer process it is now necessary to route events via the correct RWHV interface. For Aura-based platforms (Windows/Linux) this means RWHVAura::OnEvent and for macOS this means RWHVMac::RouteOrProcessEvent. Since Aura uses UI event types these have become the source of truth on Aura-based platforms with conversion to Web event types when needed (primarily for OSR).

    This change also adds a timeout for CefProcessHostMsg_GetNewBrowserInfo to avoid a hung renderer process if the guest WebContents route is not registered via CefMimeHandlerViewGuestDelegate::OnGuestDetached as expected prior to CefBrowserInfoManager::OnGetNewBrowserInfo being called. This timeout can be disabled for testing purposes by passing the --disable-new-browser-info-timeout command-line flag.

    The --disable-features=MimeHandlerViewInCrossProcessFrame command-line flag can be used for a limited time to restore the previous implementation based on BrowserPlugin. That implementation will be deleted starting with the 3897 branch update.

    Known issues: - ExecuteJavaScript calls on the frame hosting the PDF extension will not be routed to the mime handler renderer process. - The PDF extension will not load successfully if blocked by ChromePluginPlaceholder and then manually continued via the "Run this plugin" context menu option (see https://crbug.com/533069#c41).

    → <<cset a12c2ab3e131>>

  10. Log in to comment