Bug 514294 - Eclipse view developed with HTML/JS and Browser widget

Created a template for a browser view

Two types of views can be generated
1. Code for interacting browser/js with the workbench
2. Code for a JS game

The template generates separate js files that can be edited
with the js editors.

Translation keys added.

Uses js game code with permission [1]

[1] https://github.com/end3r/Gamedev-Canvas-workshop/issues/15

Change-Id: If43d804bb45bf08e74d587fd73c0380ef6070398
diff --git a/ui/org.eclipse.pde.ui.templates/plugin.properties b/ui/org.eclipse.pde.ui.templates/plugin.properties
index 43d07b0..8e4b9cc 100644
--- a/ui/org.eclipse.pde.ui.templates/plugin.properties
+++ b/ui/org.eclipse.pde.ui.templates/plugin.properties
@@ -64,6 +64,12 @@
 <li>org.eclipse.ui.genericeditor.reconcilers</li>\
 <li>org.eclipse.core.filebuffers.documentSetup</li>
 
+pluginContent.browserview.name = View using browser technology
+pluginContent.browserview.description=\
+<p>This wizard creates standard plug-in directory structure and \
+adds the following:</p>\
+<li><b>Browser view</b>. %template.browserview.desc%</li>
+
 pluginContent.view.name = View contribution using 3.x API
 pluginContent.view.description=\
 <p>This wizard creates standard plug-in directory structure and \
@@ -178,6 +184,8 @@
 newExtension.templates.helloCmd.name = "Hello, World" command contribution
 newExtension.templates.helloCmd.desc = <p>%template.helloWorldCmd.desc%</p>
 
+newExtension.templates.browserview.name = Browser View
+newExtension.templates.browserview.desc = <p>%template.browserview.desc%</p>
 
 newExtension.templates.view.name = Sample View
 newExtension.templates.view.desc = <p>%template.view.desc%</p>
@@ -265,6 +273,14 @@
 filtering. There is also an option to add context-sensitive help \
 to the view.
 
+template.browserview.name = Example browser view extension
+template.browserview.desc = This template creates a workbench view using browser technology. \
+The view is contributed to the workbench by \
+creating a category. The view can be opened by selecting \
+<b>Window</b>, <b>Show View</b> and then <b>Other...</b> \
+on the menu bar. The template demonstrates how data can be exchanged \
+between Java and JavaScript in the browser.
+
 template.multiPageEditor.name = Multi-page Editor
 template.multiPageEditor.desc = This template creates a \
 multi-page editor. It works on text files with the chosen extension. \
diff --git a/ui/org.eclipse.pde.ui.templates/plugin.xml b/ui/org.eclipse.pde.ui.templates/plugin.xml
index a223e08..5a52dde 100644
--- a/ui/org.eclipse.pde.ui.templates/plugin.xml
+++ b/ui/org.eclipse.pde.ui.templates/plugin.xml
@@ -45,6 +45,16 @@
       <wizard
             category="templates"
             icon="$nl$/icons/etool16/newex_wiz.png"
+            id="org.eclipse.pde.ui.newExtension.browserview"
+            name="%newExtension.templates.browserview.name"
+            template="org.eclipse.pde.ui.templates.browserview">
+         <description>
+            %newExtension.templates.browserview.desc
+         </description>
+      </wizard>
+      <wizard
+            category="templates"
+            icon="$nl$/icons/etool16/newex_wiz.png"
             id="org.eclipse.pde.ui.newExtension.commonNavigator"
             name="%newExtension.template.commonNavigator.name"
             template="org.eclipse.pde.ui.templates.commonNavigator">
@@ -234,6 +244,12 @@
             id="org.eclipse.pde.ui.templates.view">
       </template>
       <template
+            class="org.eclipse.pde.internal.ui.templates.ide.BrowserViewTemplate"
+            contributingId="org.eclipse.ui.views"
+            id="org.eclipse.pde.ui.templates.browserview"
+            name="%template.browserview.name">
+      </template>
+      <template
             contributingId="org.eclipse.ui.editors"
             name="%template.multiPageEditor.name"
             class="org.eclipse.pde.internal.ui.templates.ide.MultiPageEditorTemplate"
@@ -374,6 +390,15 @@
          </description>
       </wizard>
       <wizard
