Bug 536185 - [Tips] Add possibility to provide functions, which can be
invoked from the Browser

PS1-PS3
* Create API and implementation

PS4
* Included example from Bug 536227

PS5 - PS7
* Minor refactoring.

Change-Id: Ie6acd0998d73713ae4d8428a48d7abfc834d14f8
Signed-off-by: Wim Jongman <wim.jongman@remainsoftware.com>
diff --git a/org.eclipse.tips.examples/META-INF/MANIFEST.MF b/org.eclipse.tips.examples/META-INF/MANIFEST.MF
index bfbc38d..dadc220 100644
--- a/org.eclipse.tips.examples/META-INF/MANIFEST.MF
+++ b/org.eclipse.tips.examples/META-INF/MANIFEST.MF
@@ -5,12 +5,12 @@
 Bundle-Version: 0.1.100.qualifier
 Bundle-Vendor: Eclipse
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0",
+Require-Bundle: org.eclipse.core.runtime;bundle-version="3.6.0",
  org.eclipse.tips.core;bundle-version="0.1.0",
  org.eclipse.tips.json;bundle-version="0.1.0",
- org.eclipse.swt,
- org.eclipse.jface,
- org.eclipse.tips.ui;bundle-version="0.1.0"
+ org.eclipse.tips.ui;bundle-version="0.1.0",
+ org.eclipse.e4.core.commands;bundle-version="0.12.200",
+ org.eclipse.ui;bundle-version="3.108.0"
 Eclipse-BundleShape: dir
 Import-Package: org.osgi.framework;version="1.8.0"
 Automatic-Module-Name: org.eclipse.tips.examples
