org.htmlunit.html.BaseFrameElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
The newest version!
/*
* Copyright (c) 2002-2024 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.html;
import static org.htmlunit.BrowserVersionFeatures.FRAME_LOCATION_ABOUT_BLANK_FOR_ABOUT_SCHEME;
import static org.htmlunit.BrowserVersionFeatures.URL_MINIMAL_QUERY_ENCODING;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.htmlunit.FailingHttpStatusCodeException;
import org.htmlunit.FrameContentHandler;
import org.htmlunit.Page;
import org.htmlunit.SgmlPage;
import org.htmlunit.WebClient;
import org.htmlunit.WebClientOptions;
import org.htmlunit.WebRequest;
import org.htmlunit.WebWindow;
import org.htmlunit.javascript.AbstractJavaScriptEngine;
import org.htmlunit.javascript.PostponedAction;
import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
import org.htmlunit.util.UrlUtils;
import org.w3c.dom.Attr;
/**
* Base class for frame and iframe.
*
* @author Mike Bowler
* @author David K. Taylor
* @author Christian Sell
* @author Marc Guillemot
* @author David D. Kilzer
* @author Stefan Anzinger
* @author Ahmed Ashour
* @author Dmitri Zoubkov
* @author Daniel Gredler
* @author Ronald Brill
* @author Frank Danek
*/
public abstract class BaseFrameElement extends HtmlElement {
private static final Log LOG = LogFactory.getLog(BaseFrameElement.class);
private FrameWindow enclosedWindow_;
private boolean contentLoaded_;
private boolean loadSrcWhenAddedToPage_;
/**
* Creates an instance of BaseFrame.
*
* @param qualifiedName the qualified name of the element type to instantiate
* @param page the HtmlPage that contains this element
* @param attributes the initial attributes
*/
protected BaseFrameElement(final String qualifiedName, final SgmlPage page,
final Map attributes) {
super(qualifiedName, page, attributes);
init();
if (null != page && page.isHtmlPage() && ((HtmlPage) page).isParsingHtmlSnippet()) {
// if created by the HTMLParser the src attribute is not set via setAttribute() or some other method but is
// part of the given attributes already.
final String src = getSrcAttribute();
if (ATTRIBUTE_NOT_DEFINED != src && !UrlUtils.ABOUT_BLANK.equals(src)) {
loadSrcWhenAddedToPage_ = true;
}
}
}
private void init() {
FrameWindow enclosedWindow = null;
try {
final HtmlPage htmlPage = getHtmlPageOrNull();
if (null != htmlPage) { // if loaded as part of XHR.responseXML, don't load content
enclosedWindow = new FrameWindow(this);
// put about:blank in the window to allow JS to run on this frame before the
// real content is loaded
final WebClient webClient = htmlPage.getWebClient();
final HtmlPage temporaryPage = webClient.getPage(enclosedWindow, WebRequest.newAboutBlankRequest());
temporaryPage.setReadyState(READY_STATE_LOADING);
}
}
catch (final FailingHttpStatusCodeException | IOException e) {
// should never occur
}
enclosedWindow_ = enclosedWindow;
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* Called after the node for the {@code frame} or {@code iframe} has been added to the containing page.
* The node needs to be added first to allow JavaScript in the frame to see the frame in the parent.
* @throws FailingHttpStatusCodeException if the server returns a failing status code AND the property
* {@link org.htmlunit.WebClientOptions#setThrowExceptionOnFailingStatusCode(boolean)} is
* set to true
*/
public void loadInnerPage() throws FailingHttpStatusCodeException {
String source = getSrcAttribute();
if (source.isEmpty()) {
source = UrlUtils.ABOUT_BLANK;
}
else if (StringUtils.startsWithIgnoreCase(source, UrlUtils.ABOUT_SCHEME)
&& hasFeature(FRAME_LOCATION_ABOUT_BLANK_FOR_ABOUT_SCHEME)) {
source = UrlUtils.ABOUT_BLANK;
}
loadInnerPageIfPossible(source);
final Page enclosedPage = getEnclosedPage();
if (enclosedPage != null && enclosedPage.isHtmlPage()) {
final HtmlPage htmlPage = (HtmlPage) enclosedPage;
final AbstractJavaScriptEngine> jsEngine = htmlPage.getWebClient().getJavaScriptEngine();
if (jsEngine != null && jsEngine.isScriptRunning()) {
final PostponedAction action = new PostponedAction(getPage(), "BaseFrame.loadInnerPage") {
@Override
public void execute() {
htmlPage.setReadyState(READY_STATE_COMPLETE);
}
};
jsEngine.addPostponedAction(action);
}
else {
htmlPage.setReadyState(READY_STATE_COMPLETE);
}
}
}
/**
* Indicates if the content specified by the {@code src} attribute has been loaded or not.
* The initial state of a frame contains an "about:blank" that is not loaded like
* something specified in {@code src} attribute.
* @return {@code false} if the frame is still in its initial state.
*/
boolean isContentLoaded() {
return contentLoaded_;
}
/**
* Changes the state of the {@code contentLoaded_} attribute to true.
* This is needed, if the content is set from javascript to avoid
* later overwriting from method org.htmlunit.html.HtmlPage.loadFrames().
*/
void setContentLoaded() {
contentLoaded_ = true;
}
/**
* @throws FailingHttpStatusCodeException if the server returns a failing status code AND the property
* {@link WebClientOptions#setThrowExceptionOnFailingStatusCode(boolean)} is set to true
*/
private void loadInnerPageIfPossible(final String src) throws FailingHttpStatusCodeException {
setContentLoaded();
String source = src;
final SgmlPage page = getPage();
final WebClient webClient = page.getWebClient();
final FrameContentHandler handler = webClient.getFrameContentHandler();
if (null != handler && !handler.loadFrameDocument(this)) {
source = UrlUtils.ABOUT_BLANK;
}
if (!source.isEmpty()) {
final URL url;
try {
url = ((HtmlPage) page).getFullyQualifiedUrl(source);
}
catch (final MalformedURLException e) {
notifyIncorrectness("Invalid src attribute of " + getTagName() + ": url=[" + source + "]. Ignored.");
return;
}
final WebRequest request = new WebRequest(url, page.getCharset(), page.getUrl());
if (isAlreadyLoadedByAncestor(url, request.getCharset())) {
notifyIncorrectness("Recursive src attribute of " + getTagName() + ": url=[" + source + "]. Ignored.");
return;
}
try {
webClient.getPage(enclosedWindow_, request);
}
catch (final IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("IOException when getting content for " + getTagName() + ": url=[" + url + "]", e);
}
}
}
}
/**
* Test if the provided URL is the one of the parents which would cause an infinite loop.
* @param url the URL to test
* @param charset the request charset
* @return {@code false} if no parent has already this URL
*/
private boolean isAlreadyLoadedByAncestor(final URL url, final Charset charset) {
WebWindow window = getPage().getEnclosingWindow();
int nesting = 0;
while (window instanceof FrameWindow) {
nesting++;
if (nesting > 9) {
return true;
}
final URL encUrl = UrlUtils.encodeUrl(url,
window.getWebClient().getBrowserVersion().hasFeature(URL_MINIMAL_QUERY_ENCODING),
charset);
if (UrlUtils.sameFile(encUrl, window.getEnclosedPage().getUrl())) {
return true;
}
if (window == window.getParentWindow()) {
// TODO: should getParentWindow() return null on top windows?
window = null;
}
else {
window = window.getParentWindow();
}
}
return false;
}
/**
* Returns the value of the attribute {@code longdesc}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code longdesc} or an empty string if that attribute isn't defined
*/
public final String getLongDescAttribute() {
return getAttributeDirect("longdesc");
}
/**
* Returns the value of the attribute {@code name}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
*/
public final String getNameAttribute() {
return getAttributeDirect(NAME_ATTRIBUTE);
}
/**
* Sets the value of the {@code name} attribute.
*
* @param name the new window name
*/
public final void setNameAttribute(final String name) {
setAttribute(NAME_ATTRIBUTE, name);
}
/**
* Returns the value of the attribute {@code src}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code src} or an empty string if that attribute isn't defined
*/
public final String getSrcAttribute() {
return getSrcAttributeNormalized();
}
/**
* Returns the value of the attribute {@code frameborder}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code frameborder} or an empty string if that attribute isn't defined
*/
public final String getFrameBorderAttribute() {
return getAttributeDirect("frameborder");
}
/**
* Returns the value of the attribute {@code marginwidth}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code marginwidth} or an empty string if that attribute isn't defined
*/
public final String getMarginWidthAttribute() {
return getAttributeDirect("marginwidth");
}
/**
* Returns the value of the attribute {@code marginheight}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code marginheight} or an empty string if that attribute isn't defined
*/
public final String getMarginHeightAttribute() {
return getAttributeDirect("marginheight");
}
/**
* Returns the value of the attribute {@code noresize}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code noresize} or an empty string if that attribute isn't defined
*/
public final String getNoResizeAttribute() {
return getAttributeDirect("noresize");
}
/**
* Returns the value of the attribute {@code scrolling}. Refer to the
* HTML 4.01
* documentation for details on the use of this attribute.
*
* @return the value of the attribute {@code scrolling} or an empty string if that attribute isn't defined
*/
public final String getScrollingAttribute() {
return getAttributeDirect("scrolling");
}
/**
* Returns the value of the attribute {@code onload}. This attribute is not
* actually supported by the HTML specification however it is supported
* by the popular browsers.
*
* @return the value of the attribute {@code onload} or an empty string if that attribute isn't defined
*/
public final String getOnLoadAttribute() {
return getAttributeDirect("onload");
}
/**
* Returns the currently loaded page in the enclosed window.
* This is a facility method for getEnclosedWindow().getEnclosedPage()
.
* @see WebWindow#getEnclosedPage()
* @return the currently loaded page in the enclosed window, or {@code null} if no page has been loaded
*/
public Page getEnclosedPage() {
return getEnclosedWindow().getEnclosedPage();
}
/**
* Gets the window enclosed in this frame.
* @return the window enclosed in this frame
*/
public FrameWindow getEnclosedWindow() {
return enclosedWindow_;
}
/**
* Sets the value of the {@code src} attribute. Also loads the frame with the specified URL, if possible.
* @param attribute the new value of the {@code src} attribute
*/
public final void setSrcAttribute(final String attribute) {
setAttribute(SRC_ATTRIBUTE, attribute);
}
/**
* {@inheritDoc}
*/
@Override
protected void setAttributeNS(final String namespaceURI, final String qualifiedName, String attributeValue,
final boolean notifyAttributeChangeListeners, final boolean notifyMutationObserver) {
final String qualifiedNameLC = org.htmlunit.util.StringUtils.toRootLowerCase(qualifiedName);
if (null != attributeValue && SRC_ATTRIBUTE.equals(qualifiedNameLC)) {
attributeValue = attributeValue.trim();
}
super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
notifyMutationObserver);
// do not use equals() here
// see HTMLIFrameElement2Test.documentCreateElement_onLoad_srcAboutBlank()
if (SRC_ATTRIBUTE.equals(qualifiedNameLC) && UrlUtils.ABOUT_BLANK != attributeValue) {
if (isAttachedToPage()) {
loadSrc();
}
else {
loadSrcWhenAddedToPage_ = true;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Attr setAttributeNode(final Attr attribute) {
final String qualifiedName = attribute.getName();
String attributeValue = null;
if (SRC_ATTRIBUTE.equals(qualifiedName)) {
attributeValue = attribute.getValue().trim();
}
final Attr result = super.setAttributeNode(attribute);
if (SRC_ATTRIBUTE.equals(qualifiedName) && !UrlUtils.ABOUT_BLANK.equals(attributeValue)) {
if (isAttachedToPage()) {
loadSrc();
}
else {
loadSrcWhenAddedToPage_ = true;
}
}
return result;
}
private void loadSrc() {
loadSrcWhenAddedToPage_ = false;
final String src = getSrcAttribute();
// recreate a window if the old one was closed
if (enclosedWindow_.isClosed()) {
init();
}
final AbstractJavaScriptEngine> jsEngine = getPage().getWebClient().getJavaScriptEngine();
// When src is set from a script, loading is postponed until script finishes
// in fact this implementation is probably wrong: JavaScript URL should be
// first evaluated and only loading, when any, should be postponed.
if (jsEngine == null || !jsEngine.isScriptRunning()
|| src.startsWith(JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
loadInnerPageIfPossible(src);
}
else {
final Page pageInFrame = getEnclosedPage();
final PostponedAction action = new PostponedAction(getPage(), "BaseFrame.loadSrc") {
@Override
public void execute() throws Exception {
if (!src.isEmpty() && getSrcAttribute().equals(src)) {
loadInnerPage();
}
}
@Override
public boolean isStillAlive() {
// skip if page in frame has already been changed
return super.isStillAlive() && pageInFrame == getEnclosedPage();
}
};
jsEngine.addPostponedAction(action);
}
}
/**
* Creates a new {@link WebWindow} for the new clone.
* {@inheritDoc}
*/
@Override
public DomNode cloneNode(final boolean deep) {
final BaseFrameElement clone = (BaseFrameElement) super.cloneNode(deep);
clone.init();
return clone;
}
@Override
protected void onAddedToPage() {
super.onAddedToPage();
if (loadSrcWhenAddedToPage_) {
loadSrc();
}
}
@Override
public void remove() {
super.remove();
loadSrcWhenAddedToPage_ = true;
getEnclosedWindow().close();
}
@Override
public final void removeAttribute(final String attributeName) {
super.removeAttribute(attributeName);
// TODO find a better implementation without all the code duplication
if (isAttachedToPage()) {
loadSrcWhenAddedToPage_ = false;
final String src = getSrcAttribute();
final AbstractJavaScriptEngine> jsEngine = getPage().getWebClient().getJavaScriptEngine();
// When src is set from a script, loading is postponed until script finishes
// in fact this implementation is probably wrong: JavaScript URL should be
// first evaluated and only loading, when any, should be postponed.
if (jsEngine == null || !jsEngine.isScriptRunning()) {
loadInnerPageIfPossible(src);
}
else {
final Page pageInFrame = getEnclosedPage();
final PostponedAction action = new PostponedAction(getPage(), "BaseFrame.removeAttribute") {
@Override
public void execute() throws Exception {
loadInnerPage();
}
@Override
public boolean isStillAlive() {
// skip if page in frame has already been changed
return super.isStillAlive() && pageInFrame == getEnclosedPage();
}
};
jsEngine.addPostponedAction(action);
}
}
else {
loadSrcWhenAddedToPage_ = true;
}
}
}