com.microsoft.tfs.client.common.ui.controls.generic.CompatibleBrowser.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.controls.generic.CompatibleBrowser.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.ui.controls.generic;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Panel;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;

import javax.swing.JEditorPane;
import javax.swing.JScrollPane;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;

import com.microsoft.tfs.client.common.ui.Messages;
import com.microsoft.tfs.client.common.ui.controls.generic.compatibility.link.CompatibilityLinkControl;
import com.microsoft.tfs.client.common.ui.controls.generic.compatibility.link.CompatibilityLinkFactory;
import com.microsoft.tfs.client.common.ui.framework.WindowSystem;
import com.microsoft.tfs.jni.PlatformMiscUtils;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.Platform;

/**
 * <p>
 * A composite that acts like an SWT {@link Browser}, but only supports a few
 * methods. If SWT's {@link Browser} is not supported on the running platform, a
 * {@link JEditorPane} is used instead.
 * </p>
 * <p>
 * The major differences between {@link CompatibleBrowser} and
 * {@link FullFeaturedBrowser}:
 * </p>
 * <ul>
 * <li>{@link CompatibleBrowser} will fall back to the extremely limited
 * {@link JEditorPane} if {@link Browser} can't load;
 * {@link FullFeaturedBrowser} will just error if {@link Browser} can't load
 * </li>
 * <li>{@link CompatibleBrowser} offers very few features;
 * {@link FullFeaturedBrowser} offers lots of features</li>
 * <li>{@link CompatibleBrowser} might use AWT/Swing (which works better on some
 * platforms than others); {@link FullFeaturedBrowser} never uses AWT/Swing</li>
 * </ul>
 */
public class CompatibleBrowser extends Composite {
    private final static Log log = LogFactory.getLog(CompatibleBrowser.class);

    private final static String AWT_TOOLKIT_ENV_VAR_NAME = "AWT_TOOLKIT"; //$NON-NLS-1$
    private final static String XTOOLKIT_ENV_VAR_VALUE = "XToolkit"; //$NON-NLS-1$

    private final static String AWT_TOOLKIT_SYS_PROP_NAME = "awt.toolkit"; //$NON-NLS-1$
    private final static String XTOOLKIT_SYS_PROP_VALUE = "sun.awt.X11.XToolkit"; //$NON-NLS-1$

    /*
     * Only one of these will be initialized to non-null values depending on
     * whether SWT's Browser control works.
     */
    private final JEditorPane editorPane;
    private final Browser browser;
    private static final Object nativeTestLock = new Object();
    private static boolean testedForNativeBrowser = false;
    private static boolean nativeBrowserAvailable = false;

    public CompatibleBrowser(final Composite parent, final int style) {
        super(parent, style);

        Check.notNull(parent, "parent"); //$NON-NLS-1$

        final GridLayout gridLayout = new GridLayout(1, true);
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;

        setLayout(gridLayout);

        if (isNativeBrowserAvailable()) {
            log.info(MessageFormat.format("{0} using SWT Browser", CompatibleBrowser.class.getName())); //$NON-NLS-1$

            final GridData gd = new GridData();
            gd.grabExcessHorizontalSpace = true;
            gd.horizontalAlignment = SWT.FILL;
            gd.grabExcessVerticalSpace = true;
            gd.verticalAlignment = SWT.FILL;

            browser = new Browser(this, style);
            browser.setLayoutData(gd);

            editorPane = null;
        } else {
            log.info(MessageFormat.format("{0} using JEditorPane", CompatibleBrowser.class.getName())); //$NON-NLS-1$

            browser = null;

            JEditorPane tempEditorPane = null;
            CompatibilityLinkControl tempErrorLabel = null;

            /*
             * Embedded AWT widgets must be in a Composite with SWT.EMBEDDED
             * set, so create one.
             */
            final Composite embeddableComposite = new Composite(this, SWT.EMBEDDED);

            final GridData compositeGridData = new GridData();
            compositeGridData.grabExcessHorizontalSpace = true;
            compositeGridData.horizontalAlignment = SWT.FILL;
            compositeGridData.grabExcessVerticalSpace = true;
            compositeGridData.verticalAlignment = SWT.FILL;

            embeddableComposite.setLayoutData(compositeGridData);

            /*
             * We have to skip trying AWT entirely some places.
             */
            boolean loadedAWTBrowser = false;
            if (CompatibleBrowser.isAWTDangerousHere() == false) {
                try {
                    /*
                     * Create a Frame in the SWT Composite as the top-level
                     * element.
                     */
                    final Frame browserFrame = SWT_AWT.new_Frame(embeddableComposite);

                    /*
                     * Create a panel with a simple BorderLayout to hold
                     * contents.
                     */
                    final Panel panel = new Panel(new BorderLayout());
                    browserFrame.add(panel);

                    /*
                     * Create an JEditorPane with an HTML document.
                     */
                    final String pageContents = "<html><body></body></html>"; //$NON-NLS-1$
                    tempEditorPane = new JEditorPane("text/html", pageContents); //$NON-NLS-1$
                    tempEditorPane.setEditable(false);

                    /*
                     * Put the HTML viewer in a scroll pane and parent the
                     * scroll pane in the panel.
                     */
                    final JScrollPane scrollPane = new JScrollPane(tempEditorPane);
                    panel.add(scrollPane);

                    loadedAWTBrowser = true;
                } catch (final Throwable t) {
                    log.warn("Error embedding AWT frame for JEditorPane", t); //$NON-NLS-1$
                }
            }

            if (loadedAWTBrowser == false) {
                /*
                 * We don't need the embeddable composite because AWT embedding
                 * failed, and we can't put normal (error label) things in it,
                 * so hide it.
                 */
                compositeGridData.widthHint = 0;
                compositeGridData.heightHint = 0;

                tempErrorLabel = CompatibilityLinkFactory.createLink(this, SWT.NONE);

                tempErrorLabel.setText(
                        MessageFormat.format(Messages.getString("CompatibleBrowser.CouldNotLoadSWTBrowserFormat"), //$NON-NLS-1$
                                AWT_TOOLKIT_ENV_VAR_NAME, XTOOLKIT_ENV_VAR_VALUE));

                final GridData labelGridData = new GridData();
                labelGridData.grabExcessHorizontalSpace = true;
                labelGridData.horizontalAlignment = SWT.FILL;
                labelGridData.grabExcessVerticalSpace = true;
                labelGridData.verticalAlignment = SWT.FILL;

                tempErrorLabel.getControl().setLayoutData(labelGridData);
            }

            editorPane = tempEditorPane;
        }
    }