+            class="org.eclipse.pde.internal.ui.templates.ide.BrowserViewNewWizard"
+            icon="$nl$/icons/etool16/newexprj_wiz.png"
+            id="org.eclipse.pde.ui.pluginContent.browserview"
+            name="%pluginContent.browserview.name">
+         <description>
+            %pluginContent.browserview.description
+         </description>
+      </wizard>
+      <wizard
             name="%pluginContent.multiPageEditor.name"
             icon="$nl$/icons/etool16/newexprj_wiz.png"
             class="org.eclipse.pde.internal.ui.templates.ide.MultiPageEditorNewWizard"
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/PDETemplateMessages.java b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/PDETemplateMessages.java
index 12fc1c5..b5e5f9c 100644
--- a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/PDETemplateMessages.java
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/PDETemplateMessages.java
@@ -203,6 +203,21 @@
 	public static String HelpTemplate_reference;
 	public static String HelpTemplate_samples;
 
+	public static String BrowserView_newPlugin;
+
+
+	public static String BrowserViewTemplate_aGame;
+
+
+	public static String BrowserViewTemplate_browserView;
+
+
+	public static String BrowserViewTemplate_selectJsType;
+
+
+	public static String BrowserViewTemplate_wbIntegration;
+
+
 	public static String BuilderNewWizard_wtitle;
 	public static String BuilderTemplate_title;
 	public static String BuilderTemplate_desc;
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewNewWizard.java b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewNewWizard.java
new file mode 100644
index 0000000..82a51df
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewNewWizard.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ *  Copyright (c) 2000, 2021 IBM Corporation and others and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Wim Jongman - Create browser view plugin
+ *******************************************************************************/
+
+package org.eclipse.pde.internal.ui.templates.ide;
+
+import org.eclipse.pde.internal.ui.templates.PDETemplateMessages;
+import org.eclipse.pde.ui.IFieldData;
+import org.eclipse.pde.ui.templates.ITemplateSection;
+import org.eclipse.pde.ui.templates.NewPluginTemplateWizard;
+
+public class BrowserViewNewWizard extends NewPluginTemplateWizard {
+	/**
+	 * Constructor for ViewNewWizard.
+	 */
+	public BrowserViewNewWizard() {
+		super();
+	}
+
+	@Override
+	public void init(IFieldData data) {
+		super.init(data);
+		setWindowTitle(PDETemplateMessages.BrowserView_newPlugin);
+	}
+
+	@Override
+	public ITemplateSection[] createTemplateSections() {
+		return new ITemplateSection[] { new BrowserViewTemplate() };
+	}
+}
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewTemplate.java b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewTemplate.java
new file mode 100644
index 0000000..9e8e18b
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/ide/BrowserViewTemplate.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ *  Copyright (c) 2000, 2016 IBM Corporation and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 473694, 486261
+ *******************************************************************************/
+
+package org.eclipse.pde.internal.ui.templates.ide;
+
+import java.util.ArrayList;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.pde.core.plugin.*;
+import org.eclipse.pde.internal.core.ibundle.IBundle;
+import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
+import org.eclipse.pde.internal.ui.templates.*;
+import org.eclipse.pde.ui.IFieldData;
+import org.eclipse.pde.ui.templates.BooleanOption;
+import org.eclipse.pde.ui.templates.PluginReference;
+import org.osgi.framework.Constants;
+
+public class BrowserViewTemplate extends PDETemplateSection {
+	private BooleanOption addToPerspective;
+
+	/**
+	 * Constructor for HelloWorldTemplate.
+	 */
+	public BrowserViewTemplate() {
+		setPageCount(1);
+		createOptions();
+	}
+
+	@Override
+	public String getSectionId() {
+		return "browserView"; //$NON-NLS-1$
+	}
+
+	@Override
+	public int getNumberOfWorkUnits() {
+		return super.getNumberOfWorkUnits() + 1;
+	}
+
+	private void createOptions() {
+		// first page
+		addOption(KEY_PACKAGE_NAME, PDETemplateMessages.ViewTemplate_packageName, (String) null, 0);
+		addOption("className", PDETemplateMessages.ViewTemplate_className, "BrowserView", 0); //$NON-NLS-1$ //$NON-NLS-2$
+		addOption("viewName", PDETemplateMessages.ViewTemplate_name, PDETemplateMessages.BrowserViewTemplate_browserView, 0); //$NON-NLS-1$
+		addOption("viewCategoryId", PDETemplateMessages.ViewTemplate_categoryId, "sample", 0); //$NON-NLS-1$ //$NON-NLS-2$
+		addOption("viewCategoryName", PDETemplateMessages.ViewTemplate_categoryName, //$NON-NLS-1$
+				PDETemplateMessages.ViewTemplate_defaultCategoryName, 0);
+		addOption("game", PDETemplateMessages.BrowserViewTemplate_selectJsType, //$NON-NLS-1$
+				new String[][] { { "no", PDETemplateMessages.BrowserViewTemplate_wbIntegration }, //$NON-NLS-1$
+						{ "yes", PDETemplateMessages.BrowserViewTemplate_aGame } }, //$NON-NLS-1$
+				"no", 0); //$NON-NLS-1$
+		addOption("addViewID", PDETemplateMessages.ViewTemplate_addViewID, true, 0); //$NON-NLS-1$
+		addToPerspective = (BooleanOption) addOption("addToPerspective", //$NON-NLS-1$
+				PDETemplateMessages.ViewTemplate_addToPerspective, true, 0);
+	}
+
+	@Override
+	protected void initializeFields(IFieldData data) {
+		// In a new project wizard, we don't know this yet - the
+		// model has not been created
+		initializeFields(data.getId());
+
+	}
+
+	@Override
+	public void initializeFields(IPluginModelBase model) {
+		// In the new extension wizard, the model exists so
+		// we can initialize directly from it
+		initializeFields(model.getPluginBase().getId());
+	}
+
+	public void initializeFields(String id) {
+		initializeOption(KEY_PACKAGE_NAME, getFormattedPackageName(id));
+		initializeOption("viewCategoryId", id); //$NON-NLS-1$
+	}
+
+	@Override
+	public boolean isDependentOnParentWizard() {
+		return true;
+	}
+
+	@Override
+	public void addPages(Wizard wizard) {
+		WizardPage page0 = createPage(0, IHelpContextIds.TEMPLATE_VIEW);
+		page0.setTitle(PDETemplateMessages.ViewTemplate_title0);
+		page0.setDescription(PDETemplateMessages.ViewTemplate_desc0);
+		wizard.addPage(page0);
+
+		markPagesAdded();
+	}
+
+	@Override
+	public String getUsedExtensionPoint() {
+		return "org.eclipse.ui.views"; //$NON-NLS-1$
+	}
+
+	@Override
+	protected void updateModel(IProgressMonitor monitor) throws CoreException {
+
+		IBundle bundle = ((IBundlePluginModelBase) model).getBundleModel().getBundle();
+		bundle.setHeader(Constants.IMPORT_PACKAGE, "javax.inject"); //$NON-NLS-1$
+
+		IPluginBase plugin = model.getPluginBase();
+		IPluginExtension extension = createExtension("org.eclipse.ui.views", true); //$NON-NLS-1$
+		IPluginModelFactory factory = model.getPluginFactory();
+
+		String cid = getStringOption("viewCategoryId"); //$NON-NLS-1$
+
+		createCategory(extension, cid);
+		String fullClassName = getStringOption(KEY_PACKAGE_NAME) + "." + getStringOption("className"); //$NON-NLS-1$ //$NON-NLS-2$
+
+		IPluginElement viewElement = factory.createElement(extension);
+		viewElement.setName("view"); //$NON-NLS-1$
+		viewElement.setAttribute("id", fullClassName); //$NON-NLS-1$
+		viewElement.setAttribute("name", getStringOption("viewName")); //$NON-NLS-1$ //$NON-NLS-2$
+		viewElement.setAttribute("icon", "icons/sample.png"); //$NON-NLS-1$ //$NON-NLS-2$
+
+		viewElement.setAttribute("class", fullClassName); //$NON-NLS-1$
+		viewElement.setAttribute("category", cid); //$NON-NLS-1$
+		viewElement.setAttribute("inject", "true"); //$NON-NLS-1$ //$NON-NLS-2$
+		extension.add(viewElement);
+		if (!extension.isInTheModel())
+			plugin.add(extension);
+
+		if (addToPerspective.isSelected()) {
+			IPluginExtension perspectiveExtension = createExtension("org.eclipse.ui.perspectiveExtensions", true); //$NON-NLS-1$
+
+			IPluginElement perspectiveElement = factory.createElement(perspectiveExtension);
+			perspectiveElement.setName("perspectiveExtension"); //$NON-NLS-1$
+			perspectiveElement.setAttribute("targetID", //$NON-NLS-1$
+					"org.eclipse.jdt.ui.JavaPerspective"); //$NON-NLS-1$
+
+			IPluginElement view = factory.createElement(perspectiveElement);
+			view.setName("view"); //$NON-NLS-1$
+			view.setAttribute("id", fullClassName); //$NON-NLS-1$
+			view.setAttribute("relative", "org.eclipse.ui.views.ProblemView"); //$NON-NLS-1$ //$NON-NLS-2$
+			view.setAttribute("relationship", "stack"); //$NON-NLS-1$ //$NON-NLS-2$
+			perspectiveElement.add(view);
+
+			perspectiveExtension.add(perspectiveElement);
+			if (!perspectiveExtension.isInTheModel())
+				plugin.add(perspectiveExtension);
+		}
+	}
+
+	private void createCategory(IPluginExtension extension, String id) throws CoreException {
+		IPluginObject[] children = extension.getChildren();
+		for (IPluginObject child : children) {
+			IPluginElement element = (IPluginElement) child;
+			if (element.getName().equalsIgnoreCase("category")) { //$NON-NLS-1$
+				IPluginAttribute att = element.getAttribute("id"); //$NON-NLS-1$
+				if (att != null) {
+					String cid = att.getValue();
+					if (cid != null && cid.equals(id))
+						return;
+				}
+			}
+		}
+		IPluginElement categoryElement = model.getFactory().createElement(extension);
+		categoryElement.setName("category"); //$NON-NLS-1$
+		categoryElement.setAttribute("name", getStringOption("viewCategoryName")); //$NON-NLS-1$ //$NON-NLS-2$
+		categoryElement.setAttribute("id", id); //$NON-NLS-1$
+		extension.add(categoryElement);
+	}
+
+	@Override
+	public String[] getNewFiles() {
+		return new String[] { "icons/" }; //$NON-NLS-1$
+	}
+
+	@Override
+	public IPluginReference[] getDependencies(String schemaVersion) {
+		ArrayList<PluginReference> result = new ArrayList<>();
+		if (schemaVersion != null)
+			result.add(new PluginReference("org.eclipse.core.runtime")); //$NON-NLS-1$
+		result.add(new PluginReference("org.eclipse.ui")); //$NON-NLS-1$
+		return result.toArray(new IPluginReference[result.size()]);
+	}
+
+	@Override
+	protected String getFormattedPackageName(String id) {
+		String packageName = super.getFormattedPackageName(id);
+		if (packageName.length() != 0)
+			return packageName + ".views"; //$NON-NLS-1$
+		return "views"; //$NON-NLS-1$
+	}
+
+	@Override
+	public Object getValue(String name) {
+		if (name.equals("useEnablement")) //$NON-NLS-1$
+			return Boolean.valueOf(getTargetVersion() >= 3.3);
+		return super.getValue(name);
+	}
+}
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
index 7015d02..cba726e 100644
--- a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
@@ -185,6 +185,11 @@
 HelpTemplate_samples = Generate a '&Samples' category
 HelpTemplate_sampleText=Sample Table of Contents
 
