javafx.scene.web.WebEngine Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
*/
package javafx.scene.web;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.jmx.MXNodeAlgorithm;
import com.sun.javafx.jmx.MXNodeAlgorithmContext;
import com.sun.javafx.scene.web.Debugger;
import com.sun.javafx.sg.PGNode;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.TKPulseListener;
import com.sun.javafx.tk.Toolkit;
import com.sun.javafx.webkit.Accessor;
import com.sun.javafx.webkit.CursorManagerImpl;
import com.sun.javafx.webkit.EventLoopImpl;
import com.sun.javafx.webkit.PolicyClientImpl;
import com.sun.javafx.webkit.ThemeClientImpl;
import com.sun.javafx.webkit.UIClientImpl;
import com.sun.javafx.webkit.UtilitiesImpl;
import com.sun.javafx.webkit.WebPageClientImpl;
import com.sun.javafx.webkit.prism.PrismGraphicsManager;
import com.sun.javafx.webkit.prism.PrismInvoker;
import com.sun.javafx.webkit.prism.theme.PrismRenderer;
import com.sun.javafx.webkit.theme.RenderThemeImpl;
import com.sun.javafx.webkit.theme.Renderer;
import com.sun.prism.Graphics;
import com.sun.prism.paint.Color;
import com.sun.webkit.CursorManager;
import com.sun.webkit.Disposer;
import com.sun.webkit.DisposerRecord;
import com.sun.webkit.InspectorClient;
import com.sun.webkit.Invoker;
import com.sun.webkit.LoadListenerClient;
import static com.sun.webkit.LoadListenerClient.*;
import com.sun.webkit.ThemeClient;
import com.sun.webkit.Timer;
import com.sun.webkit.Utilities;
import com.sun.webkit.WebPage;
import com.sun.webkit.graphics.WCGraphicsContext;
import com.sun.webkit.graphics.WCGraphicsManager;
import com.sun.webkit.network.URLs;
import com.sun.webkit.network.Util;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javafx.animation.AnimationTimer;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectPropertyBase;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.concurrent.Worker;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.print.PageLayout;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.util.Callback;
import org.w3c.dom.Document;
/**
* {@code WebEngine} is a non-visual object capable of managing one Web page
* at a time. It loads Web pages, creates their document models, applies
* styles as necessary, and runs JavaScript on pages. It provides access
* to the document model of the current page, and enables two-way
* communication between a Java application and JavaScript code of the page.
*
* Loading Web Pages
* The {@code WebEngine} class provides two ways to load content into a
* {@code WebEngine} object:
*
* - From an arbitrary URL using the {@link #load} method. This method uses
* the {@code java.net} package for network access and protocol handling.
*
- From an in-memory String using the
* {@link #loadContent(java.lang.String, java.lang.String)} and
* {@link #loadContent(java.lang.String)} methods.
*
* Loading always happens on a background thread. Methods that initiate
* loading return immediately after scheduling a background job. To track
* progress and/or cancel a job, use the {@link javafx.concurrent.Worker}
* instance available from the {@link #getLoadWorker} method.
*
*
The following example changes the stage title when loading completes
* successfully:
*
import javafx.concurrent.Worker.State;
final Stage stage;
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
stage.setTitle(webEngine.getLocation());
}
}
});
webEngine.load("http://javafx.com");
*
*
* User Interface Callbacks
* A number of user interface callbacks may be registered with a
* {@code WebEngine} object. These callbacks are invoked when a script running
* on the page requests a user interface operation to be performed, for
* example, opens a popup window or changes status text. A {@code WebEngine}
* object cannot handle such requests internally, so it passes the request to
* the corresponding callbacks. If no callback is defined for a specific
* operation, the request is silently ignored.
*
*
The table below shows JavaScript user interface methods and properties
* with their corresponding {@code WebEngine} callbacks:
*
* JavaScript method/property
* WebEngine callback
* {@code window.alert()} {@code onAlert}
* {@code window.confirm()} {@code confirmHandler}
* {@code window.open()} {@code createPopupHandler}
* {@code window.open()} and
* {@code window.close()} {@code onVisibilityChanged}
* {@code window.prompt()} {@code promptHandler}
* Setting {@code window.status} {@code onStatusChanged}
* Setting any of the following:
* {@code window.innerWidth}, {@code window.innerHeight},
* {@code window.outerWidth}, {@code window.outerHeight},
* {@code window.screenX}, {@code window.screenY},
* {@code window.screenLeft}, {@code window.screenTop}
* {@code onResized}
*
*
* The following example shows a callback that resizes a browser window:
*
Stage stage;
webEngine.setOnResized(
new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
*
*
* Access to Document Model
* The {@code WebEngine} objects create and manage a Document Object Model
* (DOM) for their Web pages. The model can be accessed and modified using
* Java DOM Core classes. The {@link #getDocument()} method provides access
* to the root of the model. Additionally DOM Event specification is supported
* to define event handlers in Java code.
*
*
The following example attaches a Java event listener to an element of
* a Web page. Clicking on the element causes the application to exit:
*
EventListener listener = new EventListener() {
public void handleEvent(Event ev) {
Platform.exit();
}
};
Document doc = webEngine.getDocument();
Element el = doc.getElementById("exit-app");
((EventTarget) el).addEventListener("click", listener, false);
*
*
* Evaluating JavaScript expressions
* It is possible to execute arbitrary JavaScript code in the context of
* the current page using the {@link #executeScript} method. For example:
*
webEngine.executeScript("history.back()");
*
*
* The execution result is returned to the caller,
* as described in the next section.
*
*
Mapping JavaScript values to Java objects
*
* JavaScript values are represented using the obvious Java classes:
* null becomes Java null; a boolean becomes a {@code java.lang.Boolean};
* and a string becomes a {@code java.lang.String}.
* A number can be {@code java.lang.Double} or a {@code java.lang.Integer},
* depending.
* The undefined value maps to a specific unique String
* object whose value is {@code "undefined"}.
*
* If the result is a
* JavaScript object, it is wrapped as an instance of the
* {@link netscape.javascript.JSObject} class.
* (As a special case, if the JavaScript object is
* a {@code JavaRuntimeObject} as discussed in the next section,
* then the original Java object is extracted instead.)
* The {@code JSObject} class is a proxy that provides access to
* methods and properties of its underlying JavaScript object.
* The most commonly used {@code JSObject} methods are
* {@link netscape.javascript.JSObject#getMember getMember}
* (to read a named property),
* {@link netscape.javascript.JSObject#setMember setMember}
* (to set or define a property),
* and {@link netscape.javascript.JSObject#call call}
* (to call a function-valued property).
*
* A DOM {@code Node} is mapped to an object that both extends
* {@code JSObject} and implements the appropriate DOM interfaces.
* To get a {@code JSObject} object for a {@code Node} just do a cast:
*
* JSObject jdoc = (JSObject) webEngine.getDocument();
*
*
* In some cases the context provides a specific Java type that guides
* the conversion.
* For example if setting a Java {@code String} field from a JavaScript
* expression, then the JavaScript value is converted to a string.
*
*
Mapping Java objects to JavaScript values
*
* The arguments of the {@code JSObject} methods {@code setMember} and
* {@code call} pass Java objects to the JavaScript environment.
* This is roughly the inverse of the JavaScript-to-Java mapping
* described above:
* Java {@code String}, {@code Number}, or {@code Boolean} objects
* are converted to the obvious JavaScript values. A {@code JSObject}
* object is converted to the original wrapped JavaScript object.
* Otherwise a {@code JavaRuntimeObject} is created. This is
* a JavaScript object that acts as a proxy for the Java object,
* in that accessing properties of the {@code JavaRuntimeObject}
* causes the Java field or method with the same name to be accessed.
*
* Calling back to Java from JavaScript
*
* The {@link netscape.javascript.JSObject#setMember JSObject.setMember}
* method is useful to enable upcalls from JavaScript
* into Java code, as illustrated by the following example. The Java code
* establishes a new JavaScript object named {@code app}. This object has one
* public member, the method {@code exit}.
*
public class JavaApplication {
public void exit() {
Platform.exit();
}
}
...
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", new JavaApplication());
*
* You can then refer to the object and the method from your HTML page:
*
<a href="" onclick="app.exit()">Click here to exit application</a>
*
* When a user clicks the link the application is closed.
*
* If there are multiple Java methods with the given name,
* then the engine selects one matching the number of parameters
* in the call. (Varargs are not handled.) An unspecified one is
* chosen if there are multiple ones with the correct number of parameters.
*
* You can pick a specific overloaded method by listing the
* parameter types in an extended method name
, which has the
* form "method_name(param_type1,...,param_typen)"
. Typically you'd write the JavaScript expression:
*
* receiver["method_name(param_type1,...,param_typeN)"](arg1,...,argN)
*
*
* Threading
*{@code WebEngine} objects must be created and accessed solely from the
* JavaFX Application thread. This rule also applies to any DOM and JavaScript
* objects obtained from the {@code WebEngine} object.
* @since JavaFX 2.0
*/
final public class WebEngine {
static {
Accessor.setPageAccessor(new Accessor.PageAccessor() {
@Override public WebPage getPage(WebEngine w) {
return w == null ? null : w.getPage();
}
});
Invoker.setInvoker(new PrismInvoker());
Renderer.setRenderer(new PrismRenderer());
WCGraphicsManager.setGraphicsManager(new PrismGraphicsManager());
CursorManager.setCursorManager(new CursorManagerImpl());
com.sun.webkit.EventLoop.setEventLoop(new EventLoopImpl());
ThemeClient.setDefaultRenderTheme(new RenderThemeImpl());
Utilities.setUtilities(new UtilitiesImpl());
}
/**
* The number of instances of this class.
* Used to start and stop the pulse timer.
*/
private static int instanceCount = 0;
/**
* The node associated with this engine. There is a one-to-one correspondence
* between the WebView and its WebEngine (although not all WebEngines have
* a WebView, every WebView has one and only one WebEngine).
*/
private final ObjectProperty This should be a local URL, i.e. either {@code 'data:'},
* {@code 'file:'}, or {@code 'jar:'}. Remote URLs are not allowed
* for security reasons.
*
* @defaultValue null
* @since JavaFX 2.2
*/
private StringProperty userStyleSheetLocation;
public final void setUserStyleSheetLocation(String value) {
userStyleSheetLocationProperty().set(value);
}
public final String getUserStyleSheetLocation() {
return userStyleSheetLocation == null ? null : userStyleSheetLocation.get();
}
public final StringProperty userStyleSheetLocationProperty() {
if (userStyleSheetLocation == null) {
userStyleSheetLocation = new StringPropertyBase(null) {
private final static String DATA_PREFIX = "data:text/css;charset=utf-8;base64,";
@Override public void invalidated() {
checkThread();
String url = get();
String dataUrl;
if (url == null || url.length() <= 0) {
dataUrl = null;
} else if (url.startsWith(DATA_PREFIX)) {
dataUrl = url;
} else if (url.startsWith("file:") ||
url.startsWith("jar:") ||
url.startsWith("data:"))
{
try {
URLConnection conn = URLs.newURL(url).openConnection();
conn.connect();
BufferedInputStream in =
new BufferedInputStream(conn.getInputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
new sun.misc.BASE64Encoder().encodeBuffer(in, out);
dataUrl = DATA_PREFIX + out.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalArgumentException("Invalid stylesheet URL");
}
page.setUserStyleSheetLocation(dataUrl);
}
@Override public Object getBean() {
return WebEngine.this;
}
@Override public String getName() {
return "userStyleSheetLocation";
}
};
}
return userStyleSheetLocation;
}
/**
* Specifies user agent ID string. This string is the value of the
* {@code User-Agent} HTTP header.
*
* @defaultValue system dependent
* @since JavaFX 8.0
*/
private StringProperty userAgent;
public final void setUserAgent(String value) {
userAgentProperty().set(value);
}
public final String getUserAgent() {
return userAgent == null ? page.getUserAgent() : userAgent.get();
}
public final StringProperty userAgentProperty() {
if (userAgent == null) {
userAgent = new StringPropertyBase(page.getUserAgent()) {
@Override public void invalidated() {
checkThread();
page.setUserAgent(get());
}
@Override public Object getBean() {
return WebEngine.this;
}
@Override public String getName() {
return "userAgent";
}
};
}
return userAgent;
}
private final ObjectProperty To satisfy this request a handler may create a new {@code WebEngine},
* attach a visibility handler and optionally a resize handler, and return
* the newly created engine. To block the popup, a handler should return
* {@code null}.
* By default, a popup handler is installed that opens popups in this
* {@code WebEngine}.
*
* @see PopupFeatures
*/
public final ObjectProperty An implementation may display a dialog box with Yes and No options,
* and return the user's choice.
*/
public final ObjectProperty An implementation may display a dialog box with an text field,
* and return the user's input.
*
* @see PromptData
*/
public final ObjectProperty
* All methods of the debugger must be called on
* the JavaFX Application Thread.
* The message callback object registered with the debugger
* is always called on the JavaFX Application Thread.
* @return the debugger associated with this web engine.
* The return value cannot be {@code null}.
* @treatAsPrivate This is an internal API that can be changed or
* removed in the future.
* @deprecated This is an internal API that can be changed or
* removed in the future.
*/
@Deprecated
public Debugger impl_getDebugger() {
return debugger;
}
/**
* The debugger implementation.
*/
private final class DebuggerImpl implements Debugger {
private boolean enabled;
private Callback This method does not modify the state of the job, nor does it call
* {@link PrinterJob#endJob}, so the job may be safely reused afterwards.
*
* @param job printer job used for printing
* @since JavaFX 8.0
*/
public void print(PrinterJob job) {
PageLayout pl = job.getJobSettings().getPageLayout();
int pageCount = page.beginPrinting(
(float) pl.getPrintableWidth(), (float) pl.getPrintableHeight());
for (int i = 0; i < pageCount; i++) {
Node printable = new Printable(i);
job.printPage(printable);
}
page.endPrinting();
}
final class Printable extends Node {
private final PGNode peer;
Printable(int pageIndex) {
peer = new Peer(pageIndex);
}
@Override protected PGNode impl_createPGNode() {
return peer;
}
@Override public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
return null;
}
@Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
return bounds;
}
@Override protected boolean impl_computeContains(double d, double d1) {
return false;
}
private final class Peer extends NGNode {
private final int pageIndex;
Peer(int pageIndex) {
this.pageIndex = pageIndex;
}
@Override protected void renderContent(Graphics g) {
WCGraphicsContext gc = WCGraphicsManager.getGraphicsManager().
createGraphicsContext(g);
page.print(gc, pageIndex);
}
@Override protected boolean hasOverlappingContents() {
return false;
}
}
}
}
*
*/
public Object executeScript(String script) {
checkThread();
return page.executeScript(page.getMainFrame(), script);
}
private long getMainFrame() {
return page.getMainFrame();
}
WebPage getPage() {
return page;
}
void setView(WebView view) {
this.view.setValue(view);
}
private void stop() {
checkThread();
page.stop(page.getMainFrame());
}
private static final class SelfDisposer implements DisposerRecord {
private final WebPage page;
private SelfDisposer(WebPage page) {
this.page = page;
}
@Override public void dispose() {
page.dispose();
instanceCount--;
if (instanceCount == 0 &&
Timer.getMode() == Timer.Mode.PLATFORM_TICKS)
{
PulseTimer.stop();
}
}
}
private static final class AccessorImpl extends Accessor {
private final WeakReference