diff --git a/org.eclipse.tips.examples/src/org/eclipse/tips/examples/browserfunction/BrowserFunctionTip.java b/org.eclipse.tips.examples/src/org/eclipse/tips/examples/browserfunction/BrowserFunctionTip.java
new file mode 100644
index 0000000..3bfa874
--- /dev/null
+++ b/org.eclipse.tips.examples/src/org/eclipse/tips/examples/browserfunction/BrowserFunctionTip.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2018 vogella GmbH
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ *     simon.scholz@vogella.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.examples.browserfunction;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.eclipse.core.commands.ParameterizedCommand;
+import org.eclipse.e4.core.commands.ECommandService;
+import org.eclipse.e4.core.commands.EHandlerService;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tips.core.IHtmlTip;
+import org.eclipse.tips.core.Tip;
+import org.eclipse.tips.core.TipImage;
+import org.eclipse.tips.examples.DateUtil;
+import org.eclipse.tips.ui.IBrowserFunctionProvider;
+import org.eclipse.ui.PlatformUI;
+
+@SuppressWarnings("restriction")
+public class BrowserFunctionTip extends Tip implements IHtmlTip, IBrowserFunctionProvider {
+
+	public BrowserFunctionTip(String providerId) {
+		super(providerId);
+	}
+
+	@Override
+	public Map<String, Function<Object[], Object>> getBrowserFunctions() {
+		return Collections.singletonMap("openPreferencesBrowserFunction", this::openPreferences);
+	}
+
+	@Override
+	public String getHTML() {
+		return "<html><head><title>IHtmlTip with IBrowserFunctionProvider</title></head>"
+				+ "<body><p>This tip shows HTML and provides a BrowserFunction, which can be invoked by using JavaScript.</p>"
+				+ "<p><button onclick=\"openPreferencesBrowserFunction()\">I gonna open the preferences from the Browser</button></p></body></html>"
+				+ "<p><a href=\"#\" onclick=\"openPreferencesBrowserFunction()\">I do the same but with a link</a></p></body></html>";
+	}
+
+	@Override
+	public TipImage getImage() {
+		return null;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return DateUtil.getDateFromYYMMDD("25/06/2018");
+	}
+
+	@Override
+	public String getSubject() {
+		return "This is an IHtmlTip, which also implements IBrowserFunctionProvider";
+	}
+
+	private Object openPreferences(Object[] args) {
+		ECommandService commandService = PlatformUI.getWorkbench().getService(ECommandService.class);
+		EHandlerService handlerService = PlatformUI.getWorkbench().getService(EHandlerService.class);
+
+		ParameterizedCommand command = commandService.createCommand("org.eclipse.ui.window.preferences",
+				Collections.singletonMap("preferencePageId", "org.eclipse.ui.preferencePages.Keys"));
+		Display.getDefault().asyncExec(() -> {
+			handlerService.executeHandler(command);
+		});
+
+		return null;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.tips.examples/src/org/eclipse/tips/examples/eclipsetips/EclipseTipsProvider.java b/org.eclipse.tips.examples/src/org/eclipse/tips/examples/eclipsetips/EclipseTipsProvider.java
index 3a90de1..b5f35a6 100644
--- a/org.eclipse.tips.examples/src/org/eclipse/tips/examples/eclipsetips/EclipseTipsProvider.java
+++ b/org.eclipse.tips.examples/src/org/eclipse/tips/examples/eclipsetips/EclipseTipsProvider.java
@@ -22,6 +22,7 @@
 import org.eclipse.tips.core.TipImage;
 import org.eclipse.tips.core.internal.LogUtil;
 import org.eclipse.tips.examples.DateUtil;
+import org.eclipse.tips.examples.browserfunction.BrowserFunctionTip;
 import org.eclipse.tips.examples.tips.MediaWikiTip;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
@@ -99,6 +100,7 @@
 		tips.add(createTip3());
 		tips.add(createTip4());
 		tips.add(createTip5());
+		tips.add(new BrowserFunctionTip(getID()));
 		setTips(tips);
 		subMonitor.done();
 		return Status.OK_STATUS;
diff --git a/org.eclipse.tips.ui/src/org/eclipse/tips/ui/IBrowserFunctionProvider.java b/org.eclipse.tips.ui/src/org/eclipse/tips/ui/IBrowserFunctionProvider.java
new file mode 100644
index 0000000..d7d9691
--- /dev/null
+++ b/org.eclipse.tips.ui/src/org/eclipse/tips/ui/IBrowserFunctionProvider.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2018 vogella GmbH
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ *     simon.scholz@vogella.com - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tips.ui;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.tips.core.IHtmlTip;
+import org.eclipse.tips.core.IUrlTip;
+
+/**
+ * This interface is intended to be implemented by {@link IHtmlTip} or
+ * {@link IUrlTip} instances to provide Java functions that can be invoked from
+ * within an SWT browser.
+ *
+ * @see IHtmlTip
+ * @see IUrlTip
+ * @see BrowserFunction
+ */
+public interface IBrowserFunctionProvider {
+
+	/**
+	 * Provides a map with functions which can be invoked by JavaScript code
+	 * provided by an {@link IHtmlTip} or {@link IUrlTip}.
+	 *
+	 * @return a {@link Map} containing names for a JavaScript function as key and
+	 *         {@link Function} objects, which can be invoked from the JavaScript in
+	 *         a SWT Browser.
+	 *
+	 * @see BrowserFunction
+	 */
+	Map<String, Function<Object[], Object>> getBrowserFunctions();
+}
diff --git a/org.eclipse.tips.ui/src/org/eclipse/tips/ui/internal/TipComposite.java b/org.eclipse.tips.ui/src/org/eclipse/tips/ui/internal/TipComposite.java
index b8e5d98..8a81df8 100644
--- a/org.eclipse.tips.ui/src/org/eclipse/tips/ui/internal/TipComposite.java
+++ b/org.eclipse.tips.ui/src/org/eclipse/tips/ui/internal/TipComposite.java
@@ -18,6 +18,7 @@
 import java.util.List;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.function.Function;
 
 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -26,6 +27,7 @@
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
 import org.eclipse.swt.custom.StackLayout;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
@@ -51,6 +53,7 @@
 import org.eclipse.tips.core.TipProvider;
 import org.eclipse.tips.core.internal.LogUtil;
 import org.eclipse.tips.core.internal.TipManager;
+import org.eclipse.tips.ui.IBrowserFunctionProvider;
 import org.eclipse.tips.ui.ISwtTip;
 import org.eclipse.tips.ui.internal.util.ImageUtil;
 import org.eclipse.tips.ui.internal.util.ResourceManager;
@@ -79,6 +82,7 @@
 	private Button fMultiActionButton;
 	private Composite fContentComposite;
 	private List<Image> fActionImages = new ArrayList<>();
+	private List<BrowserFunction> fBrowserFunctions = new ArrayList<>();
 	private Menu fActionMenu;
 	private ToolBar ftoolBar;
 	private ToolItem fStartupItem;
@@ -379,18 +383,38 @@
 	}
 
 	private void loadContent(Tip tip) {
+		disposeBrowserFunctions();
 		if (tip instanceof ISwtTip) {
 			loadContentSWT(tip);
 		} else if (tip instanceof IHtmlTip) {
 			loadContentHtml((IHtmlTip) tip);
+			applyBrowserFunctions(tip);
 		} else if (tip instanceof IUrlTip) {
 			loadContentUrl((IUrlTip) tip);
+			applyBrowserFunctions(tip);
 		} else {
 			fTipManager.log(LogUtil.error(getClass(), Messages.TipComposite_12 + tip));
 		}
+
 		fContentComposite.requestLayout();
 	}
 
+	private void applyBrowserFunctions(Tip tip) {
+		if (tip instanceof IBrowserFunctionProvider) {
+			((IBrowserFunctionProvider) tip).getBrowserFunctions()
+					.forEach((name, function) -> fBrowserFunctions.add(createBrowserFunction(name, function)));
+		}
+	}
+
+	private BrowserFunction createBrowserFunction(String functionName, Function<Object[], Object> function) {
+		return new BrowserFunction(getBrowser(), functionName) {
+			@Override
+			public Object function(Object[] arguments) {
+				return function.apply(arguments);
+			}
+		};
+	}
+
 	private void loadContentHtml(IHtmlTip tip) {
 		fBrowser.setText(getHTML(tip).trim());
 	}
@@ -416,9 +440,15 @@
 	private void prepareForHTML() {
 		fContentStack.topControl = fBrowserComposite;
 		loadTimeOutScript();
+
 		fBrowserComposite.requestLayout();
 	}
 
+	private void disposeBrowserFunctions() {
+		fBrowserFunctions.forEach(BrowserFunction::dispose);
+		fBrowserFunctions.clear();
+	}
+
 	/**
 	 * Sets content in the browser that displays a message after 1500ms if the Tip
 	 * could not load fast enough.