+BrowserView_newPlugin=New plug-in project with a browser view
+BrowserViewTemplate_aGame=A Game
+BrowserViewTemplate_browserView=Browser View
+BrowserViewTemplate_selectJsType=Select the type of javascript
+BrowserViewTemplate_wbIntegration=Workbench Interaction
 BuilderNewWizard_wtitle = New plug-in project with a sample project builder and nature
 BuilderTemplate_title = Sample Project Builder and Nature
 BuilderTemplate_desc = Creates an incremental project builder and project nature.
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample.png b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample.png
new file mode 100644
index 0000000..02c4b79
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample.png
Binary files differ
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample@2x.png b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample@2x.png
new file mode 100644
index 0000000..c1224d1
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/bin/icons/sample@2x.png
Binary files differ
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.java b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.java
new file mode 100644
index 0000000..549f139
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.java
@@ -0,0 +1,214 @@
+package $packageName$;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.inject.Inject;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.*;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+import org.eclipse.ui.part.ViewPart;
+
+/**
+ * This sample class demonstrates how to plug-in a new
+ * workbench view with html and javascript content. The view 
+ * shows how data can be exchanged between Java and JavaScript.
+ */
+%Options in the template:%
+%packageName
+%className
+%viewName
+%viewCategoryId
+%viewCategoryName
+%game
+
+public class $className$ extends ViewPart implements ISelectionListener {
+
+%if addViewID
+	/**
+	 * The ID of the view as specified by the extension.
+	 */
+	public static final String ID = "$packageName$.$className$";
+
+%endif
+	@Inject
+	Shell shell;
+
+	private Action action1 = makeAction1();
+	private Action action2 = makeAction2();
+
+	private Browser fBrowser;
+
+	@Override
+	public void createPartControl(Composite parent) {
+		fBrowser = new Browser(parent, SWT.WEBKIT);
+		fBrowser.setText(getContent());
+		BrowserFunction prefs = new OpenPreferenceFunction(fBrowser, "openEclipsePreferences", () -> {
+			PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(shell, null, null, null);
+			dialog.open();
+		});
+		fBrowser.addDisposeListener(e -> prefs.dispose());
+		makeActions();
+		contributeToActionBars(getViewSite());
+		getSite().getPage().addSelectionListener(this);
+	}
+
+	private void contributeToActionBars(IViewSite viewSite) {
+		IActionBars bars = viewSite.getActionBars();
+		fillLocalPullDown(bars.getMenuManager());
+		fillLocalToolBar(bars.getToolBarManager());
+	}
+
+	private void fillLocalPullDown(IMenuManager manager) {
+		manager.add(action1);
+		manager.add(new Separator());
+		manager.add(action2);
+	}
+
+	private void fillLocalToolBar(IToolBarManager manager) {
+		manager.add(action1);
+		manager.add(action2);
+	}
+
+	private void makeActions() {
+		makeAction1();
+		makeAction2();
+	}
+
+%if game=="no"
+	private Action makeAction1() {
+		Action action = new Action() {
+			public void run() {
+				InputDialog inputDialog = new InputDialog(shell, null, "What must the browser say: ", null, null);
+				inputDialog.open();
+				String something = inputDialog.getValue();
+				fBrowser.execute("say(\"" + something + "\");");
+			}
+		};
+		action.setText("Say something");
+		action.setToolTipText("Say something");
+		action.setImageDescriptor(
+				PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
+		return action;
+	}
+%else
+	private Action makeAction1() {
+		Action action = new Action() {
+			public void run() {
+				fBrowser.setText(getContent());
+			}
+		};
+		action.setText("Reload");
+		action.setToolTipText("Reload");
+		action.setImageDescriptor(
+				PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_UNDO));
+		return action;
+	}
+%endif
+
+	private Action makeAction2() {
+		Action action = new Action() {
+			public void run() {
+				fBrowser.execute("changeColor();");
+			}
+		};
+		action.setText("Change Color");
+		action.setToolTipText("Change the color");
+		action.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED));
+		return action;
+	}
+
+	@Override
+	public void setFocus() {
+		fBrowser.setFocus();
+	}
+
+	private class OpenPreferenceFunction extends BrowserFunction {
+		private Runnable function;
+
+		OpenPreferenceFunction(Browser browser, String name, Runnable function) {
+			super(browser, name);
+			this.function = function;
+		}
+
+		@Override
+		public Object function(Object[] arguments) {
+			function.run();
+			return getName() + " executed!";
+		}
+	}
+
+	public String getContent() {
+		String js = null;
+		try (InputStream inputStream = getClass().getResourceAsStream("$className$.js")) {
+			js = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+		} catch (IOException e) {
+		}
+		StringBuilder buffer = new StringBuilder();
+%if game == "no"		
+		buffer.append("<!doctype html>");
+		buffer.append("<html lang=\"en\">");
+		buffer.append("<head>");
+		buffer.append("<meta charset=\"utf-8\">");
+		buffer.append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
+		buffer.append("<title>Sample View</title>");
+		buffer.append("<script>" + js + "</script>");
+		buffer.append("</script>");
+		buffer.append("</head>");
+		buffer.append("<body>");
+		buffer.append("<h3>Selection</h3>");
+		buffer.append("<div id=\"selection\"></div>");
+		buffer.append("<h3>Last Action</h3>");
+		buffer.append("<div id=\"lastAction\"></div>");
+		buffer.append("<h3>Call to Java</h3>");
+		buffer.append("<input id=button type=\"button\" value=\"Open Preferences\" onclick=\"openPreferences();\">");
+		buffer.append("</body>");
+		buffer.append("</html>");
+%else
+		buffer.append("<!doctype html>");
+		buffer.append("<html lang=\"en\">");
+		buffer.append("<head>");
+		buffer.append("<meta charset=\"utf-8\">");
+		buffer.append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
+		buffer.append("<style>* { padding: 0; margin: 0; } canvas { background: #eee; display: block; margin: 0 auto; }</style>");
+		buffer.append("<body>");
+		buffer.append("<canvas id=\"myCanvas\" width=\"480\" height=\"320\"></canvas>");
+		buffer.append("<script>" + js + "</script>");
+		buffer.append("</body>");
+		buffer.append("</html>");
+%endif
+		return buffer.toString();
+	}
+
+	@Override
+	public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+		if (selection.isEmpty()) {
+			return;
+		}
+		if (selection instanceof IStructuredSelection) {
+			fBrowser.execute("setSelection(\"" + part.getTitle() + "::"
+					+ ((IStructuredSelection) selection).getFirstElement().getClass().getSimpleName() + "\");");
+		} else {
+			fBrowser.execute("setSelection(\"Something was selected in part " + part.getTitle() + "\");");
+		}
+	}
+
+	@Override
+	public void dispose() {
+		getSite().getPage().removeSelectionListener(this);
+		super.dispose();
+	}
+}
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.js b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.js
new file mode 100644
index 0000000..9c9b459
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/browserView/java/$className$.js
@@ -0,0 +1,225 @@
+%if game == "no"
+// Call from Java to set the random background color
+function changeColor() {
+	var x = Math.floor(Math.random() * 256);
+	var y = Math.floor(Math.random() * 256);
+	var z = Math.floor(Math.random() * 256);
+	var bgColor = "rgb(" + x + "," + y + "," + z + ")";
+	document.body.style.background = bgColor;
+	document.getElementById("lastAction").innerText = "Background color set to " + bgColor;
+}
+
+// Call from Java to set the current selection
+function setSelection(text) {
+	document.getElementById("selection").innerText = text;
+	document.getElementById("lastAction").innerText = "Selection set to " + text;
+}
+
+// Call to Java to open the preferences
+function openPreferences() {
+	try {
+		var result = openEclipsePreferences(); // Java callback
+		document.getElementById("lastAction").innerText = "Preferences were opened. Return value was: " + result;
+	} catch (e) {
+		document.getElementById("lastAction").innerText = "A Java error occured: " + e.message;
+	}
+}
+
+// Call from java to say something
+function say(something) {
+	alert("Java says: " + something);
+	document.getElementById("lastAction").innerText = "We said: " + something;
+}
+%else
+
+// Game
+    var ballRadius = 10;
+    var paddleHeight = 10;
+    var paddleWidth = 75;
+    var brickWidth = 75;
+    var brickHeight = 20;
+    var brickPadding = 10;
+    var brickOffsetTop = 30;
+    var brickOffsetLeft = 30;
+    var canvas = document.getElementById("myCanvas");
+		canvas.width= window.innerWidth;
+		canvas.height=window.innerHeight;
+    var ctx = canvas.getContext("2d");
+    var x = canvas.width-brickOffsetLeft;
+    var y = canvas.height-brickOffsetTop;
+    var paddleX = (canvas.width-paddleWidth)/2;
+    var rightPressed = false;
+    var leftPressed = false;
+    var brickRowCount = Math.floor(x  / (brickWidth + brickPadding));
+    var brickColumnCount = Math.floor((y  / 2) / (brickHeight + brickPadding));
+    var dx = Math.floor(y/65);
+    var dy = -dx;
+    var score = 0;
+    var lives = 3;
+
+    var bricks = [];
+    for(var c=0; c<brickColumnCount; c++) {
+        bricks[c] = [];
+        for(var r=0; r<brickRowCount; r++) {
+            bricks[c][r] = { x: 0, y: 0, status: 1 };
+        }
+    }
+
+    document.addEventListener("keydown", keyDownHandler, false);
+    document.addEventListener("keyup", keyUpHandler, false);
+    document.addEventListener("mousemove", mouseMoveHandler, false);
+
+    function keyDownHandler(e) {
+        if(e.code  == "ArrowRight") {
+            rightPressed = true;
+        }
+        else if(e.code == 'ArrowLeft') {
+            leftPressed = true;
+        }
+    }
+    function keyUpHandler(e) {
+        if(e.code == 'ArrowRight') {
+            rightPressed = false;
+        }
+        else if(e.code == 'ArrowLeft') {
+            leftPressed = false;
+        }
+    }
+    function mouseMoveHandler(e) {
+        var relativeX = e.clientX - canvas.offsetLeft;
+        if(relativeX > 0 && relativeX < canvas.width) {
+            paddleX = relativeX - paddleWidth/2;
+        }
+    }
+    function collisionDetection() {
+        for(var c=0; c<brickColumnCount; c++) {
+            for(var r=0; r<brickRowCount; r++) {
+                var b = bricks[c][r];
+                if(b.status == 1) {
+                    if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
+                        dy = -dy;
+                        b.status = 0;
+                        score++;
+                        if(score == brickRowCount*brickColumnCount) {
+                            gameOver("YOU WON!")
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+		return false;
+    }
+
+    function drawBall() {
+        ctx.beginPath();
+        ctx.arc(x, y, ballRadius, 0, Math.PI*2);
+        ctx.fillStyle = "#0095DD";
+        ctx.fill();
+        ctx.closePath();
+    }
+    function drawPaddle() {
+        ctx.beginPath();
+        ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
+        ctx.fillStyle = "#0095DD";
+        ctx.fill();
+        ctx.closePath();
+    }
+    function drawBricks() {
+        for(var c=0; c<brickColumnCount; c++) {
+            for(var r=0; r<brickRowCount; r++) {
+                if(bricks[c][r].status == 1) {
+                    var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
+                    var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
+                    bricks[c][r].x = brickX;
+                    bricks[c][r].y = brickY;
+                    ctx.beginPath();
+                    ctx.rect(brickX, brickY, brickWidth, brickHeight);
+                    ctx.fillStyle = "#0095DD";
+                    ctx.fill();
+                    ctx.closePath();
+                }
+            }
+        }
+    }
+	function changeColor() {
+		var x = Math.floor(Math.random() * 256);
+		var y = Math.floor(Math.random() * 256);
+		var z = Math.floor(Math.random() * 256);
+		var bgColor = "rgb(" + x + "," + y + "," + z + ")";
+		document.body.style.background = bgColor;
+		document.getElementById("lastAction").innerText = "Background color set to " + bgColor;
+	}
+    function drawScore() {
+        ctx.font = "16px Arial";
+        ctx.fillStyle = "#0095DD";
+        ctx.fillText("Score: "+score, 8, 20);
+    }
+    function drawLives() {
+        ctx.font = "16px Arial";
+        ctx.fillStyle = "#0095DD";
+        ctx.fillText("Lives: "+lives, canvas.width-65, 20);
+    }
+
+    function draw() {
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+        drawBricks();
+        drawBall();
+        drawPaddle();
+        drawScore();
+        drawLives();
+		if(collisionDetection()){
+			return;
+		}
+
+        if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
+            dx = -dx;
+        }
+        if(y + dy < ballRadius) {
+            dy = -dy;
+        }
+        else if(y + dy > canvas.height-ballRadius) {
+            if(x > paddleX && x < paddleX + paddleWidth) {
+                dy = -dy;
+            }
+            else {
+                lives--;
+                if(!lives) {
+                    gameOver("YOU LOST!");
+                    return;
+                }
+                else {
+                    x = canvas.width/2;
+                    y = canvas.height-30;
+                    dx = Math.floor((canvas.height-brickOffsetTop)/65);
+    				dy = -dx;
+                    paddleX = (canvas.width-paddleWidth)/2;
+                }
+            }
+        }
+
+        if(rightPressed && paddleX < canvas.width-paddleWidth) {
+            paddleX += 7;
+        }
+        else if(leftPressed && paddleX > 0) {
+            paddleX -= 7;
+        }
+
+        x += dx;
+        y += dy;
+        requestAnimationFrame(draw);
+    }
+
+    function gameOver(text){
+		ctx.font="30px Consolas";
+		ctx.fillStyle = "red";
+		ctx.textAlign = "center";
+		ctx.fillText(text, canvas.width / 2, (canvas.height / 8) * 6);
+		ctx.font="10px Consolas";
+		ctx.fillStyle = "blue";
+		ctx.textAlign = "center";
+		ctx.fillText("(game code donated by https://github.com/end3r)", canvas.width/2, (canvas.height / 8) * 5);
+	}
+
+    draw();
+%endif
\ No newline at end of file