/*
GNU GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
verion 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: lobochief@users.sourceforge.net
*/
/*
* Created on Feb 4, 2006
*/
package org.lobobrowser.primary.clientlets.html;
import java.awt.Cursor;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import java.io.File;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import org.eclipse.jdt.annotation.NonNull;
import org.lobobrowser.html.BrowserFrame;
import org.lobobrowser.html.FormInput;
import org.lobobrowser.html.HtmlObject;
import org.lobobrowser.html.HtmlRendererContext;
import org.lobobrowser.html.domimpl.FrameNode;
import org.lobobrowser.html.domimpl.HTMLDocumentImpl;
import org.lobobrowser.html.domimpl.HTMLImageElementImpl;
import org.lobobrowser.html.domimpl.HTMLLinkElementImpl;
import org.lobobrowser.html.gui.HtmlPanel;
import org.lobobrowser.request.DomainValidation;
import org.lobobrowser.request.SilentUserAgentContextImpl;
import org.lobobrowser.ua.NavigationEntry;
import org.lobobrowser.ua.NavigatorFrame;
import org.lobobrowser.ua.Parameter;
import org.lobobrowser.ua.ParameterInfo;
import org.lobobrowser.ua.RequestType;
import org.lobobrowser.ua.TargetType;
import org.lobobrowser.ua.UserAgentContext;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.html.HTMLCollection;
import org.w3c.dom.html.HTMLElement;
import org.w3c.dom.html.HTMLLinkElement;
public class HtmlRendererContextImpl implements HtmlRendererContext {
private static final Logger logger = Logger.getLogger(HtmlRendererContextImpl.class.getName());
private static final Map<NavigatorFrame, WeakReference<HtmlRendererContextImpl>> weakAssociation = new WeakHashMap<>();
private final NavigatorFrame clientletFrame;
private final HtmlPanel htmlPanel;
private HtmlRendererContextImpl(final NavigatorFrame clientletFrame) {
this.clientletFrame = clientletFrame;
this.htmlPanel = new HtmlPanel();
}
// public static void clearFrameAssociations() {
// synchronized(weakAssociation) {
// weakAssociation.clear();
// }
// }
//
public static HtmlRendererContextImpl getHtmlRendererContext(final NavigatorFrame frame) {
synchronized (weakAssociation) {
final WeakReference<HtmlRendererContextImpl> existingWR = weakAssociation.get(frame);
HtmlRendererContextImpl hrc;
if (existingWR != null) {
hrc = existingWR.get();
if (hrc != null) {
return hrc;
}
}
hrc = new HtmlRendererContextImpl(frame);
weakAssociation.put(frame, new WeakReference<>(hrc));
return hrc;
}
}
public Document getContentDocument() {
final Object rootNode = this.htmlPanel.getRootNode();
if (rootNode instanceof Document) {
return (Document) rootNode;
}
return null;
}
public void setContentDocument(final Document d) {
this.htmlPanel.setDocument(d, this);
}
public HtmlPanel getHtmlPanel() {
return this.htmlPanel;
}
public void linkClicked(final HTMLElement linkNode, final @NonNull URL url, final String target) {
this.navigateImpl(url, target, RequestType.CLICK, linkNode);
}
public void navigate(final @NonNull URL href, final String target) {
this.navigateImpl(href, target, RequestType.PROGRAMMATIC, null);
}
private void navigateImpl(final @NonNull URL href, final String target, final RequestType requestType, final Object linkObject) {
if (logger.isLoggable(Level.INFO)) {
logger.info("navigateImpl(): href=" + href + ",target=" + target);
}
// First check if target is a frame identifier.
TargetType targetType;
if (target != null) {
final HtmlRendererContext topCtx = this.getTop();
final HTMLCollection frames = topCtx.getFrames();
if (frames != null) {
final org.w3c.dom.Node frame = frames.namedItem(target);
if (frame instanceof FrameNode) {
final BrowserFrame bframe = ((FrameNode) frame).getBrowserFrame();
if (bframe == null) {
throw new IllegalStateException("Frame node without a BrowserFrame instance: " + frame);
}
if (bframe.getHtmlRendererContext() != this) {
bframe.loadURL(href);
return;
}
}
}
// Now try special target types.
targetType = HtmlRendererContextImpl.getTargetType(target);
} else {
targetType = TargetType.SELF;
}
if (requestType == RequestType.CLICK) {
this.clientletFrame.linkClicked(href, targetType, linkObject);
} else {
this.clientletFrame.navigate(href, "GET", null, targetType, requestType);
}
}
private static TargetType getTargetType(final String target) {
if ("_blank".equalsIgnoreCase(target)) {
return TargetType.BLANK;
} else if ("_parent".equalsIgnoreCase(target)) {
return TargetType.PARENT;
} else if ("_top".equalsIgnoreCase(target)) {
return TargetType.TOP;
} else {
return TargetType.SELF;
}
}
public void submitForm(final String method, final @NonNull URL url, final String target, final String enctype, final FormInput[] formInputs) {
final TargetType targetType = HtmlRendererContextImpl.getTargetType(target);
final ParameterInfo pinfo = new LocalParameterInfo(enctype, formInputs);
this.clientletFrame.navigate(url, method, pinfo, targetType, RequestType.FORM);
}
public BrowserFrame createBrowserFrame() {
final NavigatorFrame newFrame = this.clientletFrame.createFrame();
return new BrowserFrameImpl(newFrame, this);
}
public void alert(final String message) {
this.clientletFrame.alert(message);
}
public void blur() {
this.clientletFrame.windowToBack();
}
public void close() {
this.clientletFrame.closeWindow();
}
public boolean confirm(final String message) {
return this.clientletFrame.confirm(message);
}
public void focus() {
this.clientletFrame.windowToFront();
}
public HtmlRendererContext open(final String url, final String windowName, final String windowFeatures, final boolean replace) {
try {
final URL urlObj = DomainValidation.guessURL(url);
return this.open(urlObj, windowName, windowFeatures, replace);
} catch (final Exception err) {
logger.log(Level.WARNING, "open(): Unable to open URL [" + url + "].", err);
return null;
}
}
public HtmlRendererContext open(final @NonNull URL urlObj, final String windowName, final String windowFeatures, final boolean replace) {
final Properties windowProperties = windowFeatures == null ? null : org.lobobrowser.gui.NavigatorWindowImpl
.getPropertiesFromWindowFeatures(windowFeatures);
try {
final NavigatorFrame newFrame = this.clientletFrame.open(urlObj, "GET", null, windowName, windowProperties);
if (newFrame == null) {
return null;
}
return HtmlRendererContextImpl.getHtmlRendererContext(newFrame);
} catch (final Exception err) {
logger.log(Level.WARNING, "open(): Unable to open URL [" + urlObj + "].", err);
return null;
}
}
public String prompt(final String message, final String inputDefault) {
return this.clientletFrame.prompt(message, inputDefault);
}
public void scroll(final int x, final int y) {
this.htmlPanel.scroll(x, y);
}
public void scrollBy(final int xOffset, final int yOffset) {
this.htmlPanel.scrollBy(xOffset, yOffset);
}
public boolean isClosed() {
return this.clientletFrame.isWindowClosed();
}
public String getDefaultStatus() {
return this.clientletFrame.getDefaultStatus();
}
public void setDefaultStatus(final String value) {
this.clientletFrame.setDefaultStatus(value);
}
public HTMLCollection getFrames() {
final Object rootNode = this.htmlPanel.getRootNode();
if (rootNode instanceof HTMLDocumentImpl) {
return ((HTMLDocumentImpl) rootNode).getFrames();
} else {
return null;
}
}
public int getLength() {
final HTMLCollection frames = this.getFrames();
return frames == null ? 0 : frames.getLength();
}
public String getName() {
return this.clientletFrame.getWindowId();
}
// private static final String HTML_RENDERER_ITEM = "lobo.html.renderer";
public HtmlRendererContext getParent() {
final NavigatorFrame parentFrame = this.clientletFrame.getParentFrame();
return parentFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(parentFrame);
}
public HtmlRendererContext getOpener() {
final HtmlRendererContext opener = this.assignedOpener;
if (opener != null) {
return opener;
}
final NavigatorFrame openerFrame = this.clientletFrame.getOpenerFrame();
return openerFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(openerFrame);
}
private volatile HtmlRendererContext assignedOpener;
public void setOpener(final HtmlRendererContext opener) {
this.assignedOpener = opener;
}
public String getStatus() {
return this.clientletFrame.getStatus();
}
public void setStatus(final String message) {
this.clientletFrame.setStatus(message);
}
public void reload() {
this.clientletFrame.reload();
}
public HtmlRendererContext getTop() {
final NavigatorFrame parentFrame = this.clientletFrame.getTopFrame();
return parentFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(parentFrame);
}
public HtmlObject getHtmlObject(final HTMLElement element) {
// TODO
return null;
}
private UserAgentContext uaContext;
public UserAgentContext getUserAgentContext() {
if (this.uaContext == null) {
synchronized (this) {
if (this.uaContext == null) {
this.uaContext = new SilentUserAgentContextImpl(this.clientletFrame);
}
}
}
return this.uaContext;
}
public boolean isVisitedLink(final HTMLLinkElement link) {
// TODO
return false;
}
private Runnable getMiddleClickTask(final Node element) {
Node currElement = element;
Runnable task = null;
while ((task == null) && currElement != null) {
if (currElement instanceof HTMLLinkElementImpl) {
final HTMLLinkElementImpl link = (HTMLLinkElementImpl) currElement;
task = (() -> {
HtmlRendererContextImpl.this.open(link.getAbsoluteHref(), null, null, false);
});
} else if (currElement instanceof HTMLImageElementImpl) {
final HTMLImageElementImpl img = (HTMLImageElementImpl) currElement;
try {
final URL srcUrl = img.getFullURL(img.getSrc());
task = (() -> {
HtmlRendererContextImpl.this.open(srcUrl, "new window", null, false);
});
} catch (final MalformedURLException e) {
logger.log(Level.INFO, "Couldn't get Image URL", e);
}
}
currElement = currElement.getParentNode();
}
return task;
}
public boolean onMiddleClick(final HTMLElement element, final MouseEvent event) {
final Runnable task = getMiddleClickTask(element);
if (task != null) {
SwingUtilities.invokeLater(task);
return false;
}
return true;
}
public boolean onContextMenu(final HTMLElement element, final MouseEvent event) {
final JPopupMenu popupMenu = new JPopupMenu();
if (popupMenu.isPopupTrigger(event)) {
populatePopup(element, popupMenu);
popupMenu.show(event.getComponent(), event.getX(), event.getY());
return false;
}
return true;
}
private void populatePopup(final HTMLElement element, final JPopupMenu popupMenu) {
final boolean componentEntriesAdded = populatePopupForElement(element, popupMenu);
if (componentEntriesAdded) {
popupMenu.addSeparator();
}
final boolean prevPresent = getPreviousURL().isPresent();
final boolean nextPresent = getNextURL().isPresent();
{
final JMenuItem menuItem = new JMenuItem("Back");
menuItem.addActionListener(e -> {
HtmlRendererContextImpl.this.back();
});
menuItem.setEnabled(prevPresent);
popupMenu.add(menuItem);
}
{
final JMenuItem menuItem = new JMenuItem("Reload");
menuItem.addActionListener(e -> {
HtmlRendererContextImpl.this.reload();
});
popupMenu.add(menuItem);
}
{
final JMenuItem menuItem = new JMenuItem("Forward");
menuItem.addActionListener(e -> {
HtmlRendererContextImpl.this.forward();
});
menuItem.setEnabled(nextPresent);
popupMenu.add(menuItem);
}
}
private boolean populatePopupForElement(final HTMLElement element, final JPopupMenu popupMenu) {
boolean linkEntryAdded = false;
boolean imageEntryAdded = false;
Node currElement = element;
while (currElement != null) {
if ((!linkEntryAdded) && (currElement instanceof HTMLLinkElementImpl)) {
if (imageEntryAdded) {
popupMenu.addSeparator();
}
final HTMLLinkElementImpl link = (HTMLLinkElementImpl) currElement;
final JMenuItem openLinkMenuItem = new JMenuItem("Open link in new window");
openLinkMenuItem.addActionListener(e -> {
HtmlRendererContextImpl.this.open(link.getAbsoluteHref(), null, null, false);
});
popupMenu.add(openLinkMenuItem);
final JMenuItem copyLinkMenuItem = new JMenuItem("Copy link");
copyLinkMenuItem.addActionListener(e -> {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(link.getAbsoluteHref()), null);
});
popupMenu.add(copyLinkMenuItem);
linkEntryAdded = true;
} else if ((!imageEntryAdded) && (currElement instanceof HTMLImageElementImpl)) {
if (linkEntryAdded) {
popupMenu.addSeparator();
}
final HTMLImageElementImpl img = (HTMLImageElementImpl) currElement;
try {
final URL srcUrl = img.getFullURL(img.getSrc());
final JMenuItem openImageMenuItem = new JMenuItem("Open image in new window");
openImageMenuItem.addActionListener(e -> {
HtmlRendererContextImpl.this.open(srcUrl, null, null, false);
});
popupMenu.add(openImageMenuItem);
final JMenuItem copyImageURLMenuItem = new JMenuItem("Copy Image URL");
copyImageURLMenuItem.addActionListener(e -> {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(srcUrl.toExternalForm()), null);
});
popupMenu.add(copyImageURLMenuItem);
imageEntryAdded = true;
} catch (final MalformedURLException e) {
logger.log(Level.INFO, "Couldn't get Image URL", e);
}
}
currElement = currElement.getParentNode();
}
return linkEntryAdded || imageEntryAdded;
}
public void onMouseOut(final HTMLElement element, final MouseEvent event) {
if (element instanceof HTMLLinkElementImpl) {
this.clientletFrame.setStatus(null);
}
}
public boolean isImageLoadingEnabled() {
return true;
}
public void onMouseOver(final HTMLElement element, final MouseEvent event) {
if (element instanceof HTMLLinkElementImpl) {
final HTMLLinkElementImpl linkElement = (HTMLLinkElementImpl) element;
this.clientletFrame.setStatus(linkElement.getAbsoluteHref());
}
}
public boolean onDoubleClick(final HTMLElement element, final MouseEvent event) {
return true;
}
public boolean onMouseClick(final HTMLElement element, final MouseEvent event) {
return true;
}
public void resizeBy(final int byWidth, final int byHeight) {
this.clientletFrame.resizeWindowBy(byWidth, byHeight);
}
public void resizeTo(final int width, final int height) {
this.clientletFrame.resizeWindowTo(width, height);
}
public void forward() {
this.clientletFrame.forward();
}
public void back() {
this.clientletFrame.back();
}
public String getCurrentURL() {
final NavigationEntry entry = this.clientletFrame.getCurrentNavigationEntry();
return entry == null ? null : entry.getUrl().toExternalForm();
}
public int getHistoryLength() {
return this.clientletFrame.getHistoryLength();
}
public Optional<String> getNextURL() {
final Optional<NavigationEntry> entry = this.clientletFrame.getNextNavigationEntry();
return entry.map((e) -> e.getUrl().toExternalForm());
}
public Optional<String> getPreviousURL() {
final Optional<NavigationEntry> entry = this.clientletFrame.getPreviousNavigationEntry();
return entry.map((e) -> e.getUrl().toExternalForm());
}
public void goToHistoryURL(final String url) {
this.clientletFrame.navigateInHistory(url);
}
public void moveInHistory(final int offset) {
this.clientletFrame.moveInHistory(offset);
}
private static class LocalParameterInfo implements ParameterInfo {
private final String encodingType;
private final FormInput[] formInputs;
/**
* @param type
* @param inputs
*/
public LocalParameterInfo(final String type, final FormInput[] inputs) {
super();
encodingType = type;
formInputs = inputs;
}
/*
* (non-Javadoc)
*
* @see org.xamjwg.clientlet.ParameterInfo#getEncoding()
*/
public String getEncoding() {
return this.encodingType;
}
/*
* (non-Javadoc)
*
* @see org.xamjwg.clientlet.ParameterInfo#getParameters()
*/
public Parameter[] getParameters() {
final FormInput[] formInputs = this.formInputs;
final Parameter[] params = new Parameter[formInputs.length];
for (int i = 0; i < params.length; i++) {
final int index = i;
params[i] = new Parameter() {
public String getName() {
return formInputs[index].getName();
}
public File getFileValue() {
return formInputs[index].getFileValue();
}
public String getTextValue() {
return formInputs[index].getTextValue();
}
public boolean isFile() {
return formInputs[index].isFile();
}
public boolean isText() {
return formInputs[index].isText();
}
};
}
return params;
}
}
public void setCursor(final Optional<Cursor> cursorOpt) {
final Cursor cursor = cursorOpt.orElse(Cursor.getDefaultCursor());
htmlPanel.setCursor(cursor);
}
private Runnable jobFinishedHandler = null;
public synchronized void setJobFinishedHandler(Runnable runnable) {
jobFinishedHandler = runnable;
}
public synchronized void jobsFinished() {
if (jobFinishedHandler != null) {
jobFinishedHandler.run();
jobFinishedHandler = null;
}
}
}