    /**
     * Call this method to test whether calling
     * {@link SWT_AWT#new_Frame(Composite)} would crash the JVM on this system.
     *
     * Background:
     *
     * Calling {@link SWT_AWT#new_Frame(Composite)} on some Unix platforms when
     * the AWT toolkit is MToolkit can cause a JVM crash (AIX 5.2 with Eclipse
     * 3.3-3.5 and IBM Java 1.5.0). I dug into this and simply loading
     * java.awt.Toolkit that causes the crash, so we can't even interrogate the
     * default Toolkit instance to see what kind it is! Instead we check
     * environment variables and system properties.
     *
     * See http://java.sun.com/j2se/1.5.0/docs/guide/awt/1.5/xawt.html.
     *
     * @return true if {@link SWT_AWT#new_Frame(Composite)} would crash the JVM
     *         (as it does on some Unixes when MToolkit is the active AWT
     *         toolkit)
     */
    private static boolean isAWTDangerousHere() {
        /*
         * Unfortunately we can't test any system property or environment
         * variable or static class information directly to see if MToolkit will
         * be the one chosen if the Toolkit class is used, because it's just
         * hard coded behavior in that class. There is an environment variable
         * and system property the user can set to enable XToolkit, so we test
         * for the existence of those as proof it's safe to continue.
         *
         * This isn't optimal, because there may be other non-dangerous toolkits
         * (headless mode?) that we should have gone ahead with. Probably not an
         * issue for a web browser control.
         */
        if (Platform.isCurrentPlatform(Platform.AIX)) {
            final String envVal = PlatformMiscUtils.getInstance().getEnvironmentVariable(AWT_TOOLKIT_ENV_VAR_NAME);
            final String sysPropVal = System.getProperty(AWT_TOOLKIT_SYS_PROP_NAME);

            boolean xToolkitActive = false;

            /*
             * Environment variable overrides system property according to Sun's
             * docs.
             */
            if (envVal != null) {
                xToolkitActive = envVal.equals(XTOOLKIT_ENV_VAR_VALUE);
            } else if (sysPropVal != null) {
                xToolkitActive = sysPropVal.equals(XTOOLKIT_SYS_PROP_VALUE);
            }

            return xToolkitActive == false;
        }

        /*
         * Safe to try and use AWT.
         */
        return false;
    }

    /**
     * @see Browser#setText(String)
     * @see JEditorPane#setText(String)
     */
    public void setText(final String html) {
        if (browser != null) {
            browser.setText(html);
        } else if (editorPane != null) {
            editorPane.setText(html);
        } else {
            // Do nothing for the error label case.
        }
    }

