mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 10:40:20 +03:00
Fix HTML loading stalls on large modern pages
- batch asynchronous HTTP stylesheets and apply them in a single reload - skip unsupported block at-rules without leaking nested rules - prevent scripts nested in unknown custom elements from entering the render tree - add regression coverage for unsupported at-rules and nested scripts - document the Ninja release workflow for performance testing
This commit is contained in:
@@ -135,6 +135,17 @@ bool StyleSheetParser::parse( std::string& css, std::vector<std::string>& import
|
||||
} else if ( String::startsWith( trimBuf, "@keyframes" ) ||
|
||||
String::startsWith( trimBuf, "@-webkit-keyframes" ) ) {
|
||||
keyframesParse( css, rs, pos, buffer );
|
||||
} else if ( !String::startsWith( trimBuf, "@font-face" ) &&
|
||||
!String::startsWith( trimBuf, "@glyph-icon" ) ) {
|
||||
// Unsupported block at-rules must not leak their nested contents into the
|
||||
// top-level rule stream. We cannot preserve their conditional or cascade
|
||||
// semantics, so skip the complete balanced block.
|
||||
std::size_t closePos = String::findCloseBracket( css, pos - 1, '{', '}' );
|
||||
if ( closePos != std::string::npos ) {
|
||||
rs = ReadingSelector;
|
||||
pos = closePos + 1;
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -797,6 +797,8 @@ UISceneNode* UISceneNode::setWebResourceCache( WebResourceCachePtr cache,
|
||||
}
|
||||
|
||||
Uint64 UISceneNode::beginDocumentNavigation( const URI& uri ) {
|
||||
mPendingHTTPStyleSheetLoads = 0;
|
||||
mHTTPStyleSheetChanged = false;
|
||||
return mWebResourceCache && mDocumentSessionId
|
||||
? mWebResourceCache->beginNavigation( mDocumentSessionId, uri )
|
||||
: 0;
|
||||
@@ -1916,6 +1918,7 @@ void UISceneNode::loadCSS( URI uri, std::optional<Time> defer,
|
||||
}
|
||||
}
|
||||
} else if ( "http" == uri.getScheme() || "https" == uri.getScheme() ) {
|
||||
mPendingHTTPStyleSheetLoads++;
|
||||
auto resourceState = mAsyncResourceLoadState;
|
||||
Uint64 resourceGeneration =
|
||||
resourceState ? resourceState->generation.load( std::memory_order_acquire ) : 0;
|
||||
@@ -1935,12 +1938,14 @@ void UISceneNode::loadCSS( URI uri, std::optional<Time> defer,
|
||||
resourceState, resourceGeneration,
|
||||
[css = std::move( css ), url, baseURL,
|
||||
sourceOrder]( UISceneNode* scene ) mutable {
|
||||
scene->combineStyleSheet( css, true, String::hash( url ), baseURL,
|
||||
sourceOrder );
|
||||
scene->combineHTTPStyleSheet( css, url, baseURL, sourceOrder );
|
||||
Log::debug( "UISceneNode::loadCSS: Loaded - %s", url );
|
||||
} );
|
||||
} else {
|
||||
Log::debug( "UISceneNode::loadCSS: Failed to load %s - %s", url, result.error );
|
||||
UISceneNode::runAsyncResourceOnMainThread(
|
||||
resourceState, resourceGeneration,
|
||||
[]( UISceneNode* scene ) { scene->finishHTTPStyleSheetLoad(); } );
|
||||
}
|
||||
} );
|
||||
} else if ( VFS::instance()->fileExists( uri.getPath() ) ) {
|
||||
@@ -1956,6 +1961,30 @@ void UISceneNode::loadCSS( URI uri, std::optional<Time> defer,
|
||||
}
|
||||
}
|
||||
|
||||
void UISceneNode::combineHTTPStyleSheet( const std::string& css, const std::string& url,
|
||||
URI baseURI, CSS::StyleSheet::SourceOrder sourceOrder ) {
|
||||
CSS::StyleSheetParser parser;
|
||||
parser.setBaseURI( baseURI );
|
||||
if ( parser.loadFromString( css ) ) {
|
||||
parser.getStyleSheet().setMarker( String::hash( url ) );
|
||||
resolveStyleSheetRelativeURLs( parser.getStyleSheet(), baseURI.empty() ? mURI : baseURI );
|
||||
mStyleSheet.combineStyleSheet( parser.getStyleSheet(), sourceOrder );
|
||||
processStyleSheetAtRules( parser.getStyleSheet(), baseURI );
|
||||
mHTTPStyleSheetChanged = true;
|
||||
}
|
||||
finishHTTPStyleSheetLoad();
|
||||
}
|
||||
|
||||
void UISceneNode::finishHTTPStyleSheetLoad() {
|
||||
if ( mPendingHTTPStyleSheetLoads > 0 )
|
||||
mPendingHTTPStyleSheetLoads--;
|
||||
if ( mPendingHTTPStyleSheetLoads == 0 && mHTTPStyleSheetChanged ) {
|
||||
mHTTPStyleSheetChanged = false;
|
||||
updateStyleSheet( true );
|
||||
refreshWebViewDocumentLayoutAfterStyleChange( mRoot );
|
||||
}
|
||||
}
|
||||
|
||||
void UISceneNode::setInternalPixelsSize( const Sizef& size ) {
|
||||
Sizef s( size );
|
||||
if ( s != mSize ) {
|
||||
|
||||
@@ -523,6 +523,12 @@ void UITextSpan::loadFromXmlNode( const pugi::xml_node& node ) {
|
||||
if ( hasElements ) {
|
||||
for ( pugi::xml_node child = node.first_child(); child; child = child.next_sibling() ) {
|
||||
if ( child.type() == pugi::node_element ) {
|
||||
// Unknown HTML elements are represented by UITextSpan. Keep script elements and
|
||||
// their potentially very large data payloads out of the render tree even when
|
||||
// they are nested below one of those custom elements.
|
||||
if ( String::iequals( child.name(), "script" ) )
|
||||
continue;
|
||||
|
||||
UIWidget* widget = UIWidgetCreator::createFromName( child.name() );
|
||||
|
||||
if ( widget == nullptr )
|
||||
|
||||
@@ -3003,6 +3003,36 @@ UTEST( UIHTML, MarkdownViewLoadsBodyChildrenIntoNativeTree ) {
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTML, ScriptNestedInUnknownElementIsNotRendered ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<html><body><react-app id="app">
|
||||
<script type="application/json">{"payload":"must-not-render"}</script>
|
||||
<span id="visible">Visible</span>
|
||||
</react-app></body></html>
|
||||
)html" ) );
|
||||
|
||||
auto* app = sceneNode->getRoot()->find( "app" );
|
||||
ASSERT_TRUE( app != nullptr );
|
||||
EXPECT_TRUE( app->asType<UIWidget>()->findByTag( "script" ) == nullptr );
|
||||
EXPECT_TRUE( app->find( "visible" ) != nullptr );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTML, UnsupportedBlockAtRulesDoNotLeakNestedRules ) {
|
||||
StyleSheetParser parser;
|
||||
ASSERT_TRUE( parser.loadFromString( std::string_view{
|
||||
"@layer framework { .layered { color: red; } @supports (display: grid) { .nested { "
|
||||
"display: grid; } } } .visible { color: blue; }" } ) );
|
||||
|
||||
EXPECT_TRUE( parser.getStyleSheet().findStyleFromSelectorName( ".layered" ).empty() );
|
||||
EXPECT_TRUE( parser.getStyleSheet().findStyleFromSelectorName( ".nested" ).empty() );
|
||||
EXPECT_EQ( 1u, parser.getStyleSheet().findStyleFromSelectorName( ".visible" ).size() );
|
||||
}
|
||||
|
||||
UTEST( UIHTML, StyleSheetSiblingCombinators ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 768, "CSS Sibling Test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
|
||||
Reference in New Issue
Block a user