package org.lobobrowser.html.domimpl; import java.net.URL; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.html.BrowserFrame; import org.lobobrowser.html.js.Event; import org.lobobrowser.html.js.Executor; import org.lobobrowser.html.js.Window; import org.lobobrowser.html.js.Window.JSRunnableTask; import org.lobobrowser.html.style.IFrameRenderState; import org.lobobrowser.html.style.RenderState; import org.lobobrowser.js.HideFromJS; import org.lobobrowser.ua.UserAgentContext.Request; import org.lobobrowser.ua.UserAgentContext.RequestKind; import org.mozilla.javascript.Function; import org.w3c.dom.Document; import org.w3c.dom.html.HTMLIFrameElement; public class HTMLIFrameElementImpl extends HTMLAbstractUIElement implements HTMLIFrameElement, FrameNode { private volatile BrowserFrame browserFrame; public HTMLIFrameElementImpl(final String name) { super(name); } @HideFromJS public void setBrowserFrame(final BrowserFrame frame) { this.browserFrame = frame; createJob(); } private boolean jobCreated = false; private void createJob() { synchronized (this) { final String src = this.getAttribute("src"); if (src != null) { if (!jobCreated) { ((HTMLDocumentImpl) document).addJob(() -> loadURLIntoFrame(src), false); jobCreated = true; } else { ((HTMLDocumentImpl) document).addJob(() -> loadURLIntoFrame(src), false, 0); } } else { markJobDone(0, isAttachedToDocument()); } } } private void markJobDone(final int jobs, final boolean loaded) { synchronized (this) { ((HTMLDocumentImpl) document).markJobsFinished(jobs, false); jobCreated = false; if (loaded) { if (onload != null) { // TODO: onload event object? final Window window = ((HTMLDocumentImpl) document).getWindow(); window.addJSTask(new JSRunnableTask(0, "IFrame onload handler", () -> { Executor.executeFunction(HTMLIFrameElementImpl.this, onload, new Event("load", HTMLIFrameElementImpl.this), window.getContextFactory()); })); } dispatchEvent(new Event("load", this)); } } } public BrowserFrame getBrowserFrame() { return this.browserFrame; } public String getAlign() { return this.getAttribute("align"); } public Document getContentDocument() { // TODO: Domain-based security final BrowserFrame frame = this.browserFrame; if (frame == null) { // Not loaded yet return null; } { // TODO: Remove this very ugly hack. // This is required because the content document is sometimes not ready, even though the browser frame is. // The browser frame is created by the layout thread, but the iframe is loaded in the window's JS Scheduler thread. // See GH #140 int count = 10; while (count > 0 && frame.getContentDocument() == null) { try { Thread.sleep(100); } catch (final InterruptedException e) { throw new RuntimeException("Error while waiting for iframe document"); } count--; } } return frame.getContentDocument(); } public void setContentDocument(final Document d) { final BrowserFrame frame = this.browserFrame; if (frame == null) { // TODO: This needs to be handled. return; } frame.setContentDocument(d); } public Window getContentWindow() { final BrowserFrame frame = this.browserFrame; if (frame == null) { // Not loaded yet return null; } return Window.getWindow(frame.getHtmlRendererContext()); } public String getFrameBorder() { return this.getAttribute("frameborder"); } public String getHeight() { return this.getAttribute("height"); } public String getLongDesc() { return this.getAttribute("longdesc"); } public String getMarginHeight() { return this.getAttribute("marginheight"); } public String getMarginWidth() { return this.getAttribute("marginwidth"); } public String getName() { return this.getAttribute("name"); } public String getScrolling() { return this.getAttribute("scrolling"); } public String getSrc() { return this.getAttribute("src"); } public String getWidth() { return this.getAttribute("width"); } public void setAlign(final String align) { this.setAttribute("align", align); } public void setFrameBorder(final String frameBorder) { this.setAttribute("frameborder", frameBorder); } public void setHeight(final String height) { this.setAttribute("height", height); } public void setLongDesc(final String longDesc) { this.setAttribute("longdesc", longDesc); } public void setMarginHeight(final String marginHeight) { this.setAttribute("marginHeight", marginHeight); } public void setMarginWidth(final String marginWidth) { this.setAttribute("marginWidth", marginWidth); } public void setName(final String name) { this.setAttribute("name", name); } public void setScrolling(final String scrolling) { this.setAttribute("scrolling", scrolling); } public void setSrc(final String src) { this.setAttribute("src", src); } public void setWidth(final String width) { this.setAttribute("width", width); } @Override protected void handleAttributeChanged(String name, String oldValue, String newValue) { super.handleAttributeChanged(name, oldValue, newValue); if ("src".equals(name)) { createJob(); } } @Override protected void handleDocumentAttachmentChanged() { super.handleDocumentAttachmentChanged(); if (isAttachedToDocument()) { if (hasAttribute("onload")) { setOnload(getEventFunction(null, "onload")); } } } private Function onload; public Function getOnload() { return this.getEventFunction(this.onload, "onload"); } public void setOnload(final Function onload) { this.onload = onload; } private void loadURLIntoFrame(final String value) { final BrowserFrame frame = this.browserFrame; if (frame != null) { try { final URL fullURL = value == null ? null : this.getFullURL(value); if (fullURL != null) { if (getUserAgentContext().isRequestPermitted(new Request(fullURL, RequestKind.Frame))) { frame.getHtmlRendererContext().setJobFinishedHandler(new Runnable() { public void run() { System.out.println("Iframes window's job over!"); markJobDone(1, true); } }); // frame.loadURL(fullURL); // ^^ Using window.open is better because it fires the various events correctly. getContentWindow().open(fullURL.toExternalForm(), "iframe", "", true); } else { System.out.println("Request not permitted: " + fullURL); markJobDone(1, false); } } else { this.warn("Can't load URL: " + value); // TODO: Plug: marking as load=true because we are not handling javascript URIs currently. // javascript URI is being used in some of the web-platform-tests. markJobDone(1, true); } } catch (final java.net.MalformedURLException mfu) { this.warn("loadURLIntoFrame(): Unable to navigate to src.", mfu); markJobDone(1, false); } finally { /* TODO: Implement an onload handler // Copied from image element final Function onload = this.getOnload(); System.out.println("onload: " + onload); if (onload != null) { // TODO: onload event object? Executor.executeFunction(HTMLIFrameElementImpl.this, onload, null); }*/ } } } @Override protected @NonNull RenderState createRenderState(final RenderState prevRenderState) { return new IFrameRenderState(prevRenderState, this); } }