diff --git a/src/main/java/org/htmlunit/WebClient.java b/src/main/java/org/htmlunit/WebClient.java index 1dce1190e33..6c23e5f5ac5 100644 --- a/src/main/java/org/htmlunit/WebClient.java +++ b/src/main/java/org/htmlunit/WebClient.java @@ -27,6 +27,7 @@ import static org.htmlunit.BrowserVersionFeatures.WINDOW_EXECUTE_EVENTS; import java.io.BufferedInputStream; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -71,12 +72,13 @@ import org.htmlunit.corejs.javascript.ScriptableObject; import org.htmlunit.css.ComputedCssStyleDeclaration; import org.htmlunit.cssparser.parser.CSSErrorHandler; +import org.htmlunit.cssparser.parser.javacc.CSS3Parser; import org.htmlunit.html.BaseFrameElement; import org.htmlunit.html.DomElement; import org.htmlunit.html.DomNode; import org.htmlunit.html.FrameWindow; -import org.htmlunit.html.HtmlElement; import org.htmlunit.html.FrameWindow.PageDenied; +import org.htmlunit.html.HtmlElement; import org.htmlunit.html.HtmlInlineFrame; import org.htmlunit.html.HtmlPage; import org.htmlunit.html.XHtmlPage; @@ -204,6 +206,10 @@ public class WebClient implements Serializable, AutoCloseable { private OnbeforeunloadHandler onbeforeunloadHandler_; private Cache cache_ = new Cache(); + // mini pool to save resource when parsing css + private transient PooledCSS3Parser firstPooledCSS3Parser_ = new PooledCSS3Parser(); + private transient PooledCSS3Parser secondPooledCSS3Parser_ = new PooledCSS3Parser(); + /** target "_blank". */ public static final String TARGET_BLANK = "_blank"; @@ -2944,4 +2950,50 @@ public XHtmlPage loadXHtmlCodeIntoCurrentWindow(final String xhtmlCode) throws I htmlParser.parse(webResponse, page, true, false); return page; } + + /** + * INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
+ * + * @return CSS3Parser from pool + */ + public PooledCSS3Parser getCSS3ParserFromPool() { + if (!firstPooledCSS3Parser_.inUse_) { + if (firstPooledCSS3Parser_.open()) { + return firstPooledCSS3Parser_; + } + } + if (!secondPooledCSS3Parser_.inUse_) { + if (secondPooledCSS3Parser_.open()) { + return secondPooledCSS3Parser_; + } + } + + return new PooledCSS3Parser(); + } + + public static class PooledCSS3Parser implements Closeable { + private final CSS3Parser css3Parser_; + private boolean inUse_; + + public PooledCSS3Parser() { + css3Parser_ = new CSS3Parser(); + } + + public CSS3Parser css3Parser() { + return css3Parser_; + } + + public synchronized boolean open() { + if (inUse_) { + return false; + } + inUse_ = true; + return true; + } + + @Override + public void close() throws IOException { + inUse_ = false; + } + } } diff --git a/src/main/java/org/htmlunit/css/CssStyleSheet.java b/src/main/java/org/htmlunit/css/CssStyleSheet.java index aa90bc88c19..a2051b7732b 100644 --- a/src/main/java/org/htmlunit/css/CssStyleSheet.java +++ b/src/main/java/org/htmlunit/css/CssStyleSheet.java @@ -56,6 +56,7 @@ import org.htmlunit.Page; import org.htmlunit.SgmlPage; import org.htmlunit.WebClient; +import org.htmlunit.WebClient.PooledCSS3Parser; import org.htmlunit.WebRequest; import org.htmlunit.WebResponse; import org.htmlunit.WebWindow; @@ -1011,9 +1012,9 @@ private static boolean getNthElement(final String nth, final int index) { */ private static CSSStyleSheetImpl parseCSS(final InputSource source, final WebClient client) { CSSStyleSheetImpl ss; - try { + try (PooledCSS3Parser pooledParser = client.getCSS3ParserFromPool()) { final CSSErrorHandler errorHandler = client.getCssErrorHandler(); - final CSSOMParser parser = new CSSOMParser(new CSS3Parser()); + final CSSOMParser parser = new CSSOMParser(pooledParser.css3Parser()); parser.setErrorHandler(errorHandler); ss = parser.parseStyleSheet(source, null); } @@ -1026,6 +1027,39 @@ private static CSSStyleSheetImpl parseCSS(final InputSource source, final WebCli return ss; } + /** + * Parses the given media string. If anything at all goes wrong, this + * method returns an empty MediaList list. + * + * @param mediaString the source from which to retrieve the media to be parsed + * @param webClient the {@link WebClient} to be used + * @return the media parsed from the specified input source + */ + public static MediaListImpl parseMedia(final String mediaString, final WebClient webClient) { + MediaListImpl media = media_.get(mediaString); + if (media != null) { + return media; + } + + try (PooledCSS3Parser pooledParser = webClient.getCSS3ParserFromPool()) { + final CSSOMParser parser = new CSSOMParser(pooledParser.css3Parser()); + parser.setErrorHandler(webClient.getCssErrorHandler()); + + media = new MediaListImpl(parser.parseMedia(mediaString)); + media_.put(mediaString, media); + return media; + } + catch (final Exception e) { + if (LOG.isErrorEnabled()) { + LOG.error("Error parsing CSS media from '" + mediaString + "': " + e.getMessage(), e); + } + } + + media = new MediaListImpl(null); + media_.put(mediaString, media); + return media; + } + /** * Parses the given media string. If anything at all goes wrong, this * method returns an empty MediaList list. @@ -1033,7 +1067,10 @@ private static CSSStyleSheetImpl parseCSS(final InputSource source, final WebCli * @param mediaString the source from which to retrieve the media to be parsed * @param errorHandler the {@link CSSErrorHandler} to be used * @return the media parsed from the specified input source + * + * @deprecated as of version 3.8.0; use {@link #parseMedia(String, WebClient)} instead */ + @Deprecated public static MediaListImpl parseMedia(final CSSErrorHandler errorHandler, final String mediaString) { MediaListImpl media = media_.get(mediaString); if (media != null) { @@ -1241,7 +1278,7 @@ else if (owner_ instanceof HtmlLink) { } final WebWindow webWindow = owner_.getPage().getEnclosingWindow(); - final MediaListImpl mediaList = parseMedia(webWindow.getWebClient().getCssErrorHandler(), media); + final MediaListImpl mediaList = parseMedia(media, webWindow.getWebClient()); return isActive(mediaList, webWindow); } diff --git a/src/main/java/org/htmlunit/html/DomElement.java b/src/main/java/org/htmlunit/html/DomElement.java index 9437e80e541..94c4d59ba3f 100644 --- a/src/main/java/org/htmlunit/html/DomElement.java +++ b/src/main/java/org/htmlunit/html/DomElement.java @@ -1593,12 +1593,12 @@ public boolean isMouseOver() { */ public boolean matches(final String selectorString) { try { - final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); - final SelectorList selectorList = getSelectorList(selectorString, browserVersion); + final WebClient webClient = getPage().getWebClient(); + final SelectorList selectorList = getSelectorList(selectorString, webClient); if (selectorList != null) { for (final Selector selector : selectorList) { - if (CssStyleSheet.selects(browserVersion, selector, this, null, true, true)) { + if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, this, null, true, true)) { return true; } } diff --git a/src/main/java/org/htmlunit/html/DomNode.java b/src/main/java/org/htmlunit/html/DomNode.java index 57f579f0651..d8ab0afafd3 100644 --- a/src/main/java/org/htmlunit/html/DomNode.java +++ b/src/main/java/org/htmlunit/html/DomNode.java @@ -32,13 +32,13 @@ import java.util.Map; import java.util.NoSuchElementException; -import org.htmlunit.BrowserVersion; import org.htmlunit.BrowserVersionFeatures; import org.htmlunit.IncorrectnessListener; import org.htmlunit.Page; import org.htmlunit.SgmlPage; import org.htmlunit.WebAssert; import org.htmlunit.WebClient; +import org.htmlunit.WebClient.PooledCSS3Parser; import org.htmlunit.WebWindow; import org.htmlunit.corejs.javascript.Context; import org.htmlunit.corejs.javascript.ScriptableObject; @@ -49,7 +49,6 @@ import org.htmlunit.cssparser.parser.CSSException; import org.htmlunit.cssparser.parser.CSSOMParser; import org.htmlunit.cssparser.parser.CSSParseException; -import org.htmlunit.cssparser.parser.javacc.CSS3Parser; import org.htmlunit.cssparser.parser.selector.Selector; import org.htmlunit.cssparser.parser.selector.SelectorList; import org.htmlunit.html.HtmlElement.DisplayStyle; @@ -1835,14 +1834,14 @@ private List safeGetCharacterDataListeners() { */ public DomNodeList querySelectorAll(final String selectors) { try { - final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); - final SelectorList selectorList = getSelectorList(selectors, browserVersion); + final WebClient webClient = getPage().getWebClient(); + final SelectorList selectorList = getSelectorList(selectors, webClient); final List elements = new ArrayList<>(); if (selectorList != null) { for (final DomElement child : getDomElementDescendants()) { for (final Selector selector : selectorList) { - if (CssStyleSheet.selects(browserVersion, selector, child, null, true, true)) { + if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, child, null, true, true)) { elements.add(child); break; } @@ -1859,34 +1858,36 @@ public DomNodeList querySelectorAll(final String selectors) { /** * Returns the {@link SelectorList}. * @param selectors the selectors - * @param browserVersion the {@link BrowserVersion} + * @param webClient the {@link WebClient} * @return the {@link SelectorList} * @throws IOException if an error occurs */ - protected SelectorList getSelectorList(final String selectors, final BrowserVersion browserVersion) + protected SelectorList getSelectorList(final String selectors, final WebClient webClient) throws IOException { - final CSSOMParser parser = new CSSOMParser(new CSS3Parser()); - final CheckErrorHandler errorHandler = new CheckErrorHandler(); - parser.setErrorHandler(errorHandler); - - final SelectorList selectorList = parser.parseSelectors(selectors); - // in case of error parseSelectors returns null - if (errorHandler.errorDetected()) { - throw new CSSException("Invalid selectors: '" + selectors + "'"); - } - - if (selectorList != null) { - int documentMode = 9; - if (browserVersion.hasFeature(QUERYSELECTORALL_NOT_IN_QUIRKS)) { - final Object sobj = getPage().getScriptableObject(); - if (sobj instanceof HTMLDocument) { - documentMode = ((HTMLDocument) sobj).getDocumentMode(); - } + try (PooledCSS3Parser pooledParser = webClient.getCSS3ParserFromPool()) { + final CSSOMParser parser = new CSSOMParser(pooledParser.css3Parser()); + final CheckErrorHandler errorHandler = new CheckErrorHandler(); + parser.setErrorHandler(errorHandler); + + final SelectorList selectorList = parser.parseSelectors(selectors); + // in case of error parseSelectors returns null + if (errorHandler.errorDetected()) { + throw new CSSException("Invalid selectors: '" + selectors + "'"); } - CssStyleSheet.validateSelectors(selectorList, documentMode, this); + if (selectorList != null) { + int documentMode = 9; + if (webClient.getBrowserVersion().hasFeature(QUERYSELECTORALL_NOT_IN_QUIRKS)) { + final Object sobj = getPage().getScriptableObject(); + if (sobj instanceof HTMLDocument) { + documentMode = ((HTMLDocument) sobj).getDocumentMode(); + } + } + CssStyleSheet.validateSelectors(selectorList, documentMode, this); + + } + return selectorList; } - return selectorList; } /** @@ -2009,15 +2010,15 @@ public DomElement getNextElementSibling() { */ public DomElement closest(final String selectorString) { try { - final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); - final SelectorList selectorList = getSelectorList(selectorString, browserVersion); + final WebClient webClient = getPage().getWebClient(); + final SelectorList selectorList = getSelectorList(selectorString, webClient); DomNode current = this; if (selectorList != null) { do { for (final Selector selector : selectorList) { final DomElement elem = (DomElement) current; - if (CssStyleSheet.selects(browserVersion, selector, elem, null, true, true)) { + if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, elem, null, true, true)) { return elem; } } diff --git a/src/main/java/org/htmlunit/html/HtmlLink.java b/src/main/java/org/htmlunit/html/HtmlLink.java index 8d9ad540250..d98aac7a99f 100644 --- a/src/main/java/org/htmlunit/html/HtmlLink.java +++ b/src/main/java/org/htmlunit/html/HtmlLink.java @@ -372,7 +372,7 @@ public boolean isActiveStyleSheetLink() { } final MediaListImpl mediaList = - CssStyleSheet.parseMedia(getPage().getWebClient().getCssErrorHandler(), media); + CssStyleSheet.parseMedia(media, getPage().getWebClient()); return CssStyleSheet.isActive(mediaList, getPage().getEnclosingWindow()); } return false; diff --git a/src/main/java/org/htmlunit/javascript/host/css/MediaQueryList.java b/src/main/java/org/htmlunit/javascript/host/css/MediaQueryList.java index 43d3cd73ff8..abfd0768ae6 100644 --- a/src/main/java/org/htmlunit/javascript/host/css/MediaQueryList.java +++ b/src/main/java/org/htmlunit/javascript/host/css/MediaQueryList.java @@ -22,7 +22,6 @@ import org.htmlunit.WebWindow; import org.htmlunit.css.CssStyleSheet; import org.htmlunit.cssparser.dom.MediaListImpl; -import org.htmlunit.cssparser.parser.CSSErrorHandler; import org.htmlunit.javascript.configuration.JsxClass; import org.htmlunit.javascript.configuration.JsxConstructor; import org.htmlunit.javascript.configuration.JsxFunction; @@ -72,8 +71,7 @@ public String getMedia() { @JsxGetter public boolean isMatches() { final WebWindow webWindow = getWindow().getWebWindow(); - final CSSErrorHandler errorHandler = webWindow.getWebClient().getCssErrorHandler(); - final MediaListImpl mediaList = CssStyleSheet.parseMedia(errorHandler, media_); + final MediaListImpl mediaList = CssStyleSheet.parseMedia(media_, webWindow.getWebClient()); return CssStyleSheet.isActive(mediaList, webWindow); } diff --git a/src/main/java/org/htmlunit/javascript/host/css/StyleMedia.java b/src/main/java/org/htmlunit/javascript/host/css/StyleMedia.java index b13b5d12b1e..1170ad6a6ec 100644 --- a/src/main/java/org/htmlunit/javascript/host/css/StyleMedia.java +++ b/src/main/java/org/htmlunit/javascript/host/css/StyleMedia.java @@ -21,7 +21,6 @@ import org.htmlunit.WebWindow; import org.htmlunit.css.CssStyleSheet; import org.htmlunit.cssparser.dom.MediaListImpl; -import org.htmlunit.cssparser.parser.CSSErrorHandler; import org.htmlunit.javascript.HtmlUnitScriptable; import org.htmlunit.javascript.configuration.JsxClass; import org.htmlunit.javascript.configuration.JsxFunction; @@ -60,8 +59,7 @@ public String getType() { @JsxFunction public boolean matchMedium(final String media) { final WebWindow webWindow = getWindow().getWebWindow(); - final CSSErrorHandler errorHandler = webWindow.getWebClient().getCssErrorHandler(); - final MediaListImpl mediaList = CssStyleSheet.parseMedia(errorHandler, media); + final MediaListImpl mediaList = CssStyleSheet.parseMedia(media, webWindow.getWebClient()); return CssStyleSheet.isActive(mediaList, webWindow); }