/* * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.webkit.drt; import com.sun.javafx.application.PlatformImpl; import com.sun.javafx.logging.PlatformLogger; import com.sun.javafx.logging.PlatformLogger.Level; import com.sun.webkit.*; import com.sun.webkit.graphics.*; import static com.sun.webkit.network.URLs.newURL; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; import javafx.scene.web.WebEngine; public final class DumpRenderTree { private final static PlatformLogger log = PlatformLogger.getLogger("DumpRenderTree"); private final static long PID = (new Date()).getTime() & 0xFFFF; private final static String fileSep = System.getProperty("file.separator"); private static boolean forceDumpAsText = false; final static PrintWriter out; static { try { out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( System.out, "UTF-8")), true); } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } } static volatile DumpRenderTree drt; private final WebPage webPage; private final UIClientImpl uiClient; private final EventSender eventSender; private CountDownLatch latch; private String testPath; private boolean loaded; private boolean waiting; private boolean complete; class ThemeClientImplStub extends ThemeClient { @Override protected RenderTheme createRenderTheme() { return new RenderThemeStub(); } @Override protected ScrollBarTheme createScrollBarTheme() { return new ScrollBarThemeStub(); } }; class RenderThemeStub extends RenderTheme { @Override protected Ref createWidget(long id, int widgetIndex, int state, int w, int h, int bgColor, ByteBuffer extParams) { return null; } @Override public void drawWidget(WCGraphicsContext g, Ref widget, int x, int y) { } @Override protected int getRadioButtonSize() { return 0; } @Override protected int getSelectionColor(int index) { return 0; } @Override public WCSize getWidgetSize(Ref widget) { return new WCSize(0, 0); } } class ScrollBarThemeStub extends ScrollBarTheme { @Override protected Ref createWidget(long id, int w, int h, int orientation, int value, int visibleSize, int totalSize) { return null; } @Override protected void getScrollBarPartRect(long id, int part, int rect[]) {} @Override public void paint(WCGraphicsContext g, Ref sbRef, int x, int y, int pressedPart, int hoveredPart) { } @Override public WCSize getWidgetSize(Ref widget) { return new WCSize(0, 0); } } // called on FX thread private DumpRenderTree() { uiClient = new UIClientImpl(); webPage = new WebPage(new WebPageClientImpl(), uiClient, null, null, new ThemeClientImplStub(), false); uiClient.setWebPage(webPage); eventSender = new EventSender(webPage); webPage.setBounds(0, 0, 800, 600); webPage.setUsePageCache(true); webPage.setDeveloperExtrasEnabled(true); webPage.addLoadListenerClient(new DRTLoadListener()); } private String getTestPath(String testString) { int t = testString.indexOf("'"); String pixelsHash = ""; if ((t > 0) && (t < testString.length() - 1)) { pixelsHash = testString.substring(t + 1); testString = testString.substring(0, t); } this.testPath = testString; init(testString, pixelsHash); return testString; } /* private static boolean isDebug() { return log.isLoggable(Level.FINE); } */ private static void mlog(String msg) { if (log.isLoggable(Level.FINE)) { log.fine("PID:" + Long.toHexString(PID) + " TID:" + Thread.currentThread().getId() + "(" + Thread.currentThread().getName() + ") " + msg); } } private static void initPlatform() throws Exception { // initialize default toolkit final CountDownLatch latch = new CountDownLatch(1); PlatformImpl.startup(() -> { new WebEngine(); // initialize Webkit classes System.loadLibrary("DumpRenderTreeJava"); PageCache.setCapacity(1); drt = new DumpRenderTree(); latch.countDown(); }); // wait for libraries to load latch.await(); } private void reset() { mlog("reset"); // Reset native objects associated with WebPage webPage.resetToConsistentStateBeforeTesting(); // Reset zoom factors webPage.setZoomFactor(1.0f, true); webPage.setZoomFactor(1.0f, false); // Reset DRT internal states complete = false; loaded = false; waiting = false; } // called on FX thread private void run(final String testString, final CountDownLatch latch) { this.latch = latch; String file = getTestPath(testString); mlog("{runTest: " + file); long mainFrame = webPage.getMainFrame(); try { new URL(file); } catch (MalformedURLException ex) { file = "file:///" + file; } reset(); webPage.open(mainFrame, file); mlog("}runTest"); } private void runTest(final String testString) throws Exception { final CountDownLatch l = new CountDownLatch(1); Invoker.getInvoker().invokeOnEventThread(() -> { run(testString, l); }); // wait until test is finished l.await(); Invoker.getInvoker().invokeOnEventThread(() -> { mlog("dispose"); // drt.uiClient.closePage(); dispose(); }); } // called from native private static void waitUntilDone() { mlog("waitUntilDone"); drt.setWaiting(true); // TODO: handle timeout } // called from native private static void notifyDone() { mlog("notifyDone"); drt.setWaiting(false); } private static void overridePreference(String key, String value) { mlog("overridePreference"); drt.webPage.overridePreference(key, value); } private synchronized void setLoaded(boolean loaded) { this.loaded = loaded; done(); } private synchronized void setWaiting(boolean waiting) { this.waiting = waiting; done(); } private synchronized void dump(long frame) { boolean dumpAsText = dumpAsText() || forceDumpAsText; mlog("dumpAsText = " + dumpAsText); if (dumpAsText) { String innerText = webPage.getInnerText(frame); if (frame == webPage.getMainFrame()) { if (innerText != null) { // don't use println() here as it varies from platform // to platform, but DRT expects it always to be 0x0A out.print(innerText + '\n'); } } else { out.printf("\n--------\nFrame: '%s'\n--------\n%s\n", webPage.getName(frame), innerText); } if (dumpChildFramesAsText()) { List children = webPage.getChildFrames(frame); if (children != null) { for (long child : children) { dump(child); } } } if (dumpBackForwardList() && frame == webPage.getMainFrame()) { drt.dumpBfl(); } } else { String renderTree = webPage.getRenderTree(frame); out.print(renderTree); } } private synchronized void done() { if (waiting || !loaded || complete) { return; } mlog("dump"); dump(webPage.getMainFrame()); mlog("done"); out.print("#EOF" + '\n'); // TODO: dump pixels here out.print("#EOF" + '\n'); out.flush(); System.err.print("#EOF" + '\n'); System.err.flush(); complete = true; // notify main thread that test is finished this.latch.countDown(); } private static native void init(String testPath, String pixelsHash); private static native void didClearWindowObject(long pContext, long pWindowObject, EventSender eventSender); private static native void dispose(); private static native boolean dumpAsText(); private static native boolean dumpChildFramesAsText(); private static native boolean dumpBackForwardList(); protected static native boolean shouldStayOnPageAfterHandlingBeforeUnload(); private final class DRTLoadListener implements LoadListenerClient { @Override public void dispatchLoadEvent(long frame, int state, String url, String contentType, double progress, int errorCode) { mlog("dispatchLoadEvent: ENTER"); if (frame == webPage.getMainFrame()) { mlog("dispatchLoadEvent: STATE = " + state); switch (state) { case PAGE_STARTED: mlog("PAGE_STARTED"); setLoaded(false); break; case PAGE_FINISHED: mlog("PAGE_FINISHED"); if (didFinishLoad()) { setLoaded(true); } break; case DOCUMENT_AVAILABLE: dumpUnloadListeners(webPage, frame); break; case LOAD_FAILED: mlog("LOAD_FAILED"); // safety net: if load fails, e.g. command line // parameters were bad, let's not hang forever setLoaded(true); break; } } mlog("dispatchLoadEvent: EXIT"); } @Override public void dispatchResourceLoadEvent(long frame, int state, String url, String contentType, double progress, int errorCode) { } } public static void main(final String[] args) throws Exception { /* if ( isDebug() ) { // 'log' here is from java.util.logging log.setLevel(Level.FINEST); FileHandler handler = new FileHandler("drt.log", true); handler.setFormatter(new Formatter() { @Override public String format(LogRecord record) { return formatMessage(record) + "\n"; } }); log.addHandler(handler); } */ mlog("{main"); initPlatform(); assert drt != null; for (String arg: args) { if ("--dump-as-text".equals(arg)) { forceDumpAsText = true; } else if ("-".equals(arg)) { // read from stdin BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); String testPath; while ((testPath = in.readLine()) != null) { drt.runTest(testPath); } in.close(); } else { drt.runTest(arg); } } PlatformImpl.exit(); mlog("}main"); System.exit(0); // workaround to kill media threads } // called from native private static int getWorkerThreadCount() { return WebPage.getWorkerThreadCount(); } // called from native private static String resolveURL(String relativeURL) { String testDir = new File(drt.testPath).getParentFile().getPath(); File f = new File(testDir, relativeURL); String url = "file:///" + f.toString().replace(fileSep, "/"); mlog("resolveURL: " + url); return url; } // called from native private static void loadURL(String url) { drt.webPage.open(drt.webPage.getMainFrame(), url); } // called from native private static void goBackForward(int dist) { // TODO: honor the dist if (dist > 0) { drt.webPage.goForward(); } else { drt.webPage.goBack(); } } // called from native private static int getBackForwardItemCount() { return drt.getBackForwardList().size(); } private static final String TEST_DIR_NAME = "LayoutTests"; private static final int TEST_DIR_LEN = TEST_DIR_NAME.length(); private static final String CUR_ITEM_STR = "curr->"; private static final int CUR_ITEM_STR_LEN = CUR_ITEM_STR.length(); private static final String INDENT = " "; private BackForwardList bfl; private BackForwardList getBackForwardList() { if (bfl == null) { bfl = webPage.createBackForwardList(); } return bfl; } private void dumpBfl() { out.print("\n============== Back Forward List ==============\n"); getBackForwardList(); BackForwardList.Entry curItem = bfl.getCurrentEntry(); for (BackForwardList.Entry e: bfl.toArray()) { dumpBflItem(e, 2, e == curItem); } out.print("===============================================\n"); } private void dumpBflItem(BackForwardList.Entry item, int indent, boolean isCurrent) { StringBuilder str = new StringBuilder(); for (int i = indent; i > 0; i--) str.append(INDENT); if (isCurrent) str.replace(0, CUR_ITEM_STR_LEN, CUR_ITEM_STR); String url = item.getURL().toString(); if (url.contains("file:/")) { String subUrl = url.substring(url.indexOf(TEST_DIR_NAME) + TEST_DIR_LEN + 1); str.append("(file test):" + subUrl); } else { str.append(url); } if (item.getTarget() != null) { str.append(" (in frame \"" + item.getTarget() + "\")"); } if (item.isTargetItem()) { str.append(" **nav target**\n"); } else { str.append("\n"); } out.print(str); if (item.getChildren() != null) for (BackForwardList.Entry child: item.getChildren()) dumpBflItem(child, indent + 1, false); } void dumpUnloadListeners(WebPage page, long frame) { if (waiting == true && dumpAsText()) { String dump = getUnloadListenersDescription(page, frame); if (dump != null) { out.print(dump + '\n'); } } } private static String getUnloadListenersDescription(WebPage page, long frame) { int count = page.getUnloadEventListenersCount(frame); if (count > 0) { return getFrameDescription(page, frame) + " - has " + count + " onunload handler(s)"; } return null; } private static String getFrameDescription(WebPage page, long frame) { String name = page.getName(frame); if (frame == page.getMainFrame()) { return name == null ? "main frame" : "main frame " + name; } return name == null ? "frame (anonymous)" : "frame " + name; } private native static boolean didFinishLoad(); private final class WebPageClientImpl implements WebPageClient { @Override public void setCursor(long cursorID) { } @Override public void setFocus(boolean focus) { } @Override public void transferFocus(boolean forward) { } @Override public void setTooltip(String tooltip) { } @Override public WCRectangle getScreenBounds(boolean available) { return null; } @Override public int getScreenDepth() { return 24; } @Override public Void getContainer() { return null; } @Override public WCPoint screenToWindow(WCPoint ptScreen) { return ptScreen; } @Override public WCPoint windowToScreen(WCPoint ptWindow) { return ptWindow; } @Override public WCPageBackBuffer createBackBuffer() { throw new UnsupportedOperationException(); } @Override public boolean isBackBufferSupported() { return false; } @Override public void addMessageToConsole(String message, int lineNumber, String sourceId) { if (!message.isEmpty()) { int pos = message.indexOf("file://"); if (pos != -1) { String s1 = message.substring(0, pos); String s2 = message.substring(pos); try { // Extract the last path component aka file name s2 = new File(newURL(s2).getPath()).getName(); } catch (MalformedURLException ignore) {} message = s1 + s2; } } if (lineNumber == 0) { out.printf("CONSOLE MESSAGE: %s\n", message); } else { out.printf("CONSOLE MESSAGE: line %d: %s\n", lineNumber, message); } } @Override public void didClearWindowObject(long context, long windowObject) { mlog("didClearWindowObject"); DumpRenderTree.didClearWindowObject(context, windowObject, eventSender); } } }