text: package io.webfolder.ui4j.webkit.browser;
  
  import java.util.List;
  import java.util.Map;
  import java.util.WeakHashMap;
  
  import org.w3c.dom.Node;
  import com.sun.webkit.dom.DocumentImpl;
  
  import io.webfolder.ui4j.api.browser.PageConfiguration;
  import io.webfolder.ui4j.api.browser.SelectorEngine;
  import io.webfolder.ui4j.api.browser.SelectorType;
  import io.webfolder.ui4j.api.dom.Document;
  import io.webfolder.ui4j.api.dom.Element;
  import io.webfolder.ui4j.api.dom.Window;
  import io.webfolder.ui4j.api.event.DocumentListener;
  import io.webfolder.ui4j.api.event.DocumentLoadEvent;
  import io.webfolder.ui4j.api.util.Logger;
  import io.webfolder.ui4j.api.util.LoggerFactory;
  import io.webfolder.ui4j.spi.EventManager;
  import io.webfolder.ui4j.spi.EventRegistrar;
  import io.webfolder.ui4j.spi.JavaScriptEngine;
  import io.webfolder.ui4j.spi.NativeEventManager;
  import io.webfolder.ui4j.spi.PageContext;
  import io.webfolder.ui4j.webkit.dom.WebKitDocument;
  import io.webfolder.ui4j.webkit.dom.WebKitElement;
  import io.webfolder.ui4j.webkit.dom.WebKitPage;
  import io.webfolder.ui4j.webkit.dom.WebKitWindow;
  import io.webfolder.ui4j.webkit.spi.SizzleSelectorEngine;
  import io.webfolder.ui4j.webkit.spi.W3CEventRegistrar;
  import io.webfolder.ui4j.webkit.spi.W3CSelectorEngine;
  import io.webfolder.ui4j.webkit.spi.WebKitJavaScriptEngine;
  import javafx.beans.value.ChangeListener;
  import javafx.beans.value.ObservableValue;
  import javafx.event.EventHandler;
  import javafx.scene.Scene;
  import javafx.scene.web.WebEngine;
  import javafx.scene.web.WebErrorEvent;
  import javafx.scene.web.WebView;
  import javafx.stage.Stage;
  
  public class WebKitPageContext implements PageContext {
  
      private Logger log = LoggerFactory.getLogger(getClass());
  
      private EventRegistrar eventRegistrar;
  
      private EventManager eventManager = new NativeEventManager(this);
  
      private SelectorEngine selector;
  
      private PageConfiguration configuration;
  
      private Map<DocumentImpl, Document> contentDocuments = new WeakHashMap<>();
  
      private Map<DocumentImpl, SelectorEngine> selectorEngines = new WeakHashMap<>();
  
      public static class DefaultErrorEventHandler implements EventHandler<WebErrorEvent> {
  
          private Logger log = LoggerFactory.getLogger(getClass());
  
          @Override
          public void handle(WebErrorEvent event) {
              log.error("Javascript error: " + event.getMessage());
          }
      }
  
      public static class ExceptionListener implements ChangeListener<Throwable> {
  
          private Logger log;
  
          public ExceptionListener(Logger log) {
              this.log = log;
          }
  
          @Override
          public void changed(ObservableValue<? extends Throwable> observable,
                  Throwable oldValue, Throwable newValue) {
              log.error(newValue.getMessage());
          }
      };
  
      public static class GlobalEventCleaner implements DocumentListener {
  
          @Override
          public void onLoad(DocumentLoadEvent event) {
              Document document = event.getDocument();
              if (document == null) {
                  return;
              }
              List<Element> elements = document.queryAll("[ui4j-registered-event=true]");
              if (elements.isEmpty()) {
                  return;
              }
              for (Element next : elements) {
                  next.unbind();
                  next.removeAttribute("ui4j-registered-event");
                  next.removeProperty("events");
              }
          }
      }
  
      public WebKitPageContext(PageConfiguration configuration) {
          this.configuration = configuration;
      }
  
      @Override
      public EventManager getEventManager() {
          return eventManager;
      }
  
      @Override
      public EventRegistrar getEventRegistrar() {
          return eventRegistrar;
      }
  
      public Element createElement(Object node, Document document, JavaScriptEngine engine) {
          return (Element) new WebKitElement((Node) node, document, this, (WebKitJavaScriptEngine) engine);
      }
  
      public Document createDocument(JavaScriptEngine engine) {
          DocumentImpl documentImpl = (DocumentImpl) ((WebKitJavaScriptEngine) engine).getEngine().getDocument();
  
          WebEngine webEngine = (WebEngine) engine.getEngine();
          if (configuration.getUserAgent() != null) {
              webEngine.setUserAgent(configuration.getUserAgent());
          }
          webEngine.getLoadWorker().exceptionProperty().addListener(new ExceptionListener(log));
          webEngine.setOnError(new DefaultErrorEventHandler());
          Document document = new WebKitDocument(this, documentImpl, (WebKitJavaScriptEngine) engine);
          selector = initializeSelectorEngine(document, (WebKitJavaScriptEngine) engine);
          return document;
      }
  
      public Document getContentDocument(DocumentImpl documentImpl, JavaScriptEngine engine) {
          synchronized (this) {
              Document existingDocument = contentDocuments.get(documentImpl);
              if (existingDocument != null) {
                  return existingDocument;
              } else {
                  Document document = new WebKitDocument(this, documentImpl, (WebKitJavaScriptEngine) engine);
                  contentDocuments.put(documentImpl, document);
  
                  SelectorEngine selector = initializeSelectorEngine(document, (WebKitJavaScriptEngine) engine);
                  selectorEngines.put(documentImpl, selector);
  
                  return document;
              }
          }
      }
  
      public void onLoad(Document document) {
          this.eventRegistrar = new W3CEventRegistrar(this);
      }
  
      public Window createWindow(Document document) {
          return (Window) new WebKitWindow(document);
      }
  
      public WebKitPage newPage(Object view, JavaScriptEngine engine, Window window, Stage stage, Scene scene, Document document, int pageId) {
          WebView webView = (WebView) view;
          WebKitPage page = new WebKitPage(webView, (WebKitJavaScriptEngine) engine, window, stage, scene, document, pageId);
          page.addDocumentListener(new GlobalEventCleaner());
          return page;
      }
  
      @Override
      public SelectorEngine getSelectorEngine() {
          return selector;
      }
  
      public SelectorEngine getSelectorEngine(org.w3c.dom.Document documentImpl) {
          SelectorEngine contentDocumentSelectorEngine = selectorEngines.get(documentImpl);
          if (contentDocumentSelectorEngine == null) {
              return selector;
          }
          return contentDocumentSelectorEngine;
      }
  
      protected SelectorEngine initializeSelectorEngine(Document document, WebKitJavaScriptEngine engine) {
          SelectorEngine selector = null;
          if (configuration.getSelectorEngine().equals(SelectorType.SIZZLE)) {
              String sizzle = readSizzle();
              boolean foundSizzle = Boolean.parseBoolean(engine.getEngine().executeScript("typeof window.Sizzle === 'function'").toString());
              if (!foundSizzle) {
                  engine.getEngine().executeScript(sizzle);
              }
              selector = new SizzleSelectorEngine(this, document, engine);
          } else {
              selector = new W3CSelectorEngine(this, document, engine);
          }
          return selector;
      }
  
      protected String readSizzle() {
          return readFromClasspath("/io/webfolder/ui4j/sizzle.js");
      }
  
      protected String readFromClasspath(String path) {
          try(java.util.Scanner scanner = new java.util.Scanner(getClass().getResourceAsStream(path))) {
              scanner.useDelimiter("\\A");
              if (scanner.hasNext()) {
                  return scanner.next();
              } else {
                  return null;
              }
          }
      }
  
      public PageConfiguration getConfiguration() {
          return configuration;
      }
  }
  