1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package test.javafx.scene.web;
  27 
  28 import java.io.File;
  29 import java.util.concurrent.Callable;
  30 import java.util.concurrent.CountDownLatch;
  31 import java.util.concurrent.FutureTask;
  32 import java.util.concurrent.atomic.AtomicBoolean;
  33 
  34 import javafx.application.Platform;
  35 import javafx.beans.InvalidationListener;
  36 import javafx.beans.Observable;
  37 import javafx.beans.value.ChangeListener;
  38 import javafx.beans.value.ObservableValue;
  39 import javafx.concurrent.Worker;
  40 
  41 import com.sun.javafx.application.PlatformImpl;
  42 import java.util.concurrent.ExecutionException;
  43 import javafx.scene.web.WebEngine;
  44 import javafx.scene.web.WebView;
  45 import org.junit.BeforeClass;
  46 import org.w3c.dom.Document;
  47 
  48 public class TestBase implements ChangeListener, InvalidationListener {
  49     private static final AtomicBoolean LOCK = new AtomicBoolean(false);
  50     private static final int INIT_TIMEOUT = 10000;
  51     private static final int LOAD_TIMEOUT = 60000;
  52 
  53     private static WebView view;
  54 
  55     @BeforeClass
  56     public static void setupOnce() {
  57         final CountDownLatch startupLatch = new CountDownLatch(1);
  58 
  59         PlatformImpl.startup(() -> {
  60             startupLatch.countDown();
  61         });
  62 
  63         try {
  64             startupLatch.await();
  65         } catch (InterruptedException ex) {}
  66     }
  67 
  68     public TestBase() {
  69         Platform.runLater(() -> {
  70             view = new WebView();
  71             WebEngine web = view.getEngine();
  72 
  73             web.documentProperty().addListener((ChangeListener)TestBase.this);
  74             web.documentProperty().addListener((InvalidationListener)TestBase.this);
  75             web.titleProperty().addListener((ChangeListener)TestBase.this);
  76             web.titleProperty().addListener((InvalidationListener)TestBase.this);
  77             web.locationProperty().addListener((ChangeListener)TestBase.this);
  78             web.locationProperty().addListener((InvalidationListener)TestBase.this);
  79 
  80             Worker loadTask = web.getLoadWorker();
  81             loadTask.exceptionProperty().addListener((ChangeListener)TestBase.this);
  82             loadTask.exceptionProperty().addListener((InvalidationListener)TestBase.this);
  83             loadTask.messageProperty().addListener((ChangeListener)TestBase.this);
  84             loadTask.messageProperty().addListener((InvalidationListener)TestBase.this);
  85             loadTask.progressProperty().addListener((ChangeListener)TestBase.this);
  86             loadTask.progressProperty().addListener((InvalidationListener)TestBase.this);
  87             loadTask.runningProperty().addListener((ChangeListener)TestBase.this);
  88             loadTask.runningProperty().addListener((InvalidationListener)TestBase.this);
  89             loadTask.stateProperty().addListener((ChangeListener)TestBase.this);
  90             loadTask.stateProperty().addListener((InvalidationListener)TestBase.this);
  91             loadTask.titleProperty().addListener((ChangeListener)TestBase.this);
  92             loadTask.titleProperty().addListener((InvalidationListener)TestBase.this);
  93             loadTask.totalWorkProperty().addListener((ChangeListener)TestBase.this);
  94             loadTask.totalWorkProperty().addListener((InvalidationListener)TestBase.this);
  95             loadTask.valueProperty().addListener((ChangeListener)TestBase.this);
  96             loadTask.valueProperty().addListener((InvalidationListener)TestBase.this);
  97             loadTask.workDoneProperty().addListener((ChangeListener)TestBase.this);
  98             loadTask.workDoneProperty().addListener((InvalidationListener)TestBase.this);
  99 
 100             loadTask.runningProperty().addListener(new LoadFinishedListener());
 101 
 102             TestBase.this.notify(LOCK);
 103         });
 104 
 105         wait(LOCK, INIT_TIMEOUT);
 106     }
 107 
 108     /**
 109      * Loads content from a URL.
 110      * This method blocks until loading is finished.
 111      */
 112     protected void load(final String url) {
 113         Platform.runLater(() -> {
 114             getEngine().load(url);
 115         });
 116         waitLoadFinished();
 117     }
 118 
 119     /**
 120      * Reloads current page.
 121      * This method blocks until loading is finished.
 122      */
 123     protected void reload() {
 124         Platform.runLater(() -> {
 125             getEngine().reload();
 126         });
 127         waitLoadFinished();
 128     }
 129 
 130     /**
 131      * Loads content from a file.
 132      * This method blocks until loading is finished.
 133      */
 134     protected void load(File file) {
 135         load(file.toURI().toASCIIString());
 136     }
 137 
 138     /**
 139      * Loads content from a file, and returns the resulting document.
 140      * This method blocks until loading is finished.
 141      */
 142     protected Document getDocumentFor(String fileName) {
 143         load(new File(fileName));
 144         return getEngine().getDocument();
 145     }
 146 
 147     /**
 148      * Loads content of the specified type from a String.
 149      * This method does not return until loading is finished.
 150      */
 151     protected void loadContent(final String content, final String contentType) {
 152         Platform.runLater(() -> {
 153             getEngine().loadContent(content, contentType);
 154         });
 155         waitLoadFinished();
 156     }
 157 
 158     /**
 159      * Loads HTML content from a String.
 160      * This method does not return until loading is finished.
 161      */
 162     protected void loadContent(final String content) {
 163         loadContent(content, "text/html");
 164     }
 165 
 166     /**
 167      * Executes a job on FX thread, and waits until it is complete.
 168      */
 169     protected void submit(Runnable job) {
 170         final FutureTask<Void> future = new FutureTask<Void>(job, null);
 171         Platform.runLater(future);
 172         try {
 173             // block until job is complete
 174             future.get();
 175         } catch (ExecutionException e) {
 176             Throwable cause = e.getCause();
 177             // rethrow any assertion errors as is
 178             if (cause instanceof AssertionError) {
 179                 throw (AssertionError) e.getCause();
 180             } else if (cause instanceof RuntimeException) {
 181                 throw (RuntimeException) cause;
 182             }
 183             // any other exception should be considered a test error
 184             throw new AssertionError(cause);
 185         } catch (InterruptedException e) {
 186             throw new AssertionError(e);
 187         }
 188     }
 189 
 190     /**
 191      * Executes a job on FX thread, waits until completion, and returns its result.
 192      */
 193     protected <T> T submit(Callable<T> job) {
 194         final FutureTask<T> future = new FutureTask<T>(job);
 195         Platform.runLater(future);
 196         try {
 197             return future.get();
 198         } catch (ExecutionException e) {
 199             Throwable cause = e.getCause();
 200             // rethrow any assertion errors as is
 201             if (cause instanceof AssertionError) {
 202                 throw (AssertionError) e.getCause();
 203             }
 204             // any other exception should be considered a test error
 205             throw new AssertionError(cause);
 206         } catch (InterruptedException e) {
 207             throw new AssertionError(e);
 208         }
 209     }
 210 
 211     /**
 212      * Executes a script.
 213      * This method does not return until execution is complete.
 214      */
 215     protected Object executeScript(final String script) {
 216         return submit(() -> getEngine().executeScript(script));
 217     }
 218 
 219     private class LoadFinishedListener implements ChangeListener<Boolean> {
 220         @Override
 221         public void changed(ObservableValue<? extends Boolean> observable,
 222                 Boolean oldValue, Boolean newValue) {
 223             if (! newValue) {
 224                 TestBase.this.notify(LOCK);
 225             }
 226         }
 227     }
 228 
 229     private void wait(AtomicBoolean condition, long timeout) {
 230         synchronized (condition) {
 231             long startTime = System.currentTimeMillis();
 232             while (!condition.get()) {
 233                 try {
 234                     condition.wait(timeout);
 235                 } catch (InterruptedException e) {
 236                 } finally {
 237                     if (System.currentTimeMillis() - startTime >= timeout) {
 238                         throw new AssertionError("Waiting timed out");
 239                     }
 240                 }
 241             }
 242             condition.set(false);
 243         }
 244     }
 245 
 246     private void notify(AtomicBoolean condition) {
 247         synchronized (condition) {
 248             condition.set(true);
 249             condition.notifyAll();
 250         }
 251     }
 252 
 253     /**
 254      * Override this to get loading notifications from both WebEngine
 255      * and its loadWorker.
 256      */
 257     @Override public void invalidated(Observable value) {
 258     }
 259 
 260     /**
 261      * Override this to get loading notifications from both WebEngine
 262      * and its loadWorker.
 263      */
 264     @Override public void changed(ObservableValue value, Object oldValue, Object newValue) {
 265     }
 266 
 267     /**
 268      * Returns the WebEngine object under test.
 269      */
 270     protected WebEngine getEngine() {
 271         return view.getEngine();
 272     }
 273 
 274     /**
 275      * Returns the WebView object under test.
 276      */
 277     protected WebView getView() {
 278         return view;
 279     }
 280 
 281     /**
 282      * Allows to override default load timeout value (in milliseconds).
 283      */
 284     protected int getLoadTimeOut() {
 285         return LOAD_TIMEOUT;
 286     }
 287 
 288     public void waitLoadFinished() {
 289         wait(LOCK, getLoadTimeOut());
 290     }
 291 
 292     /**
 293      * Check for Jigsaw Mode
 294      */
 295     public boolean isJigsawMode() {
 296         Class clazz = null;
 297         try {
 298             clazz = Class.forName("java.lang.reflect.ModuleDescriptor", false, TestBase.class.getClassLoader());
 299         } catch (Exception e) { }
 300 
 301         return clazz != null;
 302     }
 303 }