/* 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 */ package org.lobobrowser.gui; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Window; import java.net.MalformedURLException; import java.net.URL; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.clientlet.ClientletRequest; import org.lobobrowser.clientlet.ClientletResponse; import org.lobobrowser.clientlet.ComponentContent; import org.lobobrowser.main.ExtensionManager; import org.lobobrowser.main.PlatformInit; import org.lobobrowser.request.ClientletRequestHandler; import org.lobobrowser.request.ClientletRequestImpl; import org.lobobrowser.request.DomainValidation; import org.lobobrowser.request.RequestEngine; import org.lobobrowser.request.RequestHandler; import org.lobobrowser.request.SilentUserAgentContextImpl; import org.lobobrowser.security.GenericLocalPermission; import org.lobobrowser.security.RequestManager; import org.lobobrowser.ua.NavigationEntry; import org.lobobrowser.ua.NavigationEvent; import org.lobobrowser.ua.NavigationListener; import org.lobobrowser.ua.NavigationVetoException; import org.lobobrowser.ua.NavigatorFrame; import org.lobobrowser.ua.NavigatorProgressEvent; import org.lobobrowser.ua.NetworkRequest; import org.lobobrowser.ua.ParameterInfo; import org.lobobrowser.ua.RequestType; import org.lobobrowser.ua.TargetType; import org.lobobrowser.ua.UserAgentContext; import org.lobobrowser.ua.UserAgentContext.Request; import org.lobobrowser.util.ArrayUtilities; import org.lobobrowser.util.Items; import org.lobobrowser.util.SecurityUtil; import org.lobobrowser.util.WrapperException; import org.lobobrowser.util.gui.WrapperLayout; /** * A browser frame panel. It may be used as any other Swing component. The * {@link #navigate(String)} method may be invoked to load content into the * browser frame. * <p> * Content types supported depend on available browser extensions. * <p> * It's recommended that <code>FramePanel</code>s be placed in windows that * extend {@link AbstractBrowserWindow} or implement {@link BrowserWindow}. Such * windows may receive navigation notifications via {@link WindowCallback}. * <p> * A frame panel with navigation controls and a status bar can be obtained with * {@link BrowserPanel}. * * @see PlatformInit#init(boolean, boolean) */ public class FramePanel extends JPanel implements NavigatorFrame { private static final long serialVersionUID = -8873769110035409639L; private static final Logger logger = Logger.getLogger(FramePanel.class.getName()); private final String windowId; private final NavigationEngine navigationEngine = new NavigationEngine(); private final FramePanel knownParentFrame; private final Collection<NavigationListener> navigationListeners = new ArrayList<>(); private final Collection<ResponseListener> responseListeners = new ArrayList<>(); private final Collection<ContentListener> contentListeners = new ArrayList<>(); private final Object propertiesMonitor = new Object(); private NavigatorFrame openerFrame; private Window topFrameWindow; /** * Constructs a FramePanel specifying a "window" ID. */ public FramePanel(final String windowId) { this.knownParentFrame = null; this.windowId = windowId; this.setLayout(WrapperLayout.getInstance()); this.setBackground(Color.WHITE); this.setOpaque(true); } /** * Constructs a FramePanel specifying a non-null parent frame. This * constructor is useful when navigation in the new frame must occur before * the frame is added to the GUI component hierarchy. */ public FramePanel(final FramePanel parentFrame) { this.knownParentFrame = parentFrame; this.windowId = null; this.setLayout(WrapperLayout.getInstance()); this.setBackground(Color.WHITE); this.setOpaque(true); } /** * Constructs a standalone <code>FramePanel</code> that can be added to any * Swing window or component. Note that the FramePanel should be part of a * Swing or AWT window before it becomes functional. */ public FramePanel() { this((FramePanel) null); } public void setOpenerFrame(final NavigatorFrame opener) { this.openerFrame = opener; } /** * Causes an event to be fired. This method is for internal use. * * @param response * A clientlet response. */ public void informResponseProcessed(final ClientletResponse response) { this.dispatchResponseProcessed(new ResponseEvent(this, response)); } /** * Adds a listener of navigation events. * * @param listener * The listener. */ public void addNavigationListener(final NavigationListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.navigationListeners.add(listener); } } /** * Removes a listener of navigation events previously added with * {@link #addNavigationListener(NavigationListener)}. * * @param listener * The listener. */ public void removeNavigationListener(final NavigationListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.navigationListeners.remove(listener); } } /** * Adds a listener of content events. * * @param listener * The listener. * @see #getComponentContent() */ public void addContentListener(final ContentListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.contentListeners.add(listener); } } /** * Removes a listener of content events previously added with * {@link #addNavigationListener(NavigationListener)}. * * @param listener * The listener. */ public void removeContentListener(final ContentListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.contentListeners.remove(listener); } } /** * Adds a listener of response events. * * @param listener * The listener. */ public void addResponseListener(final ResponseListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.responseListeners.add(listener); } } /** * Removes a listener of navigation events previously added with * {@link #addNavigationListener(NavigationListener)}. * * @param listener * The listener. */ public void removeResponseListener(final ResponseListener listener) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { this.responseListeners.remove(listener); } } private void dispatchBeforeNavigate(final NavigationEvent event) throws NavigationVetoException { try { AccessController.doPrivileged(new PrivilegedAction<Object>() { // Reason: Dispatching an event to extensions requires permission to, // among other things, setting the context class loader. public Object run() { try { ExtensionManager.getInstance().dispatchBeforeNavigate(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } ArrayUtilities.forEachSynched(navigationListeners, this, (listener) -> { try { listener.beforeNavigate(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } }); return null; } }); } catch (final WrapperException we) { throw (NavigationVetoException) we.getCause(); } } private void dispatchBeforeLocalNavigate(final NavigationEvent event) throws NavigationVetoException { try { AccessController.doPrivileged(new PrivilegedAction<Object>() { // Reason: Dispatching an event to extensions requires permission to, // among other things, setting the context class loader. public Object run() { try { ExtensionManager.getInstance().dispatchBeforeLocalNavigate(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } ArrayUtilities.forEachSynched(navigationListeners, this, (listener) -> { try { listener.beforeLocalNavigate(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } }); return null; } }); } catch (final WrapperException we) { throw (NavigationVetoException) we.getCause(); } } private void dispatchBeforeWindowOpen(final NavigationEvent event) throws NavigationVetoException { try { AccessController.doPrivileged(new PrivilegedAction<Object>() { // Reason: Dispatching an event to extensions requires permission to, // among other things, setting the context class loader. public Object run() { try { ExtensionManager.getInstance().dispatchBeforeWindowOpen(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } ArrayUtilities.forEachSynched(navigationListeners, this, (listener) -> { try { listener.beforeWindowOpen(event); } catch (final NavigationVetoException nve) { throw new WrapperException(nve); } }); return null; } }); } catch (final WrapperException we) { throw (NavigationVetoException) we.getCause(); } } private void dispatchContentSet(final ContentEvent event) { ArrayUtilities.forEachSynched(contentListeners, this, (listener) -> { listener.contentSet(event); }); } private void dispatchResponseProcessed(final ResponseEvent event) { ArrayUtilities.forEachSynched(responseListeners, this, (listener) -> { listener.responseProcessed(event); }); } /** * Gets a {@link WindowCallback} instance that is used to dispatch information * during local navigation. The FramePanel tries to find an implementor of the * interface among its ancestor components. Unless overridden, this * implementation of <code>getWindowCallback</code> only looks for instances * of the {@link BrowserWindow} interface. */ protected WindowCallback getWindowCallback() { final FramePanel kpf = this.knownParentFrame; if (kpf != null) { return kpf.getWindowCallback(); } Container parent = this.getParent(); while ((parent != null) && !(parent instanceof BrowserWindow)) { parent = parent.getParent(); } if (parent == null) { return null; } return ((BrowserWindow) parent).getWindowCallback(); } /** * Gets the parent frame. This is <code>null<code> for the top-most frame * in a window or when the FramePanel is detached. */ public NavigatorFrame getParentFrame() { // TODO: Security? final NavigatorFrame kpf = this.knownParentFrame; if (kpf != null) { return kpf; } Container parent = this.getParent(); while ((parent != null) && !(parent instanceof NavigatorFrame)) { parent = parent.getParent(); } return (NavigatorFrame) parent; } /** * Gets the top-most frame in a window. It may return the current frame if its * parent is <code>null</code>. */ public NavigatorFrame getTopFrame() { NavigatorFrame current = this; for (;;) { final NavigatorFrame ancestor = current.getParentFrame(); if (ancestor == null) { return current; } current = ancestor; } } /** * Implements {@link NavigatorFrame#getComponent()}. */ public Component getComponent() { return this; } @Override public void paint(final Graphics g) { // Unless done this way, duplicate // painting occurs for nested frames. this.paintComponent(g); this.paintBorder(g); this.paintChildren(g); } /** * Gets an array of navigation entries that came before the current one. */ public List<NavigationEntry> getBackNavigationEntries() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { return this.navigationEngine.getBackNavigationEntries(); } } /** * Gets an array of navigation entries that would be visited with consecutive * <code>forward</code> calls. */ public List<NavigationEntry> getForwardNavigationEntries() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC); } synchronized (this) { return this.navigationEngine.getForwardNavigationEntries(); } } /** * Determines if the current navigation entry has associated source code. */ public boolean hasSource() { // TODO: Security? final ComponentContent content = this.content; return (content != null) && (content.getSourceCode() != null); } /** * Determines whether there's a selection with content to be copied in this * frame. */ public boolean canCopy() { // TODO: Security? final ComponentContent content = this.content; return content == null ? false : content.canCopy(); } /** * Copies the selection, if any, to the clipboard. Whether this method is * supported depends on content being rendered. */ public boolean copy() { // TODO: Security? final ComponentContent content = this.content; return content == null ? false : content.copy(); } /* public final void replaceContent(final Component component) { // TODO: Security? this.replaceContent(null, new SimpleComponentContent(component)); } */ /** * Replaces the content of the frame. This method can be safely called outside * the GUI dispatch thread. */ public void replaceContent(final ClientletResponse response, final ComponentContent content) { // Method probably invoked outside GUI thread. if (SwingUtilities.isEventDispatchThread()) { this.replaceContentImpl(response, content); } else { // Security note: Need to pass security context of caller // into invokeLater task. final AccessControlContext context = AccessController.getContext(); SwingUtilities.invokeLater(() -> { final PrivilegedAction<Object> action = () -> { FramePanel.this.replaceContentImpl(response, content); return null; }; AccessController.doPrivileged(action, context); }); } } @Override protected void addImpl(final Component comp, final Object constraints, final int index) { // Check security. Content downloaded off the web should // not be allowed to replace frame content at will. final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GenericLocalPermission.EXT_GENERIC); } super.addImpl(comp, constraints, index); } @Override public void remove(final Component comp) { // Check security. Content downloaded off the web should // not be allowed to replace frame content at will. final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GenericLocalPermission.EXT_GENERIC); } super.remove(comp); } @Override public void remove(final int index) { // Check security. Content downloaded off the web should // not be allowed to replace frame content at will. final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GenericLocalPermission.EXT_GENERIC); } super.remove(index); } @Override public void removeAll() { // Check security. Content downloaded off the web should // not be allowed to replace frame content at will. final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GenericLocalPermission.EXT_GENERIC); } super.removeAll(); } protected void replaceContentImpl(final ClientletResponse response, final ComponentContent content) { // Security note: Currently expected to be private. // Always called in GUI thread. // removeAll and add will invalidate. final ComponentContent oldContent = this.content; this.removeAll(); if (oldContent != null) { oldContent.removeNotify(); } if (content != null) { final Component component = content.getComponent(); if (component == null) { throw new java.lang.IllegalStateException("Component from " + content + " is null: " + response.getResponseURL() + "."); } this.add(component); } // Call to validate will lay out children. this.validate(); this.repaint(); // Set this at the end, after removal and addition of components has // succeded. this.content = content; if (content != null) { content.addNotify(); this.updateContentProperties(content); } if (response != null) { final String title = content == null ? null : content.getTitle(); final String description = content == null ? null : content.getDescription(); final NavigationEntry navigationEntry = NavigationEntry.fromResponse(this, response, title, description); if (response.isNewNavigationAction()) { synchronized (this) { this.navigationEngine.addNavigationEntry(navigationEntry); } } if (content != null) { if (PlatformInit.getInstance().debugOn) { System.out.println("Navigation over: " + response.getResponseURL()); } content.navigatedNotify(); } final WindowCallback wc = this.getWindowCallback(); if (wc != null) { // It's important that the handleDocumentRendering method be called // right after navigationEngine is updated. // TODO: Why? wc.handleDocumentRendering(this, response, content); } } else { // Notify so that lazy layouting algorithm can know that layouting is not blocked if (content != null) { content.navigatedNotify(); } } this.dispatchContentSet(new ContentEvent(this, content, response)); } /** * Clears current content. This method should be invoked in the GUI thread. */ public void clear() { this.removeAll(); this.content = null; } private Window getWindow() { // TODO: Security? Getting parent security? final FramePanel kpf = this.knownParentFrame; if (kpf != null) { return kpf.getWindow(); } Container parent = this.getParent(); if (parent instanceof FramePanel) { return ((FramePanel) parent).getWindow(); } while ((parent != null) && !(parent instanceof Window)) { parent = parent.getParent(); } return (Window) parent; } /** * Closes the window this frame belongs to. */ public void closeWindow() { // TODO: Security? final Window window = this.getWindow(); if (window != null) { window.dispose(); } } /** * Opens a confirmation dialog. */ public boolean confirm(final String message) { return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { // Reason: We don't want an "Applet Window" message and // it's no big deal to allow it here. public Boolean run() { return JOptionPane.showConfirmDialog(FramePanel.this, message) == JOptionPane.YES_OPTION; } }); } /** * Implements {@link NavigatorFrame#invokeLater(Runnable)}. */ public void invokeLater(final Runnable runnable) { SwingUtilities.invokeLater(runnable); } public final void navigate(final String urlOrPath) throws java.net.MalformedURLException { final URL url = DomainValidation.guessURL(urlOrPath); this.navigate(url, "GET", null, TargetType.SELF, RequestType.PROGRAMMATIC); } public final void navigate(final String urlOrPath, final RequestType requestType) throws java.net.MalformedURLException { final URL url = DomainValidation.guessURL(urlOrPath); this.navigate(url, "GET", null, TargetType.SELF, requestType); } public void navigate(final @NonNull URL url, final String method, final ParameterInfo paramInfo, final TargetType type, final RequestType requestType) { this.navigate(url, method, paramInfo, type, requestType, this); } public void navigate(final @NonNull URL url, final String method, final ParameterInfo paramInfo, final TargetType type, final RequestType requestType, final NavigatorFrame originatingFrame) { final NavigationEvent event = new NavigationEvent(this, url, method, paramInfo, type, requestType, originatingFrame); this.navigate(event); } private void navigate(final NavigationEvent event) { try { this.dispatchBeforeNavigate(event); } catch (final NavigationVetoException nve) { if (logger.isLoggable(Level.INFO)) { logger.info("navigateLocal(): Navigation was vetoed: " + nve.getMessage()); } return; } final TargetType type = event.getTargetType(); final URL url = event.getURL(); final String method = event.getMethod(); final ParameterInfo paramInfo = event.getParamInfo(); final RequestType requestType = event.getRequestType(); switch (type) { case PARENT: final NavigatorFrame parent = this.getParentFrame(); if (parent != null) { parent.navigate(url, method, paramInfo, TargetType.SELF, requestType, this); } else { this.navigateLocal(event); } break; case TOP: final NavigatorFrame top = this.getTopFrame(); if (top == this) { this.navigateLocal(event); } else { top.navigate(url, method, paramInfo, TargetType.SELF, requestType, this); } break; case SELF: this.navigateLocal(event); break; case BLANK: this.open(url, method, paramInfo); break; } } private void navigateToHistoryEntry(final @NonNull URL url) { this.navigateLocal(url, "GET", RequestType.HISTORY, this); } protected boolean isOKToAddReferrer(final RequestType requestType) { return (requestType == RequestType.CLICK) || (requestType == RequestType.PROGRAMMATIC) || (requestType == RequestType.PROGRAMMATIC_FROM_CLICK) || (requestType == RequestType.OPEN_WINDOW) || (requestType == RequestType.OPEN_WINDOW_FROM_CLICK) || (requestType == RequestType.FORM); } private void navigateLocal(final @NonNull URL url, final String method, final RequestType requestType, final FramePanel originatingFrame) { final NavigationEvent event = new NavigationEvent(this, url, method, requestType, originatingFrame); this.navigateLocal(event); } private void navigateLocal(final NavigationEvent event) { requestManager.reset(event.getURL()); try { this.dispatchBeforeLocalNavigate(event); } catch (final NavigationVetoException nve) { if (logger.isLoggable(Level.INFO)) { logger.info("navigateLocal(): Navigation was vetoed: " + nve.getMessage()); } return; } String referrer = null; final RequestType requestType = event.getRequestType(); final URL url = event.getURL(); final String method = event.getMethod(); final ParameterInfo paramInfo = event.getParamInfo(); if (this.isOKToAddReferrer(requestType)) { // TODO: When child frame does a _top navigate, referrer // should apparently be from child. final NavigationEntry entry = this.getCurrentNavigationEntry(); if (entry != null) { referrer = entry.getUrl().toExternalForm(); } } final ClientletRequest request = new ClientletRequestImpl(false, url, method, paramInfo, null, referrer, null, requestType); final UserAgentContext uaContext = new SilentUserAgentContextImpl(this); final RequestHandler handler = new ClientletRequestHandler(request, this.getWindowCallback(), this, uaContext); SecurityUtil.doPrivileged(() -> { // Justification: While requests by untrusted code are generally only // allowed on certain hosts, navigation is an exception. RequestEngine.getInstance().scheduleRequest(handler); return null; }); } /** * Opens a window and renders the URL or path provided. * * @return The top frame of the new window. */ public final NavigatorFrame open(final String urlOrPath) throws MalformedURLException { final URL url = DomainValidation.guessURL(urlOrPath); return this.open(url, (Properties) null); } /** * Opens a window and renders the URL provided. * * @param url * The URL of the document. * @param windowProperties * A Properties object that follows JavaScript Window.open() * conventions. * @return The top frame of the new window. */ public final NavigatorFrame open(final @NonNull URL url, final Properties windowProperties) { return this.open(url, null, windowProperties); } /** * Opens a window and renders the URL provided. * * @param url * The URL of the document. * @param windowId * A unique ID for the window. * @param windowProperties * A Properties object that follows JavaScript Window.open() * conventions. * @return The top frame of the new window. */ public final NavigatorFrame open(final @NonNull URL url, final String windowId, final Properties windowProperties) { return this.open(url, "GET", null, windowId, windowProperties); } /** * Opens a window and renders the URL provided. * * @param url * The URL of the document. * @return The top frame of the new window. */ public final NavigatorFrame open(final @NonNull URL url) { return this.open(url, (Properties) null); } /** * Opens a window and renders the URL provided. This method is called to * request that a new window is opened. For example, this method will be * invoked on JavaScript Window.open() calls. Override to be informed of such * calls. * * @return The top frame of the new window. The method may return * <code>null</code> if navigation was vetoed by a listener. */ public NavigatorFrame open(final @NonNull URL url, final String method, final ParameterInfo pinfo, final String windowId, final Properties windowProperties) { final NavigationEvent event = new NavigationEvent(this, url, method, pinfo, TargetType.BLANK, RequestType.OPEN_WINDOW, this); try { this.dispatchBeforeWindowOpen(event); } catch (final NavigationVetoException nve) { if (logger.isLoggable(Level.INFO)) { logger.info("navigateLocal(): Navigation was vetoed: " + nve.getMessage()); } return null; } return FramePanel.openWindow(this, url, windowId, windowProperties, method, pinfo); } /** * Opens a window and renders the URL provided. * * @param url * The URL of the document. * @param method * The request method. * @param pinfo * Any additional parameter data. * @return The top frame of the new window. */ public final NavigatorFrame open(final @NonNull URL url, final String method, final ParameterInfo pinfo) { return this.open(url, method, pinfo, null, null); } /** * Static method for opening a window. * * @return The top frame in the window that was opened. */ public static NavigatorFrame openWindow(final FramePanel opener, final @NonNull URL url, final String windowId, final Properties windowProperties, final String method, final ParameterInfo pinfo) { final ClientletRequest request = new ClientletRequestImpl(true, url, method, pinfo, RequestType.OPEN_WINDOW); final NavigatorWindowImpl wcontext = AccessController.doPrivileged(new PrivilegedAction<NavigatorWindowImpl>() { // Reason: Window creation can require special permissions at various // levels, e.g. ExtensionManager access and os.version check in Swing. public NavigatorWindowImpl run() { return new NavigatorWindowImpl(opener, windowId, windowProperties); } }); final FramePanel newFrame = wcontext.getFramePanel(); newFrame.requestManager.reset(url); final UserAgentContext uaContext = new SilentUserAgentContextImpl(newFrame); final ClientletRequestHandler handler = new ClientletRequestHandler(request, wcontext, newFrame, uaContext); SwingUtilities.invokeLater(() -> wcontext.resetAsNavigator(handler.getContextWindowProperties())); SecurityUtil.doPrivileged(() -> { // Justification: While requests by untrusted code are generally only allowed on certain hosts, // navigation is an exception. RequestEngine.getInstance().scheduleRequest(handler); return null; }); return newFrame; } /** * Opens a message prompt dialog. */ public String prompt(final String message, final String inputDefault) { return AccessController.doPrivileged(new PrivilegedAction<String>() { // Reason: We don't want an "Applet Window" message and // it's no big deal to allow it here. public String run() { return JOptionPane.showInputDialog(FramePanel.this, message, inputDefault); } }); } /** * Sends the window to the back (blur). */ public void windowToBack() { final Window window = this.getWindow(); if (window != null) { window.toBack(); } } /** * Sends the window to the front and grabs focus for the frame. */ public void windowToFront() { final Window window = this.getWindow(); if (window != null) { window.toFront(); } this.grabFocus(); } /** * Opens an alert dialog. */ public void alert(final String message) { AccessController.doPrivileged(new PrivilegedAction<Object>() { // Reason: We don't want an "Applet Window" message and // it's no big deal to allow it here. public Object run() { JOptionPane.showMessageDialog(FramePanel.this, message); return null; } }); } /** * Navigates to the given entry without adding the entry to frame history. * This is the mechanism that should be used to "go back" to an entry already * visited. */ public boolean goTo(final NavigationEntry entry) { if (!"GET".equals(entry.getMethod())) { throw new IllegalArgumentException("Method only accepts entries with GET method."); } this.navigateToHistoryEntry(entry.getUrl()); synchronized (this) { return this.navigationEngine.moveTo(entry); } } /** * Navigates back. */ public boolean back() { return this.moveNavigation(-1); } /** * Navigates forward. */ public boolean forward() { return this.moveNavigation(+1); } private boolean moveNavigation(final int offset) { if ((offset == 0) || (offset > 1) || (offset < -1)) { throw new IllegalArgumentException("offset: only +1 or -1 are allowed"); } synchronized (this) { NavigationEntry newEntry; for (;;) { newEntry = this.navigationEngine.move(offset); if (newEntry == null) { return false; } if (!"GET".equals(newEntry.getMethod())) { // back() and forward() only supported for GET. continue; } break; } this.navigateToHistoryEntry(newEntry.getUrl()); return true; } } /** * Determines whether the frame can navigate forward. */ public boolean canForward() { synchronized (this) { return this.navigationEngine.hasNextWithGET(); } } /** * Determines whether the frame can navigate back. */ public boolean canBack() { synchronized (this) { return this.navigationEngine.hasPrevWithGET(); } } /** * Reloads the current document. */ public void reload() { NavigationEntry entry; synchronized (this) { entry = this.navigationEngine.getCurrentEntry(); } if (entry != null) { final String method = entry.getMethod(); if (!"GET".equals(method)) { final String lineBreak = System.getProperty("line.separator"); this.alert("Reloading a document not obtained with the GET " + lineBreak + "method is disallowed for security reasons." + lineBreak + "The request method of the current page is " + method + "."); } else { this.navigateLocal(entry.getUrl(), entry.getMethod(), RequestType.SOFT_RELOAD, this); } } } /** * Determines whether the current document can be reloaded. */ public boolean canReload() { NavigationEntry entry; synchronized (this) { entry = this.navigationEngine.getCurrentEntry(); } // Check for request method or not? return entry != null; } /** * Creates a frame that is expected to be used as a child of the current one. */ public NavigatorFrame createFrame() { return FramePanelFactorySource.getInstance().getActiveFactory().createFramePanel(this); } /** * Gets the default window status. */ public String getDefaultStatus() { final WindowCallback wc = this.getWindowCallback(); if (wc != null) { return wc.getDefaultStatus(); } else { return null; } } public Object getItem(final String name) { return Items.getItem(this, name); } /** * Gets the frame that opened the current frame, if any. */ public NavigatorFrame getOpenerFrame() { return this.openerFrame; } /** * Gets the current window status. */ public String getStatus() { final WindowCallback wc = this.getWindowCallback(); if (wc != null) { return wc.getStatus(); } else { return null; } } /** * Gets the window ID if this is the top frame in a window. */ public String getWindowId() { return this.windowId; } /** * Determines if the window is closed. */ public boolean isWindowClosed() { final Window window = this.getWindow(); if (window != null) { return !window.isDisplayable(); } return true; } /** * Sets the default window status. */ public void setDefaultStatus(final String value) { final WindowCallback wc = this.getWindowCallback(); if (wc != null) { wc.setDefaultStatus(this, value); } } public void setItem(final String name, final Object value) { Items.setItem(this, name, value); } /** * Sets the window status. */ public void setStatus(final String status) { final WindowCallback wc = this.getWindowCallback(); if (wc != null) { wc.setStatus(this, status); } } @Override public Dimension getPreferredSize() { if (this.isPreferredSizeSet()) { return super.getPreferredSize(); } else { return new Dimension(600, 400); } } @Override public Dimension getMinimumSize() { return new Dimension(1, 1); } @Override public Dimension getMaximumSize() { return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); } private ComponentContent content; /** * Gets the component content currently set in the frame. */ public ComponentContent getComponentContent() { // TODO: Security? return this.content; } public String getSourceCode() { final ComponentContent content = this.content; return content == null ? null : content.getSourceCode(); } public final void navigate(final @NonNull URL url) { this.navigate(url, RequestType.PROGRAMMATIC); } public final void navigate(final @NonNull URL url, final RequestType requestType) { this.navigate(url, "GET", null, TargetType.SELF, requestType); } /** * Gets the current navigation entry. */ public NavigationEntry getCurrentNavigationEntry() { synchronized (this) { return this.navigationEngine.getCurrentEntry(); } } public NavigatorProgressEvent getProgressEvent() { return this.progressEvent; } private NavigatorProgressEvent progressEvent; public void setProgressEvent(final NavigatorProgressEvent event) { this.progressEvent = event; if (event != null) { final WindowCallback wc = this.getWindowCallback(); if (wc != null) { wc.updateProgress(event); } } } public NetworkRequest createNetworkRequest() { final UserAgentContext uaContext = new SilentUserAgentContextImpl(this); return new org.lobobrowser.context.NetworkRequestImpl(uaContext); } @Override public String toString() { return "FramePanel[windowId=" + windowId + ",hashCode=" + this.hashCode() + ",parent=" + this.getParent() + "]"; } public void resizeWindowBy(final int byWidth, final int byHeight) { final Window window = this.getWindow(); if (logger.isLoggable(Level.INFO)) { logger.info("resizeWindowBy(): byWidth=" + byWidth + ",byHeight=" + byHeight + "; window=" + window); } if (window != null) { window.setSize(window.getWidth() + byWidth, window.getHeight() + byHeight); } } public void resizeWindowTo(final int width, final int height) { final Window window = this.getWindow(); if (logger.isLoggable(Level.INFO)) { logger.info("resizeWindowTo(): width=" + width + ",height=" + height + "; window=" + window); } if (window != null) { window.setSize(width, height); } } public Window getTopFrameWindow() { return topFrameWindow; } public void setTopFrameWindow(final Window topFrameWindow) { this.topFrameWindow = topFrameWindow; } /** * Gets an object that is used to represent the current frame content. For * example, if the frame is currently showing HTML, this method will probably * return an instance of <code>org.w3c.dom.html2.HTMLDocument</code>. */ public Object getContentObject() { final ComponentContent content = this.getComponentContent(); return content == null ? null : content.getContentObject(); } /** * Gets a mime type that goes with the object returned by * {@link FramePanel#getContentObject()}. This is not necessarily the same as * the mime type declared in the headers of the response that produced the * current content. */ public String getCurrentMimeType() { final ComponentContent content = this.getComponentContent(); return content == null ? null : content.getMimeType(); } public void linkClicked(final @NonNull URL url, final TargetType targetType, final Object linkObject) { final NavigationEvent event = new NavigationEvent(this, url, targetType, RequestType.CLICK, linkObject, this); this.navigate(event); } public int getHistoryLength() { synchronized (this) { return this.navigationEngine.getLength(); } } public Optional<NavigationEntry> getNextNavigationEntry() { synchronized (this) { final List<NavigationEntry> entries = this.navigationEngine.getForwardNavigationEntries(); return entries.stream().findFirst(); } } public Optional<NavigationEntry> getPreviousNavigationEntry() { synchronized (this) { final List<NavigationEntry> entries = this.navigationEngine.getBackNavigationEntries(); return entries.stream().findFirst(); } } public void moveInHistory(final int offset) { this.moveNavigation(offset); } public void navigateInHistory(final String absoluteURL) { NavigationEntry entry; synchronized (this) { entry = this.navigationEngine.findEntry(absoluteURL); } if (entry != null) { this.navigateToHistoryEntry(entry.getUrl()); } } private Map<String, Object> contentProperties = null; public void setProperty(final String name, final Object value) { final ComponentContent content = this.getComponentContent(); synchronized (this.propertiesMonitor) { if (content != null) { content.setProperty(name, value); } Map<String, Object> props = this.contentProperties; if (props == null) { props = new HashMap<>(5); this.contentProperties = props; } props.put(name, value); } } private void updateContentProperties(final ComponentContent content) { synchronized (this.propertiesMonitor) { final Map<String, Object> props = this.contentProperties; if (props != null) { final Iterator<Entry<String, Object>> i = props.entrySet().iterator(); while (i.hasNext()) { final Entry<String, Object> entry = i.next(); content.setProperty(entry.getKey(), entry.getValue()); } } } } private final RequestManager requestManager = new RequestManager(this); public boolean isRequestPermitted(final Request request) { return requestManager.isRequestPermitted(request); } public void manageRequests(final Object initiator) { requestManager.manageRequests((JComponent) initiator); } public void allowAllFirstPartyRequests() { requestManager.allowAllFirstPartyRequests(); } }