/*
GNU GENERAL LICENSE
Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution
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 3 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 License for more details.
You should have received a copy of the GNU General Public
along with this program. If not, see <http://www.gnu.org/licenses/>.
Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
*/
package org.lobobrowser.primary.clientlets.html;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.WeakHashMap;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.JPopupMenu;
import org.lobobrowser.gui.NavigatorWindowImpl;
import org.lobobrowser.html.BrowserFrame;
import org.lobobrowser.html.FormInput;
import org.lobobrowser.html.HtmlObject;
import org.lobobrowser.html.HtmlRendererContext;
import org.lobobrowser.html.dombl.FrameNode;
import org.lobobrowser.html.domimpl.HTMLAbstractUIElement;
import org.lobobrowser.html.domimpl.HTMLAnchorElementImpl;
import org.lobobrowser.html.domimpl.HTMLDocumentImpl;
import org.lobobrowser.html.domimpl.HTMLImageElementImpl;
import org.lobobrowser.html.gui.HtmlPanel;
import org.lobobrowser.http.UserAgentContext;
import org.lobobrowser.primary.info.LocalParameterInfo;
import org.lobobrowser.ua.NavigationEntry;
import org.lobobrowser.ua.NavigatorFrame;
import org.lobobrowser.ua.ParameterInfo;
import org.lobobrowser.ua.RequestType;
import org.lobobrowser.ua.TargetType;
import org.lobobrowser.w3c.html.HTMLAnchorElement;
import org.lobobrowser.w3c.html.HTMLCollection;
import org.lobobrowser.w3c.html.HTMLElement;
import org.w3c.dom.Document;
/**
* The Class HtmlRendererContextImpl.
*/
public class HtmlRendererContextImpl implements HtmlRendererContext {
/** The Constant logger. */
private static final Logger logger = LogManager.getLogger(HtmlRendererContextImpl.class);
/** The Constant weakAssociation. */
private static final Map<NavigatorFrame, WeakReference<HtmlRendererContextImpl>> weakAssociation = new WeakHashMap<NavigatorFrame, WeakReference<HtmlRendererContextImpl>>();
/** The clientlet frame. */
private final NavigatorFrame clientletFrame;
/** The html panel. */
private final HtmlPanel htmlPanel;
/**
* Instantiates a new html renderer context impl.
*
* @param clientletFrame
* the clientlet frame
*/
private HtmlRendererContextImpl(NavigatorFrame clientletFrame) {
this.clientletFrame = clientletFrame;
this.htmlPanel = new HtmlPanel();
}
/**
* Gets the html renderer context.
*
* @param frame
* the frame
* @return the html renderer context
*/
public static HtmlRendererContextImpl getHtmlRendererContext(NavigatorFrame frame) {
synchronized (weakAssociation) {
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<HtmlRendererContextImpl>(hrc));
return hrc;
}
}
/**
* Gets the content document.
*
* @return the content document
*/
public Document getContentDocument() {
Object rootNode = this.htmlPanel.getRootNode();
if (rootNode instanceof Document) {
return (Document) rootNode;
}
return null;
}
/**
* Gets the html panel.
*
* @return the html panel
*/
public HtmlPanel getHtmlPanel() {
return this.htmlPanel;
}
/**
* Warn.
*
* @param message
* the message
* @param throwable
* the throwable
*/
public void warn(String message, Throwable throwable) {
logger.error(message, throwable);
}
/**
* Error.
*
* @param message
* the message
* @param throwable
* the throwable
*/
public void error(String message, Throwable throwable) {
logger.log(Level.ERROR, message, throwable);
}
/**
* Warn.
*
* @param message
* the message
*/
public void warn(String message) {
logger.warn(message);
}
/**
* Error.
*
* @param message
* the message
*/
public void error(String message) {
logger.log(Level.ERROR, message);
}
@Override
public void linkClicked(HTMLElement linkNode, URL url, String target) {
this.navigateImpl(url, target, RequestType.CLICK, linkNode);
}
@Override
public void navigate(URL href, String target) {
this.navigateImpl(href, target, RequestType.PROGRAMMATIC, null);
}
/**
* Navigate impl.
*
* @param href
* the href
* @param target
* the target
* @param requestType
* the request type
* @param linkObject
* the link object
*/
private void navigateImpl(URL href, String target, RequestType requestType, Object linkObject) {
if (logger.isInfoEnabled()) {
logger.info("navigateImpl(): href=" + href + ",target=" + target);
}
// First check if target is a frame identifier.
TargetType targetType;
if (target != null) {
HtmlRendererContext topCtx = this.getTop();
HTMLCollection frames = topCtx.getFrames();
if (frames != null) {
org.w3c.dom.Node frame = frames.namedItem(target);
if (frame instanceof FrameNode) {
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 = this.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);
}
}
/**
* Gets the target type.
*
* @param target
* the target
* @return the target type
*/
private TargetType getTargetType(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;
}
}
/**
* Open.
*
* @param url
* the url
* @param windowName
* the window name
* @param windowFeatures
* the window features
* @param replace
* the replace
* @return the html renderer context
*/
public HtmlRendererContext open(String url, String windowName, String windowFeatures, boolean replace) {
try {
URL urlObj = org.lobobrowser.util.Urls.guessURL(url);
return this.open(urlObj, windowName, windowFeatures, replace);
} catch (Exception err) {
logger.error("open(): Unable to open URL [" + url + "].", err);
return null;
}
}
/**
* Gets the length.
*
* @return the length
*/
public int getLength() {
HTMLCollection frames = this.getFrames();
return frames == null ? 0 : frames.getLength();
}
/**
* Gets the clientlet frame.
*
* @return the clientlet frame
*/
public NavigatorFrame getClientletFrame() {
return clientletFrame;
}
@Override
public void submitForm(String method, URL url, String target, String enctype, FormInput[] formInputs) {
TargetType targetType = this.getTargetType(target);
ParameterInfo pinfo = new LocalParameterInfo(enctype, formInputs);
this.clientletFrame.navigate(url, method, pinfo, targetType, RequestType.FORM);
}
@Override
public BrowserFrame createBrowserFrame() {
NavigatorFrame newFrame = this.clientletFrame.createFrame();
return new BrowserFrameImpl(newFrame, this);
}
@Override
public void alert(String message) {
this.clientletFrame.alert(message);
}
@Override
public void blur() {
this.clientletFrame.windowToBack();
}
@Override
public void close() {
this.clientletFrame.closeWindow();
}
@Override
public boolean confirm(String message) {
return this.clientletFrame.confirm(message);
}
@Override
public void focus() {
this.clientletFrame.windowToFront();
}
@Override
public HtmlRendererContext open(URL urlObj, String windowName, String windowFeatures, boolean replace) {
Properties windowProperties = windowFeatures == null ? null
: NavigatorWindowImpl.getPropertiesFromWindowFeatures(windowFeatures);
try {
NavigatorFrame newFrame = this.clientletFrame.open(urlObj, "GET", null, windowName, windowProperties);
if (newFrame == null) {
return null;
}
return HtmlRendererContextImpl.getHtmlRendererContext(newFrame);
} catch (Exception err) {
logger.error("open(): Unable to open URL [" + urlObj + "].", err);
return null;
}
}
@Override
public String prompt(String message, String inputDefault) {
return this.clientletFrame.prompt(message, inputDefault);
}
@Override
public void scroll(int x, int y) {
this.htmlPanel.scroll(x, y);
}
@Override
public void scrollBy(int xOffset, int yOffset) {
this.htmlPanel.scrollBy(xOffset, yOffset);
}
@Override
public boolean isClosed() {
return this.clientletFrame.isWindowClosed();
}
@Override
public String getDefaultStatus() {
return this.clientletFrame.getDefaultStatus();
}
@Override
public void setDefaultStatus(String value) {
this.clientletFrame.setDefaultStatus(value);
}
@Override
public HTMLCollection getFrames() {
Object rootNode = this.htmlPanel.getRootNode();
if (rootNode instanceof HTMLDocumentImpl) {
return ((HTMLDocumentImpl) rootNode).getFrames();
} else {
return null;
}
}
@Override
public String getName() {
return this.clientletFrame.getWindowId();
}
@Override
public HtmlRendererContext getParent() {
NavigatorFrame parentFrame = this.clientletFrame.getParentFrame();
return parentFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(parentFrame);
}
@Override
public HtmlRendererContext getOpener() {
HtmlRendererContext opener = this.assignedOpener;
if (opener != null) {
return opener;
}
NavigatorFrame openerFrame = this.clientletFrame.getOpenerFrame();
return openerFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(openerFrame);
}
/** The assigned opener. */
private volatile HtmlRendererContext assignedOpener;
@Override
public void setOpener(HtmlRendererContext opener) {
this.assignedOpener = opener;
}
@Override
public String getStatus() {
return this.clientletFrame.getStatus();
}
@Override
public void setStatus(String message) {
this.clientletFrame.setStatus(message);
}
@Override
public void reload() {
this.clientletFrame.reload();
}
@Override
public HtmlRendererContext getTop() {
NavigatorFrame parentFrame = this.clientletFrame.getTopFrame();
return parentFrame == null ? null : HtmlRendererContextImpl.getHtmlRendererContext(parentFrame);
}
@Override
public HtmlObject getHtmlObject(HTMLElement element) {
// TODO
return null;
}
/** The ua context. */
private UserAgentContext uaContext;
@Override
public UserAgentContext getUserAgentContext() {
if (this.uaContext == null) {
synchronized (this) {
if (this.uaContext == null) {
this.uaContext = new UserAgentContextImpl(this.getClientletFrame());
}
}
}
return this.uaContext;
}
@Override
public boolean isVisitedLink(HTMLAnchorElement link) {
// TODO
return false;
}
@Override
public boolean onContextMenu(HTMLElement element, MouseEvent event) {
HtmlContextMenu imageMenu = new HtmlContextMenu(element, this);
if (element instanceof HTMLImageElementImpl) {
JPopupMenu popupMenuImage = imageMenu.popupMenuImage();
popupMenuImage.show(event.getComponent(), event.getX(), event.getY());
return false;
} else if (element instanceof HTMLAnchorElementImpl) {
JPopupMenu popupMenuImage = imageMenu.popupMenuLink();
popupMenuImage.show(event.getComponent(), event.getX(), event.getY());
return false;
} else if (element instanceof HTMLAbstractUIElement) {
JPopupMenu popupMenuImage = imageMenu.popupMenuAbstractUI();
popupMenuImage.show(event.getComponent(), event.getX(), event.getY());
return false;
}
return true;
}
@Override
public void onMouseOut(HTMLElement element, MouseEvent event) {
if (element instanceof HTMLAnchorElementImpl) {
this.clientletFrame.setStatus(null);
}
}
@Override
public boolean isImageLoadingEnabled() {
return true;
}
@Override
public void onMouseOver(HTMLElement element, MouseEvent event) {
if (element instanceof HTMLAnchorElementImpl) {
HTMLAnchorElementImpl linkElement = (HTMLAnchorElementImpl) element;
this.clientletFrame.setStatus(linkElement.getAbsoluteHref());
}
}
@Override
public boolean onDoubleClick(HTMLElement element, MouseEvent event) {
return true;
}
@Override
public boolean onMouseClick(HTMLElement element, MouseEvent event) {
return true;
}
@Override
public void resizeBy(int byWidth, int byHeight) {
this.clientletFrame.resizeWindowBy(byWidth, byHeight);
}
@Override
public void resizeTo(int width, int height) {
this.clientletFrame.resizeWindowTo(width, height);
}
@Override
public void forward() {
this.clientletFrame.forward();
}
@Override
public void back() {
this.clientletFrame.back();
}
@Override
public String getCurrentURL() {
NavigationEntry entry = this.clientletFrame.getCurrentNavigationEntry();
return entry == null ? null : entry.getUrl().toExternalForm();
}
@Override
public int getHistoryLength() {
return this.clientletFrame.getHistoryLength();
}
@Override
public String getNextURL() {
NavigationEntry entry = this.clientletFrame.getNextNavigationEntry();
return entry == null ? null : entry.getUrl().toExternalForm();
}
@Override
public String getPreviousURL() {
NavigationEntry entry = this.getClientletFrame().getPreviousNavigationEntry();
return entry == null ? null : entry.getUrl().toExternalForm();
}
@Override
public void goToHistoryURL(String url) {
this.clientletFrame.navigateInHistory(url);
}
@Override
public void moveInHistory(int offset) {
this.clientletFrame.moveInHistory(offset);
}
@Override
public void setCursor(Optional<Cursor> cursorOpt) {
Cursor cursor = cursorOpt.orElse(Cursor.getDefaultCursor());
htmlPanel.setCursor(cursor);
}
}