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