1 /++
2 	A webview (based on [arsd.webview]) for minigui.
3 
4 	For now at least, to use this, you MUST have a [WebViewApp] in scope in main for the duration of your gui application.
5 
6 	Warning: CEF spams the current directory with a bunch of files and directories. You might want to run your program in a dedicated location.
7 
8 	History:
9 		Added November 5, 2021. NOT YET STABLE.
10 
11 	Examples:
12 	---
13 	/+ dub.sdl:
14 		name "web"
15 		dependency "arsd-official:minigui-webview" version="*"
16 	+/
17 
18 	import arsd.minigui;
19 	import arsd.minigui_addons.webview;
20 
21 	void main() {
22 		auto app = WebViewApp(null);
23 		auto window = new Window;
24 		auto webview = new WebViewWidget("http://dlang.org/", window);
25 		window.loop;
26 	}
27 	---
28 +/
29 module arsd.minigui_addons.webview;
30 // FIXME: i think i can download the cef automatically if needed.
31 
32 version(linux)
33 	version=cef;
34 version(Windows)
35 	version=wv2;
36 
37 /+
38 	SPA mode: put favicon on top level window, no other user controls at top level, links to different domains always open in new window.
39 +/
40 
41 // FIXME: look in /opt/cef for the dll and the locales
42 
43 import arsd.minigui;
44 import arsd.webview;
45 
46 version(wv2) {
47 	alias WebViewWidget = WebViewWidget_WV2;
48 	alias WebViewApp = Wv2App;
49 } else version(cef) {
50 	alias WebViewWidget = WebViewWidget_CEF;
51 	alias WebViewApp = CefApp;
52 } else static assert(0, "no webview available");
53 
54 class WebViewWidgetBase : NestedChildWindowWidget {
55 	protected SimpleWindow containerWindow;
56 
57 	protected this(Widget parent) {
58 		containerWindow = new SimpleWindow(640, 480, null, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
59 
60 		super(containerWindow, parent);
61 	}
62 
63 	mixin Observable!(string, "title");
64 	mixin Observable!(string, "url");
65 	mixin Observable!(string, "status");
66 
67 	// not implemented on WV2
68 	mixin Observable!(int, "loadingProgress");
69 
70 	// not implemented on WV2
71 	mixin Observable!(string, "favicon_url");
72 	mixin Observable!(MemoryImage, "favicon"); // please note it can be changed to null!
73 
74 	abstract void refresh();
75 	abstract void back();
76 	abstract void forward();
77 	abstract void stop();
78 
79 	abstract void navigate(string url);
80 
81 	// the url and line are for error reporting purposes. They might be ignored.
82 	// FIXME: add a callback with the reply. this can send a message from the js thread in cef and just ExecuteScript inWV2
83 	// FIXME: add AddScriptToExecuteOnDocumentCreated for cef....
84 	abstract void executeJavascript(string code, string url = null, int line = 0);
85 	// for injecting stuff into the context
86 	// abstract void executeJavascriptBeforeEachLoad(string code);
87 
88 	abstract void showDevTools();
89 
90 	/++
91 		Your communication consists of running Javascript and sending string messages back and forth,
92 		kinda similar to your communication with a web server.
93 	+/
94 	// these form your communcation channel between the web view and the native world
95 	// abstract void sendMessageToHost(string json);
96 	// void delegate(string json) receiveMessageFromHost;
97 
98 	/+
99 		I also need a url filter
100 	+/
101 
102 	// this is implemented as a do-nothing in the NestedChildWindowWidget base
103 	// but you will almost certainly need to override it in implementations.
104 	// abstract void registerMovementAdditionalWork();
105 }
106 
107 // AddScriptToExecuteOnDocumentCreated
108 
109 
110 
111 version(wv2)
112 class WebViewWidget_WV2 : WebViewWidgetBase {
113 	private RC!ICoreWebView2 webview_window;
114 	private RC!ICoreWebView2Environment webview_env;
115 	private RC!ICoreWebView2Controller controller;
116 
117 	private bool initialized;
118 
119 	this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) {
120 		// FIXME: openNewWindow
121 		super(parent);
122 		// that ctor sets containerWindow
123 
124 		Wv2App.useEnvironment((env) {
125 			env.CreateCoreWebView2Controller(containerWindow.impl.hwnd,
126 				callback!(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler)(delegate(error, controller_raw) {
127 					if(error || controller_raw is null)
128 						return error;
129 
130 					// need to keep this beyond the callback or we're doomed.
131 					controller = RC!ICoreWebView2Controller(controller_raw);
132 
133 					webview_window = controller.CoreWebView2;
134 
135 					webview_window.add_DocumentTitleChanged((sender, args) {
136 						this.title = toGC(&sender.get_DocumentTitle);
137 						return S_OK;
138 					});
139 
140 					// add_HistoryChanged
141 					// that's where CanGoBack and CanGoForward can be rechecked.
142 
143 					RC!ICoreWebView2Settings Settings = webview_window.Settings;
144 					Settings.IsScriptEnabled = TRUE;
145 					Settings.AreDefaultScriptDialogsEnabled = TRUE;
146 					Settings.IsWebMessageEnabled = TRUE;
147 
148 
149 					auto ert = webview_window.add_NavigationStarting(
150 						delegate (sender, args) {
151 							this.url = toGC(&args.get_Uri);
152 							return S_OK;
153 						});
154 
155 					RECT bounds;
156 					GetClientRect(containerWindow.impl.hwnd, &bounds);
157 					controller.Bounds = bounds;
158 					//error = webview_window.Navigate("http://arsdnet.net/test.html"w.ptr);
159 					//error = webview_window.NavigateToString("<html><body>Hello</body></html>"w.ptr);
160 					//error = webview_window.Navigate("http://192.168.1.10/"w.ptr);
161 
162 					WCharzBuffer bfr = WCharzBuffer(url);
163 					webview_window.Navigate(bfr.ptr);
164 
165 					controller.IsVisible = true;
166 
167 					initialized = true;
168 
169 					return S_OK;
170 				}));
171 			});
172 	}
173 
174 	override void registerMovementAdditionalWork() {
175 		if(initialized) {
176 			RECT bounds;
177 			GetClientRect(containerWindow.impl.hwnd, &bounds);
178 			controller.Bounds = bounds;
179 
180 			controller.NotifyParentWindowPositionChanged();
181 		}
182 	}
183 
184 	override void refresh() {
185 		if(!initialized) return;
186 		webview_window.Reload();
187 	}
188 	override void back() {
189 		if(!initialized) return;
190 		webview_window.GoBack();
191 	}
192 	override void forward() {
193 		if(!initialized) return;
194 		webview_window.GoForward();
195 	}
196 	override void stop() {
197 		if(!initialized) return;
198 		webview_window.Stop();
199 	}
200 
201 	override void navigate(string url) {
202 		if(!initialized) return;
203 		import std.utf;
204 		auto error = webview_window.Navigate(url.toUTF16z);
205 	}
206 
207 	// the url and line are for error reporting purposes
208 	override void executeJavascript(string code, string url = null, int line = 0) {
209 		if(!initialized) return;
210 		import std.utf;
211 		webview_window.ExecuteScript(code.toUTF16z, null);
212 	}
213 
214 	override void showDevTools() {
215 		if(!initialized) return;
216 		webview_window.OpenDevToolsWindow();
217 	}
218 }
219 
220 /++
221 	The openInNewWindow delegate is given these params.
222 
223 	To accept the new window, call
224 
225 	params.accept(parent_widget);
226 
227 	Please note, you can force it to replace the current tab
228 	by just navigating your current thing to the given url instead
229 	of accepting it.
230 
231 	If you accept with a null widget, it will create a new window
232 	but then return null, since the new window is managed by the
233 	underlying webview instead of by minigui.
234 
235 	If you do not call accept, the pop up will be blocked.
236 
237 	accept returns an instance to the newly created widget, which will
238 	be a parent to the widget you passed.
239 
240 	accept will be called from the gui thread and it MUST not call into
241 	any other webview methods. It should only create windows/widgets
242 	and set event handlers etc.
243 
244 	You MUST not escape references to anything in this structure. It
245 	is entirely strictly temporary!
246 +/
247 struct OpenNewWindowParams {
248 	string url;
249 	WebViewWidget delegate(Widget parent, BrowserSettings settings = BrowserSettings.init) accept;
250 }
251 
252 /++
253 	Represents a browser setting that can be left default or specifically turned on or off.
254 +/
255 struct SettingValue {
256 	private byte value = -1;
257 
258 	/++
259 		Set it with `= true` or `= false`.
260 	+/
261 	void opAssign(bool enable) {
262 		value = enable ? 1 : 0;
263 	}
264 
265 	/++
266 		And this resets it to the default value.
267 	+/
268 	void setDefault() {
269 		value = -1;
270 	}
271 
272 	/// If isDefault, it will use the default setting from the browser. Else, the getValue return value will be used. getValue is invalid if !isDefault.
273 	bool isDefault() {
274 		return value == -1;
275 	}
276 
277 	/// ditto
278 	bool getValue() {
279 		return value == 1;
280 	}
281 }
282 
283 /++
284 	Defines settings for a browser widget. Not all backends will respect all settings.
285 
286 	The order of members of this struct may change at any time. Refer to its members by
287 	name.
288 +/
289 struct BrowserSettings {
290 	/// This is just disabling the automatic positional constructor, since that is not stable here.
291 	this(typeof(null)) {}
292 
293 	string standardFontFamily;
294 	string fixedFontFamily;
295 	string serifFontFamily;
296 	string sansSerifFontFamily;
297 	string cursiveFontFamily;
298 	string fantasyFontFamily;
299 
300 	int defaultFontSize;
301 	int defaultFixedFontSize;
302 	int minimumFontSize;
303 	//int minimumLogicalFontSize;
304 
305 	SettingValue remoteFontsEnabled;
306 	SettingValue javascriptEnabled;
307 	SettingValue imagesEnabled;
308 	SettingValue clipboardAccessEnabled;
309 	SettingValue localStorageEnabled;
310 
311 	version(cef)
312 	private void set(cef_browser_settings_t* browser_settings) {
313 		alias settings = this;
314 		if(settings.standardFontFamily)
315 			browser_settings.standard_font_family = cef_string_t(settings.standardFontFamily);
316 		if(settings.fixedFontFamily)
317 			browser_settings.fixed_font_family = cef_string_t(settings.fixedFontFamily);
318 		if(settings.serifFontFamily)
319 			browser_settings.serif_font_family = cef_string_t(settings.serifFontFamily);
320 		if(settings.sansSerifFontFamily)
321 			browser_settings.sans_serif_font_family = cef_string_t(settings.sansSerifFontFamily);
322 		if(settings.cursiveFontFamily)
323 			browser_settings.cursive_font_family = cef_string_t(settings.cursiveFontFamily);
324 		if(settings.fantasyFontFamily)
325 			browser_settings.fantasy_font_family = cef_string_t(settings.fantasyFontFamily);
326 		if(settings.defaultFontSize)
327 			browser_settings.default_font_size = settings.defaultFontSize;
328 		if(settings.defaultFixedFontSize)
329 			browser_settings.default_fixed_font_size = settings.defaultFixedFontSize;
330 		if(settings.minimumFontSize)
331 			browser_settings.minimum_font_size = settings.minimumFontSize;
332 
333 		if(!settings.remoteFontsEnabled.isDefault())
334 			browser_settings.remote_fonts = settings.remoteFontsEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
335 		if(!settings.javascriptEnabled.isDefault())
336 			browser_settings.javascript = settings.javascriptEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
337 		if(!settings.imagesEnabled.isDefault())
338 			browser_settings.image_loading = settings.imagesEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
339 		if(!settings.clipboardAccessEnabled.isDefault())
340 			browser_settings.javascript_access_clipboard = settings.clipboardAccessEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
341 		if(!settings.localStorageEnabled.isDefault())
342 			browser_settings.local_storage = settings.localStorageEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
343 
344 	}
345 }
346 
347 version(cef)
348 class WebViewWidget_CEF : WebViewWidgetBase {
349 	/++
350 		Create a webview that does not support opening links in new windows and uses default settings to load the given url.
351 	+/
352 	this(string url, Widget parent) {
353 		this(url, null, BrowserSettings.init, parent);
354 	}
355 
356 	/++
357 		Full-featured constructor.
358 	+/
359 	this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) {
360 		//semaphore = new Semaphore;
361 		assert(CefApp.active);
362 
363 		this(new MiniguiCefClient(openNewWindow), parent, false);
364 
365 		cef_window_info_t window_info;
366 		window_info.parent_window = containerWindow.nativeWindowHandle;
367 
368 		cef_string_t cef_url = cef_string_t(url);//"http://arsdnet.net/test.html");
369 
370 		cef_browser_settings_t browser_settings;
371 		browser_settings.size = cef_browser_settings_t.sizeof;
372 
373 		settings.set(&browser_settings);
374 
375 		auto got = libcef.browser_host_create_browser(&window_info, client.passable, &cef_url, &browser_settings, null, null);
376 	}
377 
378 	/+
379 	~this() {
380 		import core.stdc.stdio;
381 		import core.memory;
382 		printf("CLEANUP %s\n", GC.inFinalizer ? "GC".ptr : "destroy".ptr);
383 	}
384 	+/
385 
386 	override void dispose() {
387 		// sdpyPrintDebugString("closed");
388 		// the window is already gone so too late to do this really....
389 		// if(browserHandle) browserHandle.get_host.close_browser(true);
390 
391 		// sdpyPrintDebugString("DISPOSE");
392 
393 		if(win && win.nativeWindowHandle())
394 			mapping.remove(win.nativeWindowHandle());
395 		if(browserWindow)
396 			browserMapping.remove(browserWindow);
397 
398 		.destroy(this); // but this is ok to do some memory management cleanup
399 	}
400 
401 	private this(MiniguiCefClient client, Widget parent, bool isDevTools) {
402 		super(parent);
403 
404 		this.client = client;
405 
406 		flushGui();
407 
408 		mapping[containerWindow.nativeWindowHandle()] = this;
409 
410 		this.addEventListener(delegate(KeyDownEvent ke) {
411 			if(ke.key == Key.Tab)
412 				ke.preventDefault();
413 		});
414 
415 		this.addEventListener((FocusEvent fe) {
416 			if(!browserHandle) return;
417 
418 			XFocusChangeEvent ev;
419 			ev.type = arsd.simpledisplay.EventType.FocusIn;
420 			ev.display = XDisplayConnection.get;
421 			ev.window = ozone;
422 			ev.mode = NotifyModes.NotifyNormal;
423 			ev.detail = NotifyDetail.NotifyVirtual;
424 
425 			trapXErrors( {
426 				XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
427 			});
428 
429 			// this also works if the message is buggy and it avoids weirdness from raising window etc
430 			//executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
431 		});
432 		this.addEventListener((BlurEvent be) {
433 			if(!browserHandle) return;
434 
435 			XFocusChangeEvent ev;
436 			ev.type = arsd.simpledisplay.EventType.FocusOut;
437 			ev.display = XDisplayConnection.get;
438 			ev.window = ozone;
439 			ev.mode = NotifyModes.NotifyNormal;
440 			ev.detail = NotifyDetail.NotifyNonlinearVirtual;
441 
442 			trapXErrors( {
443 				XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
444 			});
445 
446 			//executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
447 		});
448 
449 		bool closeAttempted = false;
450 
451 		if(isDevTools)
452 		this.parentWindow.addEventListener((scope ClosingEvent ce) {
453 			this.parentWindow.hide();
454 			ce.preventDefault();
455 		});
456 		else
457 		this.parentWindow.addEventListener((scope ClosingEvent ce) {
458 			if(devTools)
459 				devTools.close();
460 			if(!closeAttempted && browserHandle) {
461 				browserHandle.get_host.close_browser(true);
462 				ce.preventDefault();
463 				// sdpyPrintDebugString("closing");
464 			}
465 			closeAttempted = true;
466 		});
467 	}
468 
469 	private MiniguiCefClient client;
470 
471 	override void registerMovementAdditionalWork() {
472 		if(browserWindow) {
473 			static if(UsingSimpledisplayX11)
474 				XResizeWindow(XDisplayConnection.get, browserWindow, width, height);
475 			// FIXME: do for Windows too
476 		}
477 	}
478 
479 	SimpleWindow browserWindowWrapped;
480 	override SimpleWindow focusableWindow() {
481 		if(browserWindowWrapped is null && browserWindow)
482 			browserWindowWrapped = new SimpleWindow(browserWindow);
483 		return browserWindowWrapped;
484 	}
485 
486 	private NativeWindowHandle browserWindow;
487 	private NativeWindowHandle ozone;
488 	private RC!cef_browser_t browserHandle;
489 
490 	private static WebViewWidget[NativeWindowHandle] mapping;
491 	private static WebViewWidget[NativeWindowHandle] browserMapping;
492 
493 	private {
494 		int findingIdent;
495 		string findingText;
496 		bool findingCase;
497 	}
498 
499 	// might not be stable, webview does this fully integrated
500 	void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) {
501 		if(browserHandle) {
502 			auto host = browserHandle.get_host();
503 
504 			static ident = 0;
505 			auto txt = cef_string_t(text);
506 			host.find(++ident, &txt, forward, matchCase, findNext);
507 
508 			findingIdent = ident;
509 			findingText = text;
510 			findingCase = matchCase;
511 		}
512 	}
513 
514 	// ditto
515 	void findPrevious() {
516 		if(findingIdent == 0)
517 			return;
518 		if(!browserHandle)
519 			return;
520 		auto host = browserHandle.get_host();
521 		auto txt = cef_string_t(findingText);
522 		host.find(findingIdent, &txt, 0, findingCase, 1);
523 	}
524 
525 	// ditto
526 	void findNext() {
527 		if(findingIdent == 0)
528 			return;
529 		if(!browserHandle)
530 			return;
531 		auto host = browserHandle.get_host();
532 		auto txt = cef_string_t(findingText);
533 		host.find(findingIdent, &txt, 1, findingCase, 1);
534 	}
535 
536 	// ditto
537 	void stopFind() {
538 		if(findingIdent == 0)
539 			return;
540 		if(!browserHandle)
541 			return;
542 		auto host = browserHandle.get_host();
543 		host.stop_finding(1);
544 
545 		findingIdent = 0;
546 	}
547 
548 	override void refresh() { if(browserHandle) browserHandle.reload(); }
549 	override void back() { if(browserHandle) browserHandle.go_back(); }
550 	override void forward() { if(browserHandle) browserHandle.go_forward(); }
551 	override void stop() { if(browserHandle) browserHandle.stop_load(); }
552 
553 	override void navigate(string url) {
554 		if(!browserHandle) return;
555 		auto s = cef_string_t(url);
556 		browserHandle.get_main_frame.load_url(&s);
557 	}
558 
559 	// the url and line are for error reporting purposes
560 	override void executeJavascript(string code, string url = null, int line = 0) {
561 		if(!browserHandle) return;
562 
563 		auto c = cef_string_t(code);
564 		auto u = cef_string_t(url);
565 		browserHandle.get_main_frame.execute_java_script(&c, &u, line);
566 	}
567 
568 	private Window devTools;
569 	override void showDevTools() {
570 		if(!browserHandle) return;
571 
572 		if(devTools is null) {
573 			auto host = browserHandle.get_host;
574 
575 			if(host.has_dev_tools()) {
576 				host.close_dev_tools();
577 				return;
578 			}
579 
580 			cef_window_info_t windowinfo;
581 			version(linux) {
582 				auto sw = new Window("DevTools");
583 				//sw.win.beingOpenKeepsAppOpen = false;
584 				devTools = sw;
585 
586 				auto wv = new WebViewWidget_CEF(client, sw, true);
587 
588 				sw.show();
589 
590 				windowinfo.parent_window = wv.containerWindow.nativeWindowHandle;
591 			}
592 			host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */);
593 		} else {
594 			if(devTools.hidden)
595 				devTools.show();
596 			else
597 				devTools.hide();
598 		}
599 	}
600 
601 	// FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries.
602 
603 	// JS on init?
604 	// JS bindings?
605 	// user styles?
606 	// navigate to string? (can just use a data uri maybe?)
607 	// custom scheme handlers?
608 
609 	// navigation callbacks to prohibit certain things or move links to new window etc?
610 }
611 
612 version(cef) {
613 
614 	//import core.sync.semaphore;
615 	//__gshared Semaphore semaphore;
616 
617 	/+
618 		Finds the WebViewWidget associated with the given browser, then runs the given code in the gui thread on it.
619 	+/
620 	void runOnWebView(RC!cef_browser_t browser, void delegate(WebViewWidget) dg) nothrow {
621 		auto wh = cast(NativeWindowHandle) browser.get_host.get_window_handle;
622 
623 		import core.thread;
624 		try { thread_attachThis(); } catch(Exception e) {}
625 
626 		runInGuiThreadAsync({
627 			if(auto wvp = wh in WebViewWidget.browserMapping) {
628 				dg(*wvp);
629 			} else {
630 				//writeln("not found ", wh, WebViewWidget.browserMapping);
631 			}
632 		});
633 	}
634 
635 	class MiniguiCefLifeSpanHandler : CEF!cef_life_span_handler_t {
636 		private MiniguiCefClient client;
637 		this(MiniguiCefClient client) {
638 			this.client = client;
639 		}
640 
641 		override int on_before_popup(
642 			RC!cef_browser_t browser,
643 			RC!cef_frame_t frame,
644 			const(cef_string_t)* target_url,
645 			const(cef_string_t)* target_frame_name,
646 			cef_window_open_disposition_t target_disposition,
647 			int user_gesture,
648 			const(cef_popup_features_t)* popupFeatures,
649 			cef_window_info_t* windowInfo,
650 			cef_client_t** client,
651 			cef_browser_settings_t* browser_settings,
652 			cef_dictionary_value_t** extra_info,
653 			int* no_javascript_access
654 		) {
655 		sdpyPrintDebugString("on_before_popup");
656 			if(this.client.openNewWindow is null)
657 				return 1; // new windows disabled
658 
659 			try {
660 				int ret;
661 
662 				import core.thread;
663 				try { thread_attachThis(); } catch(Exception e) {}
664 
665 				// FIXME: change settings here
666 
667 				runInGuiThread({
668 					ret = 1;
669 					scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
670 						ret = 0;
671 						if(parent !is null) {
672 							auto widget = new WebViewWidget_CEF(this.client, parent, false);
673 							(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
674 
675 							passed_settings.set(browser_settings);
676 
677 							return widget;
678 						}
679 						return null;
680 					};
681 					this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept));
682 					return;
683 				});
684 
685 				return ret;
686 			} catch(Exception e) {
687 				return 1;
688 			}
689 			/+
690 			if(!user_gesture)
691 				return 1; // if not created by the user, cancel it; basic popup blocking
692 			+/
693 		}
694 		override void on_after_created(RC!cef_browser_t browser) {
695 			auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle();
696 			auto ptr = browser.passable; // this adds to the refcount until it gets inside
697 
698 			import core.thread;
699 			try { thread_attachThis(); } catch(Exception e) {}
700 
701 			// the only reliable key (at least as far as i can tell) is the window handle
702 			// so gonna look that up and do the sync mapping that way.
703 			runInGuiThreadAsync({
704 				version(Windows) {
705 					auto parent = GetParent(handle);
706 				} else static if(UsingSimpledisplayX11) {
707 					import arsd.simpledisplay : Window;
708 					Window root;
709 					Window parent;
710 					Window ozone;
711 					uint c = 0;
712 					auto display = XDisplayConnection.get;
713 					Window* children;
714 					XQueryTree(display, handle, &root, &parent, &children, &c);
715 					if(c == 1)
716 						ozone = children[0];
717 					XFree(children);
718 				} else static assert(0);
719 
720 				if(auto wvp = parent in WebViewWidget.mapping) {
721 					auto wv = *wvp;
722 					wv.browserWindow = handle;
723 					wv.browserHandle = RC!cef_browser_t(ptr);
724 					wv.ozone = ozone ? ozone : handle;
725 
726 					wv.browserWindowWrapped = new SimpleWindow(wv.ozone);
727 					/+
728 					XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask);
729 					
730 					wv.browserWindowWrapped.onFocusChange = (bool got) {
731 						import std.format;
732 						sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window));
733 					};
734 					+/
735 
736 					wv.registerMovementAdditionalWork();
737 
738 					WebViewWidget.browserMapping[handle] = wv;
739 				} else assert(0);
740 			});
741 		}
742 		override int do_close(RC!cef_browser_t browser) {
743 			browser.runOnWebView((wv) {
744 				auto bce = new BrowserClosedEvent(wv);
745 				bce.dispatch();
746 			});
747 			return 1;
748 		}
749 		override void on_before_close(RC!cef_browser_t browser) {
750 			/+
751 			import std.stdio; debug writeln("notify");
752 			try
753 			semaphore.notify;
754 			catch(Exception e) { assert(0); }
755 			+/
756 		}
757 	}
758 
759 	class MiniguiLoadHandler : CEF!cef_load_handler_t {
760 		override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) {
761 			/+
762 			browser.runOnWebView((WebViewWidget wvw) {
763 				wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree;
764 			});
765 			+/
766 		}
767 		override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) {
768 		}
769 		override void on_load_error(RC!(cef_browser_t), RC!(cef_frame_t), cef_errorcode_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) {
770 		}
771 		override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) {
772 		}
773 	}
774 
775 	class MiniguiDialogHandler : CEF!cef_dialog_handler_t {
776 
777 		override int on_file_dialog(RC!(cef_browser_t) browser, cef_file_dialog_mode_t mode, const(cef_string_utf16_t)* title, const(cef_string_utf16_t)* default_file_path, cef_string_list_t accept_filters, int selected_accept_filter, RC!(cef_file_dialog_callback_t) callback) {
778 			try {
779 				auto ptr = callback.passable();
780 				browser.runOnWebView((wv) {
781 					getOpenFileName((string name) {
782 						auto callback = RC!cef_file_dialog_callback_t(ptr);
783 						auto list = libcef.string_list_alloc();
784 						auto item = cef_string_t(name);
785 						libcef.string_list_append(list, &item);
786 						callback.cont(selected_accept_filter, list);
787 					}, null, null, () {
788 						auto callback = RC!cef_file_dialog_callback_t(ptr);
789 						callback.cancel();
790 					});
791 				});
792 			} catch(Exception e) {}
793 
794 			return 1;
795 		}
796 	}
797 
798 	class MiniguiDownloadHandler : CEF!cef_download_handler_t {
799 		override void on_before_download(
800 			RC!cef_browser_t browser,
801 			RC!cef_download_item_t download_item,
802 			const(cef_string_t)* suggested_name,
803 			RC!cef_before_download_callback_t callback
804 		) nothrow
805 		{
806 			// FIXME: different filename and check if exists for overwrite etc
807 			auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length]));
808 			sdpyPrintDebugString(fn.toGC);
809 			callback.cont(&fn, false);
810 		}
811 
812 		override void on_download_updated(
813 			RC!cef_browser_t browser,
814 			RC!cef_download_item_t download_item,
815 			RC!cef_download_item_callback_t cancel
816 		) nothrow
817 		{
818 			sdpyPrintDebugString(download_item.get_percent_complete());
819 			// FIXME
820 		}
821 	}
822 
823 	class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t {
824 		override int on_pre_key_event(
825 			RC!(cef_browser_t) browser,
826 			const(cef_key_event_t)* event,
827 			XEvent* osEvent,
828 			int* isShortcut
829 		) nothrow {
830 		/+
831 			sdpyPrintDebugString("---pre---");
832 			sdpyPrintDebugString(event.focus_on_editable_field);
833 			sdpyPrintDebugString(event.windows_key_code);
834 			sdpyPrintDebugString(event.modifiers);
835 			sdpyPrintDebugString(event.unmodified_character);
836 		+/
837 			//*isShortcut = 1;
838 			return 0; // 1 if handled, which cancels sending it to browser
839 		}
840 
841 		override int on_key_event(
842 			RC!(cef_browser_t) browser,
843 			const(cef_key_event_t)* event,
844 			XEvent* osEvent
845 		) nothrow {
846 		/+
847 			sdpyPrintDebugString("---key---");
848 			sdpyPrintDebugString(event.focus_on_editable_field);
849 			sdpyPrintDebugString(event.windows_key_code);
850 			sdpyPrintDebugString(event.modifiers);
851 		+/
852 			return 0; // 1 if handled
853 		}
854 	}
855 
856 	class MiniguiDisplayHandler : CEF!cef_display_handler_t {
857 		override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) {
858 			auto url = address.toGC;
859 			browser.runOnWebView((wv) {
860 				wv.url = url;
861 			});
862 		}
863 		override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) {
864 			auto t = title.toGC;
865 			browser.runOnWebView((wv) {
866 				wv.title = t;
867 			});
868 		}
869 		override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) {
870 			string url;
871 			auto size = libcef.string_list_size(urls);
872 			if(size > 0) {
873 				cef_string_t str;
874 				libcef.string_list_value(urls, 0, &str);
875 				url = str.toGC;
876 
877 				static class Thing : CEF!cef_download_image_callback_t {
878 					RC!cef_browser_t browserHandle;
879 					this(RC!cef_browser_t browser) nothrow {
880 						this.browserHandle = browser;
881 					}
882 					override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow {
883 						int width;
884 						int height;
885 						if(image.getRawPointer is null) {
886 							browserHandle.runOnWebView((wv) {
887 								wv.favicon = null;
888 							});
889 							return;
890 						}
891 
892 						auto data = image.get_as_bitmap(1.0, cef_color_type_t.CEF_COLOR_TYPE_RGBA_8888, cef_alpha_type_t.CEF_ALPHA_TYPE_POSTMULTIPLIED, &width, &height);
893 
894 						if(data.getRawPointer is null || width == 0 || height == 0) {
895 							browserHandle.runOnWebView((wv) {
896 								wv.favicon = null;
897 							});
898 						} else {
899 							auto s = data.get_size();
900 							auto buffer = new ubyte[](s);
901 							auto got = data.get_data(buffer.ptr, buffer.length, 0);
902 							auto slice = buffer[0 .. got];
903 
904 							auto img = new TrueColorImage (width, height, slice);
905 
906 							browserHandle.runOnWebView((wv) {
907 								wv.favicon = img;
908 							});
909 						}
910 					}
911 				}
912 
913 				if(url.length) {
914 					auto callback = new Thing(browser);
915 
916 					browser.get_host().download_image(&str, true, 16, 0, callback.passable);
917 				} else {
918 					browser.runOnWebView((wv) {
919 						wv.favicon = null;
920 					});
921 				}
922 			}
923 
924 			browser.runOnWebView((wv) {
925 				wv.favicon_url = url;
926 			});
927 		}
928 		override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) {
929 		}
930 		override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) {
931 			return 0;
932 		}
933 		override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) {
934 			auto status = msg.toGC;
935 			browser.runOnWebView((wv) {
936 				wv.status = status;
937 			});
938 		}
939 		override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) {
940 			// progress is from 0.0 to 1.0
941 			browser.runOnWebView((wv) {
942 				wv.loadingProgress = cast(int) (progress * 100);
943 			});
944 		}
945 		override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) {
946 			return 0; // 1 means to suppress it being automatically output
947 		}
948 		override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) {
949 			return 0;
950 		}
951 		override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) {
952 			return 0;
953 		}
954 	}
955 
956 	class MiniguiRequestHandler : CEF!cef_request_handler_t {
957 		override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow {
958 			return 0;
959 		}
960 		override int on_open_urlfrom_tab(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_string_utf16_t)*, cef_window_open_disposition_t, int) nothrow {
961 			return 0;
962 		}
963 		override cef_resource_request_handler_t* get_resource_request_handler(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int, const(cef_string_utf16_t)*, int*) nothrow {
964 			return null;
965 		}
966 		override int get_auth_credentials(RC!(cef_browser_t), const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, RC!(cef_auth_callback_t)) nothrow {
967 			// this is for http basic auth popup.....
968 			return 0;
969 		}
970 		override int on_quota_request(RC!(cef_browser_t), const(cef_string_utf16_t)*, long, RC!(cef_callback_t)) nothrow {
971 			return 0;
972 		}
973 		override int on_certificate_error(RC!(cef_browser_t), cef_errorcode_t, const(cef_string_utf16_t)*, RC!(cef_sslinfo_t), RC!(cef_callback_t)) nothrow {
974 			return 0;
975 		}
976 		override int on_select_client_certificate(RC!(cef_browser_t), int, const(cef_string_utf16_t)*, int, ulong, cef_x509certificate_t**, RC!(cef_select_client_certificate_callback_t)) nothrow {
977 			return 0;
978 		}
979 		override void on_plugin_crashed(RC!(cef_browser_t), const(cef_string_utf16_t)*) nothrow {
980 
981 		}
982 		override void on_render_view_ready(RC!(cef_browser_t) p) nothrow {
983 
984 		}
985 		override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow {
986 
987 		}
988 		override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow {
989 			browser.runOnWebView(delegate(wv) {
990 				wv.executeJavascript("console.log('here');");
991 			});
992 
993 		}
994 	}
995 
996 	class MiniguiFocusHandler : CEF!cef_focus_handler_t {
997 		override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
998 			browser.runOnWebView(delegate(wv) {
999 				Widget f;
1000 				if(next) {
1001 					f = Window.getFirstFocusable(wv.parentWindow);
1002 				} else {
1003 					foreach(w; &wv.parentWindow.focusableWidgets) {
1004 						if(w is wv)
1005 							break;
1006 						f = w;
1007 					}
1008 				}
1009 				if(f)
1010 					f.focus();
1011 			});
1012 		}
1013 		override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
1014 			/+
1015 			browser.runOnWebView((ev) {
1016 				ev.focus(); // even this can steal focus from other parts of my application!
1017 			});
1018 			+/
1019 			//sdpyPrintDebugString("setting");
1020 
1021 			return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
1022 			// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
1023 			// even though things work fine if i always cancel except
1024 			// it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh
1025 			// it also breaks its own pop up menus and drop down boxes to allow this! wtf
1026 		}
1027 		override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
1028 			browser.runOnWebView((ev) {
1029 				// this sometimes steals from the app too but it is relatively acceptable
1030 				// steals when i mouse in from the side of the window quickly, but still
1031 				// i want the minigui state to match so i'll allow it
1032 				ev.focus();
1033 			});
1034 		}
1035 	}
1036 
1037 	class MiniguiCefClient : CEF!cef_client_t {
1038 
1039 		void delegate(scope OpenNewWindowParams) openNewWindow;
1040 
1041 		MiniguiCefLifeSpanHandler lsh;
1042 		MiniguiLoadHandler loadHandler;
1043 		MiniguiDialogHandler dialogHandler;
1044 		MiniguiDisplayHandler displayHandler;
1045 		MiniguiDownloadHandler downloadHandler;
1046 		MiniguiKeyboardHandler keyboardHandler;
1047 		MiniguiFocusHandler focusHandler;
1048 		MiniguiRequestHandler requestHandler;
1049 		this(void delegate(scope OpenNewWindowParams) openNewWindow) {
1050 			this.openNewWindow = openNewWindow;
1051 			lsh = new MiniguiCefLifeSpanHandler(this);
1052 			loadHandler = new MiniguiLoadHandler();
1053 			dialogHandler = new MiniguiDialogHandler();
1054 			displayHandler = new MiniguiDisplayHandler();
1055 			downloadHandler = new MiniguiDownloadHandler();
1056 			keyboardHandler = new MiniguiKeyboardHandler();
1057 			focusHandler = new MiniguiFocusHandler();
1058 			requestHandler = new MiniguiRequestHandler();
1059 		}
1060 
1061 		override cef_audio_handler_t* get_audio_handler() {
1062 			return null;
1063 		}
1064 		override cef_context_menu_handler_t* get_context_menu_handler() {
1065 			return null;
1066 		}
1067 		override cef_dialog_handler_t* get_dialog_handler() {
1068 			return dialogHandler.returnable;
1069 		}
1070 		override cef_display_handler_t* get_display_handler() {
1071 			return displayHandler.returnable;
1072 		}
1073 		override cef_download_handler_t* get_download_handler() {
1074 			return downloadHandler.returnable;
1075 		}
1076 		override cef_drag_handler_t* get_drag_handler() {
1077 			return null;
1078 		}
1079 		override cef_find_handler_t* get_find_handler() {
1080 			return null;
1081 		}
1082 		override cef_focus_handler_t* get_focus_handler() {
1083 			return focusHandler.returnable;
1084 		}
1085 		override cef_jsdialog_handler_t* get_jsdialog_handler() {
1086 			// needed for alert etc.
1087 			return null;
1088 		}
1089 		override cef_keyboard_handler_t* get_keyboard_handler() {
1090 			// this can handle keyboard shortcuts etc
1091 			return keyboardHandler.returnable;
1092 		}
1093 		override cef_life_span_handler_t* get_life_span_handler() {
1094 			return lsh.returnable;
1095 		}
1096 		override cef_load_handler_t* get_load_handler() {
1097 			return loadHandler.returnable;
1098 		}
1099 		override cef_render_handler_t* get_render_handler() {
1100 			// this thing might work for an off-screen thing
1101 			// like to an image or to a video stream maybe
1102 			//
1103 			// might be useful to have it render here then send it over too for remote X sharing a process
1104 			return null;
1105 		}
1106 		override cef_request_handler_t* get_request_handler() {
1107 			return requestHandler.returnable;
1108 		}
1109 		override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
1110 			return 0; // return 1 if you can actually handle the message
1111 		}
1112 		override cef_frame_handler_t* get_frame_handler() nothrow {
1113 			return null;
1114 		}
1115 		override cef_print_handler_t* get_print_handler() nothrow {
1116 			return null;
1117 		}
1118 
1119 	}
1120 }
1121 
1122 class BrowserClosedEvent : Event {
1123 	enum EventString = "browserclosed";
1124 
1125 	this(Widget target) { super(EventString, target); }
1126 	override bool cancelable() const { return false; }
1127 }
1128 
1129 /+
1130 pragma(mangle, "_ZN12CefWindowX115FocusEv")
1131 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE")
1132 extern(C++)
1133 export void _ZN12CefWindowX115FocusEv() {
1134 	sdpyPrintDebugString("OVERRIDDEN");
1135 }
1136 +/