    /**
     * @throws IOException
     * @see Browser#setUrl(String)
     * @see JEditorPane#setURL(String)
     */
    public void setURL(final String url) throws IOException {
        if (browser != null && !browser.isDisposed()) {
            browser.setUrl(url);
        } else if (editorPane != null) {
            editorPane.setPage(url);
        }
    }

    /**
     * @see Browser#setJavascriptEnabled(boolean)
     */
    public void setJavascriptEnabled(final boolean enabled) {
        if (browser != null && !browser.isDisposed()) {
            try {
                // Method setJavascriptEnabled is Eclipse 3.5.
                final Method m = browser.getClass().getMethod("setJavascriptEnabled", new Class[] //$NON-NLS-1$
                { boolean.class });
                m.invoke(browser, new Object[] { Boolean.valueOf(enabled) });
            } catch (final Exception e) {
                // fallback is to do nothing.
                log.warn("Unable to modify javascript enablement", e); //$NON-NLS-1$
            }
        }
    }

    /**
     * @see Browser#stop(String)
     */
    public void stop() {
        if (browser != null) {
            browser.stop();
        }
    }

    /**
     * @see Browser#addLocationListener(LocationListener)
     */
    public void addLocationListener(final LocationListener listener) {
        if (browser != null) {
            browser.addLocationListener(listener);
        }
    }

    /**
     * @see Browser#removeLocationListener(LocationListener)
     */
    public void removeLocationListener(final LocationListener listener) {
        if (browser != null) {
            browser.removeLocationListener(listener);
        }
    }

    /**
     * Checks if the SWT Browser control is available.
     *
     * @return true if the WorkItemHistoryControl is available on the current
     *         platform. Due to its dependency on the SWT Browser control, the
     *         WorkItemHistoryControl is not always available.
     */
    public static boolean isNativeBrowserAvailable() {
        synchronized (nativeTestLock) {
            if (testedForNativeBrowser == false) {
                final Shell shell = new Shell();

                try {
                    /*
                     * OS X's SWT native browser for Carbon is full of bugs
                     * (https://bugs.eclipse.org/bugs/show_bug.cgi?id=230035) so
                     * we fall back to the AWT browser which works better
                     * (though with very few features) whenever possible.
                     *
                     * However, if we started in AWT headless mode (which we
                     * shouldn't, but some dumb IDE -- FlexBuilder? -- does)
                     * then we should fall back to the SWT browser because the
                     * AWT browser obviously can't be initialized in headless.
                     * See bug 2465.
                     */
                    if (WindowSystem.isCurrentWindowSystem(WindowSystem.CARBON)) {
                        try {
                            final String headless = System.getProperty("java.awt.headless"); //$NON-NLS-1$

                            if ("true".equalsIgnoreCase(headless)) //$NON-NLS-1$
                            {
                                /* Headless, AWT browser is not available */
                                log.info(
                                        "Not using AWT browser (java.awt.headless=true), falling back to SWT browser"); //$NON-NLS-1$
                                nativeBrowserAvailable = true;
                            } else {
                                /*
                                 * Try to create an AWT frame to ensure that we
                                 * can actually do so. Eclipse 3.3 on Mac OS has
                                 * some problems doing this for unknown reasons.
                                 */
                                final Composite dummyComposite = new Composite(shell, SWT.EMBEDDED);
                                final Frame dummyAwtFrame = SWT_AWT.new_Frame(dummyComposite);

                                if (dummyAwtFrame == null) {
                                    /* AWT browser not available, use native */
                                    log.info("Could not instantiate AWT browser, falling back to SWT browser"); //$NON-NLS-1$
                                    nativeBrowserAvailable = true;
                                } else {
                                    /* AWT browser is available, prefer it */
                                    log.info(
                                            "AWT browser is available, preferring it.  Set java.awt.headless=true to use SWT browser."); //$NON-NLS-1$
                                    nativeBrowserAvailable = false;
                                }
                            }
                        } catch (final Throwable t) {
                            log.info("Could not instantiate AWT browser, falling back to SWT browser", t); //$NON-NLS-1$
                            nativeBrowserAvailable = true;
                        }
                    } else {
                        try {
                            final Browser browser = new Browser(shell, SWT.NONE);
                            browser.dispose();

                            log.info("SWT Browser found during isNativeBrowserAvailable() check"); //$NON-NLS-1$
                            nativeBrowserAvailable = true;
                        } catch (final Throwable t) {
                            log.info("SWT Browser not usable during isNativeBrowserAvailable() check", t); //$NON-NLS-1$
                            nativeBrowserAvailable = false;
                        }
                    }
                } finally {
                    shell.dispose();
                    testedForNativeBrowser = true;
                }
            }

            return nativeBrowserAvailable;
        }
    }
}