Browse Source

Gui: Strip `about:` from links [#799]

gnosygnu 4 years ago
parent
commit
caad4145d5
1 changed files with 345 additions and 299 deletions
  1. 345 299
      150_gfui/src/gplx/gfui/kits/swts/Swt_html.java

+ 345 - 299
150_gfui/src/gplx/gfui/kits/swts/Swt_html.java

@@ -1,6 +1,6 @@
 /*
 XOWA: the XOWA Offline Wiki Application
-Copyright (C) 2012-2017 gnosygnu@gmail.com
+Copyright (C) 2012-2020 gnosygnu@gmail.com
 
 XOWA is licensed under the terms of the General Public License (GPL) Version 3,
 or alternatively under the terms of the Apache License Version 2.0.
@@ -13,301 +13,347 @@ The terms of each license can be found in the source code repository:
 GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
 Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
 */
-package gplx.gfui.kits.swts; import gplx.*; import gplx.gfui.*; import gplx.gfui.kits.*;
-import gplx.core.envs.System_;
-import gplx.core.primitives.*;
-import gplx.core.threads.Thread_adp_;
-import gplx.gfui.controls.elems.GfuiElem;
-import gplx.gfui.controls.gxws.GxwCbkHost;
-import gplx.gfui.controls.gxws.GxwCore_base;
-import gplx.gfui.controls.gxws.GxwElem;
-import gplx.gfui.controls.gxws.Gxw_html;
-import gplx.gfui.controls.gxws.Gxw_html_load_tid_;
-import gplx.gfui.controls.standards.Gfui_html;
-import gplx.gfui.controls.standards.Gfui_tab_mgr;
-import gplx.gfui.draws.ColorAdp;
-import gplx.gfui.draws.ColorAdp_;
-import gplx.gfui.ipts.*;
-import gplx.gfui.kits.core.Swt_kit;
-
-import java.security.acl.Owner;
-import gplx.*;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.*;
-import org.eclipse.swt.events.*;
-import org.eclipse.swt.graphics.*;
-import org.eclipse.swt.widgets.*;
-import java.security.acl.Owner;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.*;
-import org.eclipse.swt.events.*;
-import org.eclipse.swt.graphics.*;
-import org.eclipse.swt.widgets.*;
-
-public class Swt_html implements Gxw_html, Swt_control, FocusListener, Gfo_evt_mgr_owner {
-	private Swt_html_lnr_location lnr_location; private Swt_html_lnr_status lnr_status;
-	public Swt_html(Swt_kit kit, Swt_control owner_control, Keyval_hash ctorArgs) {
-		this.kit = kit;
-		lnr_location = new Swt_html_lnr_location(this);
-		lnr_status = new Swt_html_lnr_status(this);
-		Object browser_tid_obj = ctorArgs.Get_val_or(Swt_kit.Cfg_Html_BrowserType, null);
-		this.browser_tid = browser_tid_obj == null ? Browser_tid_none : Int_.Cast(browser_tid_obj);
-		browser = new Browser(owner_control.Under_composite(), browser_tid);
-		core = new Swt_core_cmds_html(this, browser);
-		browser.addKeyListener(new Swt_lnr_key(this));
-		browser.addMouseListener(new Swt_html_lnr_mouse(this, browser, kit));
-		browser.addLocationListener(lnr_location);
-		browser.addProgressListener(new Swt_html_lnr_progress(this));
-		browser.addStatusTextListener(lnr_status);
-		browser.addFocusListener(this);
-		browser.addTitleListener(new Swt_html_lnr_title(this));
-		browser.addMouseWheelListener(new Swt_html_lnr_wheel(this));
-		
-		// browser.addOpenWindowListener(new Swt_open_window_listener(this));	// handle target='blank'
-		// browser.addTraverseListener(new Swt_html_lnr_Traverse(this));
-	}
-	public Swt_kit Kit() {return kit;} private Swt_kit kit;
-	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; 	public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;}
-	@Override public Control Under_control() {return browser;} private Browser browser;
-	@Override public Composite Under_composite() {return null;}
-	@Override public Control Under_menu_control() {return browser;}
-	public int Browser_tid() {return browser_tid;} private final int browser_tid;
-	public String Load_by_url_path() {return load_by_url_path;} private String load_by_url_path;
-	public void 		Html_doc_html_load_by_mem(String html) {
-		this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_mem;
-		this.load_by_url_path = null;
-		browser.setText(html);	// DBG: Io_mgr.I.SaveFilStr(Io_url_.new_fil_("C:\\temp.txt"), s)
-	}
-	public void Html_doc_html_load_by_url(Io_url path, String html) {
-		this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_url;
-		this.load_by_url_path = path.To_http_file_str(); 
-		Io_mgr.Instance.SaveFilStr(path, html);
-		browser.setUrl(path.Xto_api());
-	}
-	public byte 		Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid;
-	public void 		Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;}
-	public void 		Html_js_enabled_(boolean v) 									{browser.setJavascriptEnabled(v);}
-	public void 		Html_js_cbks_add(String func_name, Gfo_invk invk) 				{new Swt_html_func(browser, func_name, invk);}
-	public String 		Html_js_eval_script(String script) 								{return Eval_script_as_str(script);}
-	public Object		Html_js_eval_script_as_obj(String script) 						{return Eval_script(script);}
-	public boolean 		Html_js_eval_proc_as_bool(String proc, Object... args) 			{return Bool_.Cast(Html_js_eval_proc_as_obj(proc, args));}
-	public String	Html_js_eval_proc_as_str(String proc, Object... args) {return Object_.Xto_str_strict_or_null(Html_js_eval_proc_as_obj(proc, args));}
-	public String Html_js_send_json(String name, String data) {
-		String script = String_.Format("return {0}('{1}');", name, String_.Replace(data, "\n", "") );
-		return (String)Eval_script(script);
-	}
-	private Object Html_js_eval_proc_as_obj(String proc, Object... args) {
-		Bry_bfr bfr = Bry_bfr_.New();
-		bfr.Add_str_a7("return ").Add_str_u8(proc).Add_byte(Byte_ascii.Paren_bgn);
-		int args_len = args.length;
-		for (int i = 0; i < args_len; ++i) {
-			Object arg = args[i];
-			if (i != 0) bfr.Add_byte(Byte_ascii.Comma);
-			boolean quote_val = true;
-			if 		(	Type_.Eq_by_obj(arg, Bool_.Cls_ref_type)
-					||	Type_.Eq_by_obj(arg, Int_.Cls_ref_type)
-					||	Type_.Eq_by_obj(arg, Long_.Cls_ref_type)
-				) {
-				quote_val = false;
-			}
-			if (quote_val) bfr.Add_byte(Byte_ascii.Apos);
-			if (quote_val) 
-				bfr.Add_str_u8(Escape_quote(Object_.Xto_str_strict_or_null_mark(arg)));
-			else
-				bfr.Add_obj_strict(arg);
-			if (quote_val) bfr.Add_byte(Byte_ascii.Apos);
-		}
-		bfr.Add_byte(Byte_ascii.Paren_end).Add_byte(Byte_ascii.Semic);
-		return Eval_script(bfr.To_str_and_clear());
-	}
-	public static String Escape_quote(String v) {
-		String rv = v;
-		rv = String_.Replace(rv, "'", "\\'");
-		rv = String_.Replace(rv, "\"", "\\\"");
-		rv = String_.Replace(rv, "\n", "\\n");
-		return rv;
-	}
-	public void Html_invk_src_(Gfo_evt_itm invk) {lnr_location.Host_set(invk); lnr_status.Host_set(invk);}
-	public void Html_dispose() {
-		browser.dispose();
-		delete_owner.SubElems().DelOrFail(delete_cur);	// NOTE: must delete cur from owner, else new tab will fail after closing one; DATE:2014-07-09
-		System_.Garbage_collect();
-	}
-	private GfuiElem delete_owner, delete_cur;
-	public void Delete_elems_(GfuiElem delete_owner, GfuiElem delete_cur) {this.delete_owner = delete_owner; this.delete_cur = delete_cur;}	// HACK: set owner / cur so delete can work;
-	@Override public GxwCore_base Core() {return core;} private GxwCore_base core;
-	@Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host;
-	@Override public String TextVal() {return browser.getText();}
-	@Override public void TextVal_set(String v) {browser.setText(v);}
-	@Override public void EnableDoubleBuffering() {}
-	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;}
-	private String Eval_script_as_str(String script) 	{return (String)Eval_script(script);}
-	public Object Eval_script(String script) {
-		eval_rslt.Clear();
-		try {
-			eval_rslt.Result_set(browser.evaluate(script));
-			return eval_rslt.Result();
-		}
-		catch (Exception e) {eval_rslt.Error_set(e.getMessage()); 				return eval_rslt.Error();}
-	}	private Swt_html_eval_rslt eval_rslt = new Swt_html_eval_rslt();
-	@Override public void focusGained(FocusEvent arg0) {}
-	@Override public void focusLost(FocusEvent arg0) {}
-	public static final int
-	  Browser_tid_none 		= SWT.NONE
-	, Browser_tid_mozilla 	= SWT.MOZILLA
-	, Browser_tid_webkit	= SWT.WEBKIT
-	;	
-}
-class Swt_core_cmds_html extends Swt_core__basic {
-	public Swt_core_cmds_html(Swt_html html_box, Control control) {super(control);}
-	@Override public void Focus() {
-		if (Focus_able())
-			control.forceFocus();
-	}
-	@Override public void Select_exec() {
-		this.Focus();
-	}
-}
-class Swt_html_lnr_traverse implements TraverseListener {
-	public Swt_html_lnr_traverse(Swt_html html_box) {}
-	@Override public void keyTraversed(TraverseEvent arg0) {}
-}
-class Swt_html_lnr_title implements TitleListener {
-	private Swt_html html_box;
-	public Swt_html_lnr_title(Swt_html html_box) {this.html_box = html_box;}
-	@Override public void changed(TitleEvent ev) {
-		try {UsrDlg_.Instance.Note(ev.title);}		
-		catch (Exception e) {html_box.Kit().Ask_ok("xowa.swt.html_box", "title.fail", Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 
-	}
-}
-class Swt_html_func extends BrowserFunction {    
-	private final Gfo_invk invk;
-	private final Browser browser;
-    public Swt_html_func(Browser browser, String name, Gfo_invk invk) {
-        super (browser, name);
-        this.browser = browser;
-        this.invk = invk;
-    }
-    public Object function (Object[] args) {
-    	try {
-    		return gplx.gfui.controls.standards.Gfui_html.Js_args_exec(invk, args);
-    	}
-    	catch (Exception e) {
-    		String rv = Err_.Message_gplx_full(e);
-    		browser.execute("alert('" + Swt_html.Escape_quote(rv) + "')");
-    		return rv;
-    	}
-    }
-}
-class Swt_html_lnr_status implements StatusTextListener {
-	public Swt_html_lnr_status(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box;
-	public void Host_set(Gfo_evt_itm host) {this.host = host;} Gfo_evt_itm host;
-	@Override public void changed(StatusTextEvent ev) {
-		if (html_box.Kit().Kit_mode__term()) return;	// shutting down raises status changed events; ignore, else SWT exception thrown; DATE:2014-05-29 
-		String ev_text = ev.text;
-		String load_by_url_path = html_box.Load_by_url_path();
-		if (load_by_url_path != null) ev_text = String_.Replace(ev_text, load_by_url_path, "");	// remove "C:/xowa/tab_1.html"
-//		if (String_.Has(ev_text, "Loading [MathJax]")) return;	// suppress MathJax messages; // NOTE: disabled for 2.1 (which no longer outputs messages to status); DATE:2013-05-03
-		try {if (host != null) Gfo_evt_mgr_.Pub_obj(host, Gfui_html.Evt_link_hover, "v", ev_text);}
-		catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", "status.fail", Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 
-	}
-}
-class Swt_html_lnr_progress implements ProgressListener {
-	public Swt_html_lnr_progress(Swt_html html_box) {}
-	@Override public void changed(ProgressEvent arg0) {}
-	@Override public void completed(ProgressEvent arg0) {
-//		UsrDlg_._.Note("done");
-	}
-}
-class Swt_html_lnr_location implements LocationListener {
-	public Swt_html_lnr_location(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box;
-	public void Host_set(Gfo_evt_itm host) {this.host = host;} private Gfo_evt_itm host;
-	@Override public void changed(LocationEvent arg) 	{Pub_evt(arg, Gfui_html.Evt_location_changed);}
-	@Override public void changing(LocationEvent arg) 	{Pub_evt(arg, Gfui_html.Evt_location_changing);}
-	private void Pub_evt(LocationEvent arg, String evt) {		
-		String location = arg.location;
-		if (String_.Eq(location, "about:blank")) return;	// location changing event fires once when page is loaded; ignore
-		if (	html_box.Browser_tid() == Swt_html.Browser_tid_webkit	// webkit prefixes "about:blank" to anchors; causes TOC to fail when clicking on links; EX:about:blank#TOC1; DATE:2015-06-09
-			&&	String_.Has_at_bgn(location, "about:blank")) {
-			location = String_.Mid(location, 11);	// 11 = "about:blank".length 
-		}
-		if (	html_box.Html_doc_html_load_tid() == Gxw_html_load_tid_.Tid_url	// navigating to file://page.html will fire location event; ignore if url mode
-			&& 	String_.Has_at_bgn(location, "file:")
-			&& 	String_.Has_at_end(location, ".html")
-			)
-			return;
-		try {
-			Gfo_evt_mgr_.Pub_obj(host, evt, "v", location);
-			arg.doit = false; // cancel navigation event, else there will be an error when trying to go to invalid location
-		}
-		catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", evt, Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 		
-	}
-}
-class Swt_html_lnr_mouse implements MouseListener {
-	private GxwElem elem; private Browser browser; private Swt_kit kit;
-	public Swt_html_lnr_mouse(GxwElem elem, Browser browser, Swt_kit kit) {this.elem = elem; this.browser = browser; this.kit = kit;}
-	@Override public void mouseDown(MouseEvent ev) {
-		if (Is_at_scrollbar_area()) return;
-		elem.Host().MouseDownCbk(XtoMouseData(ev));
-	}
-	@Override public void mouseUp(MouseEvent ev) {
-		if (Is_at_scrollbar_area()) return;
-		elem.Host().MouseUpCbk(XtoMouseData(ev));
-	}
-	private boolean Is_at_scrollbar_area() {
-		// WORKAROUND.SWT: SEE:NOTE_1:browser scrollbar and click  
-		Point browser_size = browser.getSize();
-		Point click_pos = kit.Swt_display().getCursorLocation();
-		return click_pos.x >= browser_size.x - 12;
-	}
-	@Override public void mouseDoubleClick(MouseEvent ev) {}
-	IptEvtDataMouse XtoMouseData(MouseEvent ev) {
-		IptMouseBtn btn = null;
-		switch (ev.button) {
-			case 1: btn = IptMouseBtn_.Left; break;
-			case 2: btn = IptMouseBtn_.Middle; break;
-			case 3: btn = IptMouseBtn_.Right; break;
-			case 4: btn = IptMouseBtn_.X1; break;
-			case 5: btn = IptMouseBtn_.X2; break;
-		}
-		return IptEvtDataMouse.new_(btn, IptMouseWheel_.None, ev.x, ev.y);		
-	}
-}
-class Swt_html_lnr_wheel implements MouseWheelListener {
-	private final Swt_html html_box;
-	public Swt_html_lnr_wheel(Swt_html html_box) {
-		this.html_box = html_box;
-	}
-	@Override public void mouseScrolled(MouseEvent evt) {
-		if (evt.stateMask == SWT.CTRL) {	// ctrl held;
-			Gfo_evt_mgr_.Pub_obj(html_box, Gfui_html.Evt_zoom_changed, "clicks_is_positive", evt.count > 0);
-		}
-	}	
-}
-//class Swt_open_window_listener implements OpenWindowListener {
-//	private final Swt_html html_box;
-//	public Swt_open_window_listener(Swt_html html_box) {this.html_box = html_box;}
-//	@Override public void open(WindowEvent arg0) {
-//		Tfds.Write();
-//	}	
-//}
-/*
-NOTE_1:browser scrollbar and click
-a click in the scrollbar area will raise a mouse-down/mouse-up event in content-editable mode
-. a click should be consumed by the scrollbar and not have any effect elsewhere on the window
-. instead, a click event is raised, and counted twice
-  1) for the scroll bar this will scroll the area.
-  2) for the window. if keyboard-focus is set on a link, then it will activate the link.
-
-swt does not expose any scrollbar information (visible, width), b/c the scrollbar is controlled by the underlying browser
-so, assume:
-. scrollbar is always present
-. scrollbar has arbitrary width (currently 12)
-. and discard if click is in this scrollbar area
-
-two issues still occur with the workaround
-1) even if the scrollbar is not present, any click on the right-hand edge of the screen will be ignored
-2) click -> hold -> move mouse over to left -> release; the mouse up should be absorbed, but it is not due to position of release   
-*/
+package gplx.gfui.kits.swts;
+
+import gplx.Bool_;
+import gplx.Bry_bfr;
+import gplx.Bry_bfr_;
+import gplx.Byte_ascii;
+import gplx.Err_;
+import gplx.GfoMsg;
+import gplx.Gfo_evt_itm;
+import gplx.Gfo_evt_mgr;
+import gplx.Gfo_evt_mgr_;
+import gplx.Gfo_evt_mgr_owner;
+import gplx.Gfo_invk;
+import gplx.Gfo_invk_;
+import gplx.GfsCtx;
+import gplx.Int_;
+import gplx.Io_mgr;
+import gplx.Io_url;
+import gplx.Keyval_hash;
+import gplx.Long_;
+import gplx.Object_;
+import gplx.String_;
+import gplx.Type_;
+import gplx.UsrDlg_;
+import gplx.core.envs.System_;
+import gplx.gfui.controls.elems.GfuiElem;
+import gplx.gfui.controls.gxws.GxwCbkHost;
+import gplx.gfui.controls.gxws.GxwCore_base;
+import gplx.gfui.controls.gxws.GxwElem;
+import gplx.gfui.controls.gxws.Gxw_html;
+import gplx.gfui.controls.gxws.Gxw_html_load_tid_;
+import gplx.gfui.controls.standards.Gfui_html;
+import gplx.gfui.ipts.IptEvtDataMouse;
+import gplx.gfui.ipts.IptMouseBtn;
+import gplx.gfui.ipts.IptMouseBtn_;
+import gplx.gfui.ipts.IptMouseWheel_;
+import gplx.gfui.kits.core.Swt_kit;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.BrowserFunction;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.browser.LocationListener;
+import org.eclipse.swt.browser.ProgressEvent;
+import org.eclipse.swt.browser.ProgressListener;
+import org.eclipse.swt.browser.StatusTextEvent;
+import org.eclipse.swt.browser.StatusTextListener;
+import org.eclipse.swt.browser.TitleEvent;
+import org.eclipse.swt.browser.TitleListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseWheelListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class Swt_html implements Gxw_html, Swt_control, FocusListener, Gfo_evt_mgr_owner {
+	private Swt_html_lnr_location lnr_location; private Swt_html_lnr_status lnr_status;
+	public Swt_html(Swt_kit kit, Swt_control owner_control, Keyval_hash ctorArgs) {
+		this.kit = kit;
+		lnr_location = new Swt_html_lnr_location(this);
+		lnr_status = new Swt_html_lnr_status(this);
+		Object browser_tid_obj = ctorArgs.Get_val_or(Swt_kit.Cfg_Html_BrowserType, null);
+		this.browser_tid = browser_tid_obj == null ? Browser_tid_none : Int_.Cast(browser_tid_obj);
+		browser = new Browser(owner_control.Under_composite(), browser_tid);
+		core = new Swt_core_cmds_html(this, browser);
+		browser.addKeyListener(new Swt_lnr_key(this));
+		browser.addMouseListener(new Swt_html_lnr_mouse(this, browser, kit));
+		browser.addLocationListener(lnr_location);
+		browser.addProgressListener(new Swt_html_lnr_progress(this));
+		browser.addStatusTextListener(lnr_status);
+		browser.addFocusListener(this);
+		browser.addTitleListener(new Swt_html_lnr_title(this));
+		browser.addMouseWheelListener(new Swt_html_lnr_wheel(this));
+		
+		// browser.addOpenWindowListener(new Swt_open_window_listener(this));	// handle target='blank'
+		// browser.addTraverseListener(new Swt_html_lnr_Traverse(this));
+	}
+	public Swt_kit Kit() {return kit;} private Swt_kit kit;
+	public Gfo_evt_mgr Evt_mgr() {return ev_mgr;} private Gfo_evt_mgr ev_mgr; 	public void Evt_mgr_(Gfo_evt_mgr v) {ev_mgr = v;}
+	@Override public Control Under_control() {return browser;} private Browser browser;
+	@Override public Composite Under_composite() {return null;}
+	@Override public Control Under_menu_control() {return browser;}
+	public int Browser_tid() {return browser_tid;} private final int browser_tid;
+	public String Load_by_url_path() {return load_by_url_path;} private String load_by_url_path;
+	public void 		Html_doc_html_load_by_mem(String html) {
+		this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_mem;
+		this.load_by_url_path = null;
+		browser.setText(html);	// DBG: Io_mgr.I.SaveFilStr(Io_url_.new_fil_("C:\\temp.txt"), s)
+	}
+	public void Html_doc_html_load_by_url(Io_url path, String html) {
+		this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_url;
+		this.load_by_url_path = path.To_http_file_str(); 
+		Io_mgr.Instance.SaveFilStr(path, html);
+		browser.setUrl(path.Xto_api());
+	}
+	public byte 		Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid;
+	public void 		Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;}
+	public void 		Html_js_enabled_(boolean v) 									{browser.setJavascriptEnabled(v);}
+	public void 		Html_js_cbks_add(String func_name, Gfo_invk invk) 				{new Swt_html_func(browser, func_name, invk);}
+	public String 		Html_js_eval_script(String script) 								{return Eval_script_as_str(script);}
+	public Object		Html_js_eval_script_as_obj(String script) 						{return Eval_script(script);}
+	public boolean 		Html_js_eval_proc_as_bool(String proc, Object... args) 			{return Bool_.Cast(Html_js_eval_proc_as_obj(proc, args));}
+	public String	Html_js_eval_proc_as_str(String proc, Object... args) {return Object_.Xto_str_strict_or_null(Html_js_eval_proc_as_obj(proc, args));}
+	public String Html_js_send_json(String name, String data) {
+		String script = String_.Format("return {0}('{1}');", name, String_.Replace(data, "\n", "") );
+		return (String)Eval_script(script);
+	}
+	private Object Html_js_eval_proc_as_obj(String proc, Object... args) {
+		Bry_bfr bfr = Bry_bfr_.New();
+		bfr.Add_str_a7("return ").Add_str_u8(proc).Add_byte(Byte_ascii.Paren_bgn);
+		int args_len = args.length;
+		for (int i = 0; i < args_len; ++i) {
+			Object arg = args[i];
+			if (i != 0) bfr.Add_byte(Byte_ascii.Comma);
+			boolean quote_val = true;
+			if 		(	Type_.Eq_by_obj(arg, Bool_.Cls_ref_type)
+					||	Type_.Eq_by_obj(arg, Int_.Cls_ref_type)
+					||	Type_.Eq_by_obj(arg, Long_.Cls_ref_type)
+				) {
+				quote_val = false;
+			}
+			if (quote_val) bfr.Add_byte(Byte_ascii.Apos);
+			if (quote_val) 
+				bfr.Add_str_u8(Escape_quote(Object_.Xto_str_strict_or_null_mark(arg)));
+			else
+				bfr.Add_obj_strict(arg);
+			if (quote_val) bfr.Add_byte(Byte_ascii.Apos);
+		}
+		bfr.Add_byte(Byte_ascii.Paren_end).Add_byte(Byte_ascii.Semic);
+		return Eval_script(bfr.To_str_and_clear());
+	}
+	public static String Escape_quote(String v) {
+		String rv = v;
+		rv = String_.Replace(rv, "'", "\\'");
+		rv = String_.Replace(rv, "\"", "\\\"");
+		rv = String_.Replace(rv, "\n", "\\n");
+		return rv;
+	}
+	public void Html_invk_src_(Gfo_evt_itm invk) {lnr_location.Host_set(invk); lnr_status.Host_set(invk);}
+	public void Html_dispose() {
+		browser.dispose();
+		delete_owner.SubElems().DelOrFail(delete_cur);	// NOTE: must delete cur from owner, else new tab will fail after closing one; DATE:2014-07-09
+		System_.Garbage_collect();
+	}
+	private GfuiElem delete_owner, delete_cur;
+	public void Delete_elems_(GfuiElem delete_owner, GfuiElem delete_cur) {this.delete_owner = delete_owner; this.delete_cur = delete_cur;}	// HACK: set owner / cur so delete can work;
+	@Override public GxwCore_base Core() {return core;} private GxwCore_base core;
+	@Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host;
+	@Override public String TextVal() {return browser.getText();}
+	@Override public void TextVal_set(String v) {browser.setText(v);}
+	@Override public void EnableDoubleBuffering() {}
+	@Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return Gfo_invk_.Rv_unhandled;}
+	private String Eval_script_as_str(String script) 	{return (String)Eval_script(script);}
+	public Object Eval_script(String script) {
+		eval_rslt.Clear();
+		try {
+			eval_rslt.Result_set(browser.evaluate(script));
+			return eval_rslt.Result();
+		}
+		catch (Exception e) {eval_rslt.Error_set(e.getMessage()); 				return eval_rslt.Error();}
+	}	private Swt_html_eval_rslt eval_rslt = new Swt_html_eval_rslt();
+	@Override public void focusGained(FocusEvent arg0) {}
+	@Override public void focusLost(FocusEvent arg0) {}
+	public static final int
+	  Browser_tid_none 		= SWT.NONE
+	, Browser_tid_mozilla 	= SWT.MOZILLA
+	, Browser_tid_webkit	= SWT.WEBKIT
+	;
+	private static final String URL_ABOUT_PREFIX = "about:";
+	public static String StripAboutFromUrl(String url) {
+		return String_.Has_at_bgn(url, URL_ABOUT_PREFIX)
+			? String_.Mid(url, URL_ABOUT_PREFIX.length())
+			: url;
+	}
+}
+class Swt_core_cmds_html extends Swt_core__basic {
+	public Swt_core_cmds_html(Swt_html html_box, Control control) {super(control);}
+	@Override public void Focus() {
+		if (Focus_able())
+			control.forceFocus();
+	}
+	@Override public void Select_exec() {
+		this.Focus();
+	}
+}
+class Swt_html_lnr_traverse implements TraverseListener {
+	public Swt_html_lnr_traverse(Swt_html html_box) {}
+	@Override public void keyTraversed(TraverseEvent arg0) {}
+}
+class Swt_html_lnr_title implements TitleListener {
+	private Swt_html html_box;
+	public Swt_html_lnr_title(Swt_html html_box) {this.html_box = html_box;}
+	@Override public void changed(TitleEvent ev) {
+		try {UsrDlg_.Instance.Note(ev.title);}		
+		catch (Exception e) {html_box.Kit().Ask_ok("xowa.swt.html_box", "title.fail", Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 
+	}
+}
+class Swt_html_func extends BrowserFunction {    
+	private final Gfo_invk invk;
+	private final Browser browser;
+    public Swt_html_func(Browser browser, String name, Gfo_invk invk) {
+        super (browser, name);
+        this.browser = browser;
+        this.invk = invk;
+    }
+    public Object function (Object[] args) {
+    	try {
+    		return gplx.gfui.controls.standards.Gfui_html.Js_args_exec(invk, args);
+    	}
+    	catch (Exception e) {
+    		String rv = Err_.Message_gplx_full(e);
+    		browser.execute("alert('" + Swt_html.Escape_quote(rv) + "')");
+    		return rv;
+    	}
+    }
+}
+class Swt_html_lnr_status implements StatusTextListener {
+	public Swt_html_lnr_status(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box;
+	public void Host_set(Gfo_evt_itm host) {this.host = host;} Gfo_evt_itm host;
+	@Override public void changed(StatusTextEvent ev) {
+		if (html_box.Kit().Kit_mode__term()) return;	// shutting down raises status changed events; ignore, else SWT exception thrown; DATE:2014-05-29 
+		String ev_text = ev.text;
+		ev_text = Swt_html.StripAboutFromUrl(ev_text); // 2020-09-19|ISSUE#:799|strip "about:/" from url due to SWT 4.16
+		String load_by_url_path = html_box.Load_by_url_path();
+		if (load_by_url_path != null) ev_text = String_.Replace(ev_text, load_by_url_path, "");	// remove "C:/xowa/tab_1.html"
+//		if (String_.Has(ev_text, "Loading [MathJax]")) return;	// suppress MathJax messages; // NOTE: disabled for 2.1 (which no longer outputs messages to status); DATE:2013-05-03
+		try {if (host != null) Gfo_evt_mgr_.Pub_obj(host, Gfui_html.Evt_link_hover, "v", ev_text);}
+		catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", "status.fail", Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 
+	}
+}
+class Swt_html_lnr_progress implements ProgressListener {
+	public Swt_html_lnr_progress(Swt_html html_box) {}
+	@Override public void changed(ProgressEvent arg0) {}
+	@Override public void completed(ProgressEvent arg0) {
+//		UsrDlg_._.Note("done");
+	}
+}
+class Swt_html_lnr_location implements LocationListener {
+	public Swt_html_lnr_location(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box;
+	public void Host_set(Gfo_evt_itm host) {this.host = host;} private Gfo_evt_itm host;
+	@Override public void changed(LocationEvent arg) 	{Pub_evt(arg, Gfui_html.Evt_location_changed);}
+	@Override public void changing(LocationEvent arg) 	{Pub_evt(arg, Gfui_html.Evt_location_changing);}
+	private void Pub_evt(LocationEvent arg, String evt) {		
+		String location = arg.location;
+
+		// location_changing fires once when page is loaded; ignore
+		if (String_.Eq(location, "about:blank")) {
+			return;
+		}
+
+		// webkit prefixes "about:blank" to anchors; causes TOC to fail when clicking on links; EX:about:blank#TOC1; DATE:2015-06-09
+		if (html_box.Browser_tid() == Swt_html.Browser_tid_webkit
+			&&	String_.Has_at_bgn(location, "about:blank")) {
+			location = String_.Mid(location, 11);	// 11 = "about:blank".length 
+		}
+
+		// 2020-09-19|ISSUE#:799|strip "about:/" from url due to SWT 4.16
+		location = Swt_html.StripAboutFromUrl(location);
+
+		// navigating to file://page.html will fire location event; ignore if url mode
+		if (html_box.Html_doc_html_load_tid() == Gxw_html_load_tid_.Tid_url
+			&& 	String_.Has_at_bgn(location, "file:")
+			&& 	String_.Has_at_end(location, ".html")
+			) {
+			return;
+		}
+
+		try {
+			Gfo_evt_mgr_.Pub_obj(host, evt, "v", location);
+			arg.doit = false; // cancel navigation event, else there will be an error when trying to go to invalid location
+		}
+		catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", evt, Err_.Message_gplx_full(e));}	// NOTE: must catch error or will cause app to lock; currently called inside displaySync 		
+	}
+}
+class Swt_html_lnr_mouse implements MouseListener {
+	private GxwElem elem; private Browser browser; private Swt_kit kit;
+	public Swt_html_lnr_mouse(GxwElem elem, Browser browser, Swt_kit kit) {this.elem = elem; this.browser = browser; this.kit = kit;}
+	@Override public void mouseDown(MouseEvent ev) {
+		if (Is_at_scrollbar_area()) return;
+		elem.Host().MouseDownCbk(XtoMouseData(ev));
+	}
+	@Override public void mouseUp(MouseEvent ev) {
+		if (Is_at_scrollbar_area()) return;
+		elem.Host().MouseUpCbk(XtoMouseData(ev));
+	}
+	private boolean Is_at_scrollbar_area() {
+		// WORKAROUND.SWT: SEE:NOTE_1:browser scrollbar and click  
+		Point browser_size = browser.getSize();
+		Point click_pos = kit.Swt_display().getCursorLocation();
+		return click_pos.x >= browser_size.x - 12;
+	}
+	@Override public void mouseDoubleClick(MouseEvent ev) {}
+	IptEvtDataMouse XtoMouseData(MouseEvent ev) {
+		IptMouseBtn btn = null;
+		switch (ev.button) {
+			case 1: btn = IptMouseBtn_.Left; break;
+			case 2: btn = IptMouseBtn_.Middle; break;
+			case 3: btn = IptMouseBtn_.Right; break;
+			case 4: btn = IptMouseBtn_.X1; break;
+			case 5: btn = IptMouseBtn_.X2; break;
+		}
+		return IptEvtDataMouse.new_(btn, IptMouseWheel_.None, ev.x, ev.y);		
+	}
+}
+class Swt_html_lnr_wheel implements MouseWheelListener {
+	private final Swt_html html_box;
+	public Swt_html_lnr_wheel(Swt_html html_box) {
+		this.html_box = html_box;
+	}
+	@Override public void mouseScrolled(MouseEvent evt) {
+		if (evt.stateMask == SWT.CTRL) {	// ctrl held;
+			Gfo_evt_mgr_.Pub_obj(html_box, Gfui_html.Evt_zoom_changed, "clicks_is_positive", evt.count > 0);
+		}
+	}	
+}
+//class Swt_open_window_listener implements OpenWindowListener {
+//	private final Swt_html html_box;
+//	public Swt_open_window_listener(Swt_html html_box) {this.html_box = html_box;}
+//	@Override public void open(WindowEvent arg0) {
+//		Tfds.Write();
+//	}	
+//}
+/*
+NOTE_1:browser scrollbar and click
+a click in the scrollbar area will raise a mouse-down/mouse-up event in content-editable mode
+. a click should be consumed by the scrollbar and not have any effect elsewhere on the window
+. instead, a click event is raised, and counted twice
+  1) for the scroll bar this will scroll the area.
+  2) for the window. if keyboard-focus is set on a link, then it will activate the link.
+
+swt does not expose any scrollbar information (visible, width), b/c the scrollbar is controlled by the underlying browser
+so, assume:
+. scrollbar is always present
+. scrollbar has arbitrary width (currently 12)
+. and discard if click is in this scrollbar area
+
+two issues still occur with the workaround
+1) even if the scrollbar is not present, any click on the right-hand edge of the screen will be ignored
+2) click -> hold -> move mouse over to left -> release; the mouse up should be absorbed, but it is not due to position of release   
+*/