The OpenD Programming Language

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 		status text and favicon change notifications implemented on Windows WebView2 on December 16, 2023 (so long as the necessary api version is available, otherwise it will silently drop it).
12 
13 	Dependencies:
14 		Requires arsd.png on Windows for favicons, may require more in the future.
15 
16 	Examples:
17 	---
18 	/+ dub.sdl:
19 		name "web"
20 		dependency "arsd-official:minigui-webview" version="*"
21 	+/
22 
23 	import arsd.minigui;
24 	import arsd.minigui_addons.webview;
25 
26 	void main() {
27 		auto app = WebViewApp(null);
28 		auto window = new Window;
29 		auto webview = new WebViewWidget("http://dlang.org/", window);
30 		window.loop;
31 	}
32 	---
33 +/
34 module arsd.minigui_addons.webview;
35 // FIXME: i think i can download the cef automatically if needed.
36 
37 // want to add mute support
38 // https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_8?view=webview2-1.0.2210.55
39 
40 // javascript : AddScriptToExecuteOnDocumentCreated / cef_register_extension or https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md idk
41 // need a: post web message / on web message posted
42 
43 // and some magic reply to certain url schemes.
44 
45 // also want to make sure it can prefix http:// and such when typing in a url bar cross platform
46 
47 import arsd.core;
48 
49 version(linux)
50 	version=cef;
51 version(Windows)
52 	version=wv2;
53 
54 /+
55 	SPA mode: put favicon on top level window, no other user controls at top level, links to different domains always open in new window.
56 +/
57 
58 // FIXME: look in /opt/cef for the dll and the locales
59 
60 import arsd.minigui;
61 import arsd.webview;
62 
63 version(wv2) {
64 	alias WebViewWidget = WebViewWidget_WV2;
65 	alias WebViewApp = Wv2App;
66 } else version(cef) {
67 	alias WebViewWidget = WebViewWidget_CEF;
68 	alias WebViewApp = CefApp;
69 } else static assert(0, "no webview available");
70 
71 class WebViewWidgetBase : NestedChildWindowWidget {
72 	protected SimpleWindow containerWindow;
73 
74 	protected this(Widget parent) {
75 		containerWindow = new SimpleWindow(640, 480, null, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
76 		// import std.stdio; writefln("container window %d created", containerWindow.window);
77 
78 		super(containerWindow, parent);
79 	}
80 
81 	mixin Observable!(string, "title");
82 	mixin Observable!(string, "url");
83 	mixin Observable!(string, "status");
84 
85 	// not implemented on WV2
86 	mixin Observable!(int, "loadingProgress");
87 
88 	// not implemented on WV2
89 	mixin Observable!(string, "favicon_url");
90 	mixin Observable!(MemoryImage, "favicon"); // please note it can be changed to null!
91 
92 	abstract void refresh();
93 	abstract void back();
94 	abstract void forward();
95 	abstract void stop();
96 
97 	abstract void navigate(string url);
98 
99 	// the url and line are for error reporting purposes. They might be ignored.
100 	// FIXME: add a callback with the reply. this can send a message from the js thread in cef and just ExecuteScript inWV2
101 	// FIXME: add AddScriptToExecuteOnDocumentCreated for cef....
102 	abstract void executeJavascript(string code, string url = null, int line = 0);
103 	// for injecting stuff into the context
104 	// abstract void executeJavascriptBeforeEachLoad(string code);
105 
106 	abstract void showDevTools();
107 
108 	/++
109 		Your communication consists of running Javascript and sending string messages back and forth,
110 		kinda similar to your communication with a web server.
111 	+/
112 	// these form your communcation channel between the web view and the native world
113 	// abstract void sendMessageToHost(string json);
114 	// void delegate(string json) receiveMessageFromHost;
115 
116 	/+
117 		I also need a url filter
118 	+/
119 
120 	// this is implemented as a do-nothing in the NestedChildWindowWidget base
121 	// but you will almost certainly need to override it in implementations.
122 	// abstract void registerMovementAdditionalWork();
123 }
124 
125 // AddScriptToExecuteOnDocumentCreated
126 
127 
128 
129 version(wv2)
130 class WebViewWidget_WV2 : WebViewWidgetBase {
131 	private RC!ICoreWebView2 webview_window;
132 	// 12 introduces status bar
133 	// 15 introduces favicon notifications
134 	// 16 introduces printing
135 	private RC!ICoreWebView2_16 webview_window_ext_1;
136 	private RC!ICoreWebView2Environment webview_env;
137 	private RC!ICoreWebView2Controller controller;
138 
139 	private bool initialized;
140 
141 	private HRESULT initializeWithController(ICoreWebView2Controller controller_raw) {
142 
143 		// need to keep this beyond the callback or we're doomed.
144 		this.controller = RC!ICoreWebView2Controller(controller_raw);
145 
146 		this.webview_window = controller.CoreWebView2;
147 
148 		this.webview_window_ext_1 = this.webview_window.queryInterface!(ICoreWebView2_16);
149 
150 		bool enableStatusBar = true;
151 
152 		if(this.webview_window_ext_1) {
153 			enableStatusBar = false;
154 			this.webview_window_ext_1.add_StatusBarTextChanged!(typeof(this))((sender, args, this_) {
155 				this_.status = toGC(&this_.webview_window_ext_1.raw.get_StatusBarText);
156 				return S_OK;
157 			}, this);
158 
159 			webview_window_ext_1.add_FaviconChanged!(typeof(this))((sender, args, this_) {
160 				this_.webview_window_ext_1.GetFavicon(
161 					COREWEBVIEW2_FAVICON_IMAGE_FORMAT_PNG,
162 					callback!(ICoreWebView2GetFaviconCompletedHandler, typeof(this_))(function(error, streamPtrConst, ctx2) {
163 
164 						auto streamPtr = cast(IStream) streamPtrConst;
165 
166 						ubyte[] buffer = new ubyte[](640); // most favicons are pretty small
167 						enum growth_size = 1024; // and we'll grow linearly by the kilobyte
168 						size_t at;
169 
170 						more:
171 						ULONG actuallyRead;
172 						auto ret = streamPtr.Read(buffer.ptr + at, cast(UINT) (buffer.length - at), &actuallyRead);
173 						if(ret == S_OK) {
174 							// read completed, possibly more data pending
175 							auto moreData = actuallyRead >= (buffer.length - at);
176 
177 							at += actuallyRead;
178 							if(moreData && (buffer.length - at < growth_size))
179 								buffer.length += growth_size;
180 							goto more;
181 						} else if(ret == S_FALSE) {
182 							// end of file reached
183 							at += actuallyRead;
184 							buffer = buffer[0 .. at];
185 
186 							import arsd.png;
187 							ctx2.favicon = readPngFromBytes(buffer);
188 						} else {
189 							// other error
190 							throw new ComException(ret);
191 						}
192 
193 						return S_OK;
194 					}, this_)
195 				);
196 
197 				return S_OK;
198 			}, this);
199 		}
200 
201 		webview_window.add_DocumentTitleChanged!(typeof(this))((sender, args, this_) {
202 			this_.title = toGC(&sender.get_DocumentTitle);
203 			return S_OK;
204 		}, this);
205 
206 		webview_window.add_NewWindowRequested!(typeof(this))((sender, args, this_) {
207 			// args.get_Uri
208 			// args.get_IsUserInitiated
209 			// args.put_NewWindow();
210 
211 			string url = toGC(&args.get_Uri);
212 			int ret;
213 
214 			WebViewWidget_WV2 widget;
215 
216 			runInGuiThread({
217 				ret = 0;
218 
219 				scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
220 					ret = 1;
221 					if(parent !is null) {
222 						auto widget = new WebViewWidget_WV2(url, this_.openNewWindow, passed_settings, parent);
223 
224 						return widget;
225 					}
226 					return null;
227 				};
228 				this_.openNewWindow(OpenNewWindowParams(url, accept));
229 				return;
230 			});
231 
232 			if(ret) {
233 				args.put_Handled(true);
234 				// args.put_NewWindow(widget.webview_window.returnable);
235 			}
236 
237 			return S_OK;
238 
239 		}, this);
240 
241 		// add_HistoryChanged
242 		// that's where CanGoBack and CanGoForward can be rechecked.
243 
244 		RC!ICoreWebView2Settings Settings = this.webview_window.Settings;
245 		Settings.IsScriptEnabled = TRUE;
246 		Settings.AreDefaultScriptDialogsEnabled = TRUE;
247 		Settings.IsWebMessageEnabled = TRUE;
248 		Settings.IsStatusBarEnabled = enableStatusBar;
249 
250 		auto ert = this.webview_window.add_NavigationStarting!(typeof(this))(
251 			function (sender, args, this_) {
252 				this_.url = toGC(&args.get_Uri);
253 				return S_OK;
254 			}, this);
255 
256 		RECT bounds;
257 		GetClientRect(this.containerWindow.impl.hwnd, &bounds);
258 		controller.Bounds = bounds;
259 		//error = webview_window.Navigate("http://arsdnet.net/test.html"w.ptr);
260 		//error = webview_window.NavigateToString("<html><body>Hello</body></html>"w.ptr);
261 		//error = webview_window.Navigate("http://192.168.1.10/"w.ptr);
262 
263 		if(url !is null) {
264 			WCharzBuffer bfr = WCharzBuffer(url);
265 			this.webview_window.Navigate(bfr.ptr);
266 		}
267 
268 		controller.IsVisible = true;
269 
270 		this.initialized = true;
271 
272 		return S_OK;
273 	}
274 
275 	private void delegate(scope OpenNewWindowParams) openNewWindow;
276 
277 	this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) {
278 		this.openNewWindow = openNewWindow;
279 		super(parent);
280 		// that ctor sets containerWindow
281 
282 		this.url = url;
283 
284 		Wv2App.useEnvironment((env) {
285 			env.CreateCoreWebView2Controller(containerWindow.impl.hwnd,
286 				callback!(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, typeof(this))(function(error, controller_raw, ctx) {
287 					if(error || controller_raw is null)
288 						return error;
289 
290 					return ctx.initializeWithController(controller_raw);
291 				}, this));
292 		});
293 	}
294 
295 	override void registerMovementAdditionalWork() {
296 		if(initialized) {
297 			RECT bounds;
298 			GetClientRect(containerWindow.impl.hwnd, &bounds);
299 			controller.Bounds = bounds;
300 
301 			controller.NotifyParentWindowPositionChanged();
302 		}
303 	}
304 
305 	override void refresh() {
306 		if(!initialized) return;
307 		webview_window.Reload();
308 	}
309 	override void back() {
310 		if(!initialized) return;
311 		webview_window.GoBack();
312 	}
313 	override void forward() {
314 		if(!initialized) return;
315 		webview_window.GoForward();
316 	}
317 	override void stop() {
318 		if(!initialized) return;
319 		webview_window.Stop();
320 	}
321 
322 	override void navigate(string url) {
323 		if(!initialized) return;
324 		import std.utf;
325 		auto error = webview_window.Navigate(url.toUTF16z);
326 	}
327 
328 	// the url and line are for error reporting purposes
329 	override void executeJavascript(string code, string url = null, int line = 0) {
330 		if(!initialized) return;
331 		import std.utf;
332 		webview_window.ExecuteScript(code.toUTF16z, null);
333 	}
334 
335 	override void showDevTools() {
336 		if(!initialized) return;
337 		webview_window.OpenDevToolsWindow();
338 	}
339 }
340 
341 /++
342 	The openInNewWindow delegate is given these params.
343 
344 	To accept the new window, call
345 
346 	params.accept(parent_widget);
347 
348 	Please note, you can force it to replace the current tab
349 	by just navigating your current thing to the given url instead
350 	of accepting it.
351 
352 	If you accept with a null widget, it will create a new window
353 	but then return null, since the new window is managed by the
354 	underlying webview instead of by minigui.
355 
356 	If you do not call accept, the pop up will be blocked.
357 
358 	accept returns an instance to the newly created widget, which will
359 	be a parent to the widget you passed.
360 
361 	accept will be called from the gui thread and it MUST not call into
362 	any other webview methods. It should only create windows/widgets
363 	and set event handlers etc.
364 
365 	You MUST not escape references to anything in this structure. It
366 	is entirely strictly temporary!
367 +/
368 struct OpenNewWindowParams {
369 	string url;
370 	WebViewWidget delegate(Widget parent, BrowserSettings settings = BrowserSettings.init) accept;
371 }
372 
373 /++
374 	Represents a browser setting that can be left default or specifically turned on or off.
375 +/
376 struct SettingValue {
377 	private byte value = -1;
378 
379 	/++
380 		Set it with `= true` or `= false`.
381 	+/
382 	void opAssign(bool enable) {
383 		value = enable ? 1 : 0;
384 	}
385 
386 	/++
387 		And this resets it to the default value.
388 	+/
389 	void setDefault() {
390 		value = -1;
391 	}
392 
393 	/// If isDefault, it will use the default setting from the browser. Else, the getValue return value will be used. getValue is invalid if !isDefault.
394 	bool isDefault() {
395 		return value == -1;
396 	}
397 
398 	/// ditto
399 	bool getValue() {
400 		return value == 1;
401 	}
402 }
403 
404 /++
405 	Defines settings for a browser widget. Not all backends will respect all settings.
406 
407 	The order of members of this struct may change at any time. Refer to its members by
408 	name.
409 +/
410 struct BrowserSettings {
411 	/// This is just disabling the automatic positional constructor, since that is not stable here.
412 	this(typeof(null)) {}
413 
414 	string standardFontFamily;
415 	string fixedFontFamily;
416 	string serifFontFamily;
417 	string sansSerifFontFamily;
418 	string cursiveFontFamily;
419 	string fantasyFontFamily;
420 
421 	int defaultFontSize;
422 	int defaultFixedFontSize;
423 	int minimumFontSize;
424 	//int minimumLogicalFontSize;
425 
426 	SettingValue remoteFontsEnabled;
427 	SettingValue javascriptEnabled;
428 	SettingValue imagesEnabled;
429 	SettingValue clipboardAccessEnabled;
430 	SettingValue localStorageEnabled;
431 
432 	version(cef)
433 	private void set(cef_browser_settings_t* browser_settings) {
434 		alias settings = this;
435 		if(settings.standardFontFamily)
436 			browser_settings.standard_font_family = cef_string_t(settings.standardFontFamily);
437 		if(settings.fixedFontFamily)
438 			browser_settings.fixed_font_family = cef_string_t(settings.fixedFontFamily);
439 		if(settings.serifFontFamily)
440 			browser_settings.serif_font_family = cef_string_t(settings.serifFontFamily);
441 		if(settings.sansSerifFontFamily)
442 			browser_settings.sans_serif_font_family = cef_string_t(settings.sansSerifFontFamily);
443 		if(settings.cursiveFontFamily)
444 			browser_settings.cursive_font_family = cef_string_t(settings.cursiveFontFamily);
445 		if(settings.fantasyFontFamily)
446 			browser_settings.fantasy_font_family = cef_string_t(settings.fantasyFontFamily);
447 		if(settings.defaultFontSize)
448 			browser_settings.default_font_size = settings.defaultFontSize;
449 		if(settings.defaultFixedFontSize)
450 			browser_settings.default_fixed_font_size = settings.defaultFixedFontSize;
451 		if(settings.minimumFontSize)
452 			browser_settings.minimum_font_size = settings.minimumFontSize;
453 
454 		if(!settings.remoteFontsEnabled.isDefault())
455 			browser_settings.remote_fonts = settings.remoteFontsEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
456 		if(!settings.javascriptEnabled.isDefault())
457 			browser_settings.javascript = settings.javascriptEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
458 		if(!settings.imagesEnabled.isDefault())
459 			browser_settings.image_loading = settings.imagesEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
460 		if(!settings.clipboardAccessEnabled.isDefault())
461 			browser_settings.javascript_access_clipboard = settings.clipboardAccessEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
462 		if(!settings.localStorageEnabled.isDefault())
463 			browser_settings.local_storage = settings.localStorageEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED;
464 
465 	}
466 }
467 
468 version(cef)
469 class WebViewWidget_CEF : WebViewWidgetBase {
470 	/++
471 		Create a webview that does not support opening links in new windows and uses default settings to load the given url.
472 	+/
473 	this(string url, Widget parent) {
474 		this(url, null, BrowserSettings.init, parent);
475 	}
476 
477 	/++
478 		Full-featured constructor.
479 	+/
480 	this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) {
481 		//semaphore = new Semaphore;
482 		assert(CefApp.active);
483 
484 		this(new MiniguiCefClient(openNewWindow), parent, false);
485 
486 		cef_window_info_t window_info;
487 		window_info.parent_window = containerWindow.nativeWindowHandle;
488 
489 		cef_string_t cef_url = cef_string_t(url);//"http://arsdnet.net/test.html");
490 
491 		cef_browser_settings_t browser_settings;
492 		browser_settings.size = cef_browser_settings_t.sizeof;
493 
494 		settings.set(&browser_settings);
495 
496 		auto got = libcef.browser_host_create_browser(&window_info, client.passable, &cef_url, &browser_settings, null, null);
497 	}
498 
499 	/+
500 	~this() {
501 		import core.stdc.stdio;
502 		import core.memory;
503 		printf("CLEANUP %s\n", GC.inFinalizer ? "GC".ptr : "destroy".ptr);
504 	}
505 	+/
506 
507 	override void dispose() {
508 		// sdpyPrintDebugString("closed");
509 		// the window is already gone so too late to do this really....
510 		// if(browserHandle) browserHandle.get_host.close_browser(true);
511 
512 		// sdpyPrintDebugString("DISPOSE");
513 
514 		if(win && win.nativeWindowHandle())
515 			mapping.remove(win.nativeWindowHandle());
516 		if(browserWindow)
517 			browserMapping.remove(browserWindow);
518 
519 		.destroy(this); // but this is ok to do some memory management cleanup
520 	}
521 
522 	private this(MiniguiCefClient client, Widget parent, bool isDevTools) {
523 		super(parent);
524 
525 		this.client = client;
526 
527 		flushGui();
528 
529 		mapping[containerWindow.nativeWindowHandle()] = this;
530 
531 		this.addEventListener(delegate(KeyDownEvent ke) {
532 			if(ke.key == Key.Tab)
533 				ke.preventDefault();
534 		});
535 
536 		this.addEventListener((FocusEvent fe) {
537 			if(!browserHandle) return;
538 
539 			XFocusChangeEvent ev;
540 			ev.type = arsd.simpledisplay.EventType.FocusIn;
541 			ev.display = XDisplayConnection.get;
542 			ev.window = ozone;
543 			ev.mode = NotifyModes.NotifyNormal;
544 			ev.detail = NotifyDetail.NotifyVirtual;
545 
546 			trapXErrors( {
547 				XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
548 			});
549 
550 			// this also works if the message is buggy and it avoids weirdness from raising window etc
551 			//executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
552 		});
553 		this.addEventListener((BlurEvent be) {
554 			if(!browserHandle) return;
555 
556 			XFocusChangeEvent ev;
557 			ev.type = arsd.simpledisplay.EventType.FocusOut;
558 			ev.display = XDisplayConnection.get;
559 			ev.window = ozone;
560 			ev.mode = NotifyModes.NotifyNormal;
561 			ev.detail = NotifyDetail.NotifyNonlinearVirtual;
562 
563 			trapXErrors( {
564 				XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
565 			});
566 
567 			//executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
568 		});
569 
570 		bool closeAttempted = false;
571 
572 		if(isDevTools)
573 		this.parentWindow.addEventListener((scope ClosingEvent ce) {
574 			this.parentWindow.hide();
575 			ce.preventDefault();
576 		});
577 		else
578 		this.parentWindow.addEventListener((scope ClosingEvent ce) {
579 			if(devTools)
580 				devTools.close();
581 			if(browserHandle) {
582 				if(!closeAttempted) {
583 					closeAttempted = true;
584 					browserHandle.get_host.close_browser(false);
585 					ce.preventDefault();
586 				 	sdpyPrintDebugString("closing 1");
587 				} else {
588 					browserHandle.get_host.close_browser(true);
589 				 	sdpyPrintDebugString("closing 2");
590 				}
591 			}
592 		});
593 	}
594 
595 	~this() {
596 		import core.stdc.stdio;
597 		printf("GC'd %p\n", cast(void*) this);
598 	}
599 
600 	private MiniguiCefClient client;
601 
602 	override void registerMovementAdditionalWork() {
603 		if(browserWindow) {
604 			// import std.stdio; writeln("new size ", width, "x", height);
605 			static if(UsingSimpledisplayX11) {
606 				XResizeWindow(XDisplayConnection.get, browserWindow, width, height);
607 				if(ozone) XResizeWindow(XDisplayConnection.get, ozone, width, height);
608 			}
609 			// FIXME: do for Windows too
610 		}
611 	}
612 
613 	SimpleWindow browserHostWrapped;
614 	SimpleWindow browserWindowWrapped;
615 	override SimpleWindow focusableWindow() {
616 		if(browserWindowWrapped is null && browserWindow) {
617 			browserWindowWrapped = new SimpleWindow(browserWindow);
618 			// FIXME: this should never actually happen should it
619 		}
620 		return browserWindowWrapped;
621 	}
622 
623 	private NativeWindowHandle browserWindow;
624 	private NativeWindowHandle ozone;
625 	private RC!cef_browser_t browserHandle;
626 
627 	private static WebViewWidget[NativeWindowHandle] mapping;
628 	private static WebViewWidget[NativeWindowHandle] browserMapping;
629 
630 	private {
631 		string findingText;
632 		bool findingCase;
633 	}
634 
635 	// might not be stable, webview does this fully integrated
636 	void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) {
637 		if(browserHandle) {
638 			auto host = browserHandle.get_host();
639 
640 			auto txt = cef_string_t(text);
641 			host.find(&txt, forward, matchCase, findNext);
642 
643 			findingText = text;
644 			findingCase = matchCase;
645 		}
646 	}
647 
648 	// ditto
649 	void findPrevious() {
650 		if(!browserHandle)
651 			return;
652 		auto host = browserHandle.get_host();
653 		auto txt = cef_string_t(findingText);
654 		host.find(&txt, 0, findingCase, 1);
655 	}
656 
657 	// ditto
658 	void findNext() {
659 		if(!browserHandle)
660 			return;
661 		auto host = browserHandle.get_host();
662 		auto txt = cef_string_t(findingText);
663 		host.find(&txt, 1, findingCase, 1);
664 	}
665 
666 	// ditto
667 	void stopFind() {
668 		if(!browserHandle)
669 			return;
670 		auto host = browserHandle.get_host();
671 		host.stop_finding(1);
672 	}
673 
674 	override void refresh() { if(browserHandle) browserHandle.reload(); }
675 	override void back() { if(browserHandle) browserHandle.go_back(); }
676 	override void forward() { if(browserHandle) browserHandle.go_forward(); }
677 	override void stop() { if(browserHandle) browserHandle.stop_load(); }
678 
679 	override void navigate(string url) {
680 		if(!browserHandle) return;
681 		auto s = cef_string_t(url);
682 		browserHandle.get_main_frame.load_url(&s);
683 	}
684 
685 	// the url and line are for error reporting purposes
686 	override void executeJavascript(string code, string url = null, int line = 0) {
687 		if(!browserHandle) return;
688 
689 		auto c = cef_string_t(code);
690 		auto u = cef_string_t(url);
691 		browserHandle.get_main_frame.execute_java_script(&c, &u, line);
692 	}
693 
694 	private Window devTools;
695 	override void showDevTools() {
696 		if(!browserHandle) return;
697 
698 		if(devTools is null) {
699 			auto host = browserHandle.get_host;
700 
701 			if(host.has_dev_tools()) {
702 				host.close_dev_tools();
703 				return;
704 			}
705 
706 			cef_window_info_t windowinfo;
707 			version(linux) {
708 				auto sw = new Window("DevTools");
709 				//sw.win.beingOpenKeepsAppOpen = false;
710 				devTools = sw;
711 
712 				auto wv = new WebViewWidget_CEF(client, sw, true);
713 
714 				sw.show();
715 
716 				windowinfo.parent_window = wv.containerWindow.nativeWindowHandle;
717 			}
718 			host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */);
719 		} else {
720 			if(devTools.hidden)
721 				devTools.show();
722 			else
723 				devTools.hide();
724 		}
725 	}
726 
727 	// FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries.
728 
729 	// JS on init?
730 	// JS bindings?
731 	// user styles?
732 	// navigate to string? (can just use a data uri maybe?)
733 	// custom scheme handlers?
734 
735 	// navigation callbacks to prohibit certain things or move links to new window etc?
736 }
737 
738 version(cef) {
739 
740 	//import core.sync.semaphore;
741 	//__gshared Semaphore semaphore;
742 
743 	/+
744 		Finds the WebViewWidget associated with the given browser, then runs the given code in the gui thread on it.
745 	+/
746 	void runOnWebView(RC!cef_browser_t browser, void delegate(WebViewWidget) dg) nothrow {
747 		auto wh = cast(NativeWindowHandle) browser.get_host.get_window_handle;
748 
749 		import core.thread;
750 		try { thread_attachThis(); } catch(Exception e) {}
751 
752 		runInGuiThreadAsync({
753 			if(auto wvp = wh in WebViewWidget.browserMapping) {
754 				dg(*wvp);
755 			} else {
756 				writeln("not found ", wh, WebViewWidget.browserMapping);
757 			}
758 		});
759 	}
760 
761 	class MiniguiCefLifeSpanHandler : CEF!cef_life_span_handler_t {
762 		private MiniguiCefClient client;
763 		this(MiniguiCefClient client) {
764 			this.client = client;
765 		}
766 
767 		override void on_before_dev_tools_popup(RC!(cef_browser_t), cef_window_info_t*, cef_client_t**, cef_browser_settings_t*, cef_dictionary_value_t**, int*) nothrow {
768 
769 		}
770 
771 		override int on_before_popup(
772 			RC!cef_browser_t browser,
773 			RC!cef_frame_t frame,
774 			const(cef_string_t)* target_url,
775 			const(cef_string_t)* target_frame_name,
776 			cef_window_open_disposition_t target_disposition,
777 			int user_gesture,
778 			const(cef_popup_features_t)* popupFeatures,
779 			cef_window_info_t* windowInfo,
780 			cef_client_t** client,
781 			cef_browser_settings_t* browser_settings,
782 			cef_dictionary_value_t** extra_info,
783 			int* no_javascript_access
784 		) {
785 		sdpyPrintDebugString("on_before_popup");
786 			if(this.client.openNewWindow is null)
787 				return 1; // new windows disabled
788 
789 			try {
790 				int ret;
791 
792 				import core.thread;
793 				try { thread_attachThis(); } catch(Exception e) {}
794 
795 				// FIXME: change settings here
796 
797 				runInGuiThread({
798 					ret = 1;
799 					scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
800 						ret = 0;
801 						if(parent !is null) {
802 							auto widget = new WebViewWidget_CEF(this.client, parent, false);
803 							(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
804 
805 							passed_settings.set(browser_settings);
806 
807 							return widget;
808 						}
809 						return null;
810 					};
811 					this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept));
812 					return;
813 				});
814 
815 				return ret;
816 			} catch(Exception e) {
817 				return 1;
818 			}
819 			/+
820 			if(!user_gesture)
821 				return 1; // if not created by the user, cancel it; basic popup blocking
822 			+/
823 		}
824 		override void on_after_created(RC!cef_browser_t browser) {
825 			auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle();
826 			auto ptr = browser.passable; // this adds to the refcount until it gets inside
827 
828 			import core.thread;
829 			try { thread_attachThis(); } catch(Exception e) {}
830 
831 			// the only reliable key (at least as far as i can tell) is the window handle
832 			// so gonna look that up and do the sync mapping that way.
833 			runInGuiThreadAsync({
834 				version(Windows) {
835 					auto parent = GetParent(handle);
836 				} else static if(UsingSimpledisplayX11) {
837 					import arsd.simpledisplay : Window;
838 					Window root;
839 					Window parent;
840 					Window ozone;
841 					uint c = 0;
842 					auto display = XDisplayConnection.get;
843 					Window* children;
844 					XQueryTree(display, handle, &root, &parent, &children, &c);
845 					if(c == 1)
846 						ozone = children[0];
847 					XFree(children);
848 				} else static assert(0);
849 
850 				if(auto wvp = parent in WebViewWidget.mapping) {
851 					auto wv = *wvp;
852 					wv.browserWindow = handle;
853 					wv.browserHandle = RC!cef_browser_t(ptr);
854 					wv.ozone = ozone ? ozone : handle;
855 
856 					wv.browserHostWrapped = new SimpleWindow(handle);
857 					// XSelectInput(XDisplayConnection.get, handle, EventMask.StructureNotifyMask);
858 
859 					wv.browserHostWrapped.onDestroyed = delegate{
860 						import std.stdio; writefln("browser host %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow);
861 
862 						auto bce = new BrowserClosedEvent(wv);
863 						bce.dispatch();
864 					};
865 
866 					// need this to forward key events to
867 					wv.browserWindowWrapped = new SimpleWindow(wv.ozone);
868 
869 					/+
870 					XSelectInput(XDisplayConnection.get, wv.ozone, EventMask.StructureNotifyMask);
871 					wv.browserWindowWrapped.onDestroyed = delegate{
872 						import std.stdio; writefln("browser core %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow);
873 
874 						//auto bce = new BrowserClosedEvent(wv);
875 						//bce.dispatch();
876 					};
877 					+/
878 
879 					/+
880 					XSelectInput(XDisplayConnection.get, ozone, EventMask.FocusChangeMask);
881 					wv.browserWindowWrapped.onFocusChange = (bool got) {
882 						import std.format;
883 						sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window));
884 					};
885 					+/
886 
887 					wv.registerMovementAdditionalWork();
888 
889 					WebViewWidget.browserMapping[handle] = wv;
890 				} else assert(0);
891 			});
892 		}
893 		override int do_close(RC!cef_browser_t browser) {
894 						import std.stdio;
895 						debug writeln("do_close");
896 			/+
897 			browser.runOnWebView((wv) {
898 				wv.browserWindowWrapped.close();
899 				.destroy(wv.browserHandle);
900 			});
901 
902 			return 1;
903 			+/
904 
905 			return 0;
906 		}
907 		override void on_before_close(RC!cef_browser_t browser) {
908 			import std.stdio; debug writeln("notify");
909 			browser.runOnWebView((wv) {
910 				.destroy(wv.browserHandle);
911 			});
912 			/+
913 			try
914 			semaphore.notify;
915 			catch(Exception e) { assert(0); }
916 			+/
917 		}
918 	}
919 
920 	class MiniguiLoadHandler : CEF!cef_load_handler_t {
921 		override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) {
922 			/+
923 			browser.runOnWebView((WebViewWidget wvw) {
924 				wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree;
925 			});
926 			+/
927 		}
928 		override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) {
929 		}
930 		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)*) {
931 		}
932 		override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) {
933 		}
934 	}
935 
936 	class MiniguiDialogHandler : CEF!cef_dialog_handler_t {
937 		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,
938 			cef_string_list_t accept_filters,
939 			cef_string_list_t accept_extensions,
940 			cef_string_list_t accept_descriptions,
941 			RC!(cef_file_dialog_callback_t) callback)
942 		{
943 			try {
944 				auto ptr = callback.passable();
945 				browser.runOnWebView((wv) {
946 					getOpenFileName((string name) {
947 						auto callback = RC!cef_file_dialog_callback_t(ptr);
948 						auto list = libcef.string_list_alloc();
949 						auto item = cef_string_t(name);
950 						libcef.string_list_append(list, &item);
951 						callback.cont(list);
952 					}, null, null, () {
953 						auto callback = RC!cef_file_dialog_callback_t(ptr);
954 						callback.cancel();
955 					}, "/home/me/");
956 				});
957 			} catch(Exception e) {}
958 
959 			return 1;
960 		}
961 	}
962 
963 	class MiniguiDownloadHandler : CEF!cef_download_handler_t {
964 		override int on_before_download(
965 			RC!cef_browser_t browser,
966 			RC!cef_download_item_t download_item,
967 			const(cef_string_t)* suggested_name,
968 			RC!cef_before_download_callback_t callback
969 		) nothrow
970 		{
971 			// FIXME: different filename and check if exists for overwrite etc
972 			auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length]));
973 			sdpyPrintDebugString(fn.toGC);
974 			callback.cont(&fn, false);
975 
976 			return 1;
977 		}
978 
979 		override void on_download_updated(
980 			RC!cef_browser_t browser,
981 			RC!cef_download_item_t download_item,
982 			RC!cef_download_item_callback_t cancel
983 		) nothrow
984 		{
985 			sdpyPrintDebugString(download_item.get_percent_complete());
986 			// FIXME
987 		}
988 
989 		override int can_download(RC!(cef_browser_t), const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) {
990 			return 1;
991 		}
992 	}
993 
994 	class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t {
995 		override int on_pre_key_event(
996 			RC!(cef_browser_t) browser,
997 			const(cef_key_event_t)* event,
998 			XEvent* osEvent,
999 			int* isShortcut
1000 		) nothrow {
1001 		/+
1002 			sdpyPrintDebugString("---pre---");
1003 			sdpyPrintDebugString(event.focus_on_editable_field);
1004 			sdpyPrintDebugString(event.windows_key_code);
1005 			sdpyPrintDebugString(event.modifiers);
1006 			sdpyPrintDebugString(event.unmodified_character);
1007 		+/
1008 			//*isShortcut = 1;
1009 			return 0; // 1 if handled, which cancels sending it to browser
1010 		}
1011 
1012 		override int on_key_event(
1013 			RC!(cef_browser_t) browser,
1014 			const(cef_key_event_t)* event,
1015 			XEvent* osEvent
1016 		) nothrow {
1017 		/+
1018 			sdpyPrintDebugString("---key---");
1019 			sdpyPrintDebugString(event.focus_on_editable_field);
1020 			sdpyPrintDebugString(event.windows_key_code);
1021 			sdpyPrintDebugString(event.modifiers);
1022 		+/
1023 			return 0; // 1 if handled
1024 		}
1025 	}
1026 
1027 	class MiniguiDisplayHandler : CEF!cef_display_handler_t {
1028 		override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) {
1029 			auto url = address.toGC;
1030 			browser.runOnWebView((wv) {
1031 				wv.url = url;
1032 			});
1033 		}
1034 		override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) {
1035 			auto t = title.toGC;
1036 			browser.runOnWebView((wv) {
1037 				wv.title = t;
1038 			});
1039 		}
1040 		override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) {
1041 			string url;
1042 			auto size = libcef.string_list_size(urls);
1043 			if(size > 0) {
1044 				cef_string_t str;
1045 				libcef.string_list_value(urls, 0, &str);
1046 				url = str.toGC;
1047 
1048 				static class Thing : CEF!cef_download_image_callback_t {
1049 					RC!cef_browser_t browserHandle;
1050 					this(RC!cef_browser_t browser) nothrow {
1051 						this.browserHandle = browser;
1052 					}
1053 					override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow {
1054 						int width;
1055 						int height;
1056 						if(image.getRawPointer is null) {
1057 							browserHandle.runOnWebView((wv) {
1058 								wv.favicon = null;
1059 							});
1060 							return;
1061 						}
1062 
1063 						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);
1064 
1065 						if(data.getRawPointer is null || width == 0 || height == 0) {
1066 							browserHandle.runOnWebView((wv) {
1067 								wv.favicon = null;
1068 							});
1069 						} else {
1070 							auto s = data.get_size();
1071 							auto buffer = new ubyte[](s);
1072 							auto got = data.get_data(buffer.ptr, buffer.length, 0);
1073 							auto slice = buffer[0 .. got];
1074 
1075 							auto img = new TrueColorImage (width, height, slice);
1076 
1077 							browserHandle.runOnWebView((wv) {
1078 								wv.favicon = img;
1079 							});
1080 						}
1081 					}
1082 				}
1083 
1084 				if(url.length) {
1085 					auto callback = new Thing(browser);
1086 
1087 					browser.get_host().download_image(&str, true, 16, 0, callback.passable);
1088 				} else {
1089 					browser.runOnWebView((wv) {
1090 						wv.favicon = null;
1091 					});
1092 				}
1093 			}
1094 
1095 			browser.runOnWebView((wv) {
1096 				wv.favicon_url = url;
1097 			});
1098 		}
1099 		override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) {
1100 		}
1101 		override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) {
1102 			return 0;
1103 		}
1104 		override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) {
1105 			auto status = msg.toGC;
1106 			browser.runOnWebView((wv) {
1107 				wv.status = status;
1108 			});
1109 		}
1110 		override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) {
1111 			// progress is from 0.0 to 1.0
1112 			browser.runOnWebView((wv) {
1113 				wv.loadingProgress = cast(int) (progress * 100);
1114 			});
1115 		}
1116 		override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) {
1117 			return 1; // 1 means to suppress it being automatically output
1118 		}
1119 		override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) {
1120 			return 0;
1121 		}
1122 		override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) {
1123 			return 0;
1124 		}
1125 		override void on_media_access_change(RC!(cef_browser_t), int, int) {
1126 
1127 		}
1128 	}
1129 
1130 	class MiniguiRequestHandler : CEF!cef_request_handler_t {
1131 
1132 		override int on_render_process_unresponsive(RC!(cef_browser_t), RC!(cef_unresponsive_process_callback_t)) nothrow {
1133 			return 0;
1134 		}
1135 		override void on_render_process_responsive(RC!(cef_browser_t) p) nothrow {
1136 
1137 		}
1138 
1139 		override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow {
1140 			return 0;
1141 		}
1142 		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 {
1143 			return 0;
1144 		}
1145 		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 {
1146 			return null;
1147 		}
1148 		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 {
1149 			// this is for http basic auth popup.....
1150 			return 0;
1151 		}
1152 		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 {
1153 			return 0;
1154 		}
1155 		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 {
1156 			return 0;
1157 		}
1158 		override void on_render_view_ready(RC!(cef_browser_t) p) nothrow {
1159 
1160 		}
1161 		override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t, int error_code, const(cef_string_utf16_t)*) nothrow {
1162 
1163 		}
1164 		override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow {
1165 			browser.runOnWebView(delegate(wv) {
1166 				wv.executeJavascript("console.log('here');");
1167 			});
1168 
1169 		}
1170 	}
1171 
1172 	class MiniguiContextMenuHandler : CEF!cef_context_menu_handler_t {
1173 		private MiniguiCefClient client;
1174 		this(MiniguiCefClient client) {
1175 			this.client = client;
1176 		}
1177 
1178 		override void on_before_context_menu(RC!(cef_browser_t) browser, RC!(cef_frame_t) frame, RC!(cef_context_menu_params_t) params, RC!(cef_menu_model_t) model) nothrow {
1179 			// FIXME: should i customize these? it is kinda specific to my browser
1180 			int itemNo;
1181 
1182 			void addItem(string label, int commandNo) {
1183 				auto lbl = cef_string_t(label);
1184 				model.insert_item_at(/* index */ itemNo, /* command id */ cef_menu_id_t.MENU_ID_USER_FIRST + commandNo, &lbl);
1185 				itemNo++;
1186 			}
1187 
1188 			void addSeparator() {
1189 				model.insert_separator_at(itemNo);
1190 				itemNo++;
1191 			}
1192 
1193 			auto flags = params.get_type_flags();
1194 
1195 			if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_LINK) {
1196 				// cef_string_userfree_t linkUrl = params.get_unfiltered_link_url();
1197 				// toGCAndFree
1198 				addItem("Open link in new window", 1);
1199 				addItem("Copy link URL", 2);
1200 
1201 				// FIXME: open in other browsers
1202 				// FIXME: open in ytv
1203 				addSeparator();
1204 			}
1205 
1206 			if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_MEDIA) {
1207 				// cef_string_userfree_t linkUrl = params.get_source_url();
1208 				// toGCAndFree
1209 				addItem("Open media in new window", 3);
1210 				addItem("Copy source URL", 4);
1211 				addItem("Download media", 5);
1212 				addSeparator();
1213 			}
1214 
1215 
1216 			// get_page_url
1217 			// get_title_text
1218 			// has_image_contents ???
1219 			// get_source_url
1220 			// get_xcoord and get_ycoord
1221 			// get_selection_text
1222 
1223 		}
1224 		override int run_context_menu(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_context_menu_params_t), RC!(cef_menu_model_t), RC!(cef_run_context_menu_callback_t)) nothrow {
1225 			// could do a custom display here if i want but i think it is good enough as it is
1226 			return 0;
1227 		}
1228 		override int on_context_menu_command(RC!(cef_browser_t) browser, RC!(cef_frame_t) frame, RC!(cef_context_menu_params_t) params, int commandId, cef_event_flags_t flags) nothrow {
1229 			switch(commandId) {
1230 				case cef_menu_id_t.MENU_ID_USER_FIRST + 1: // open link in new window
1231 					auto what = params.get_unfiltered_link_url().toGCAndFree();
1232 
1233 					browser.runOnWebView((widget) {
1234 						auto event = new NewWindowRequestedEvent(what, widget);
1235 						event.dispatch();
1236 					});
1237 					return 1;
1238 				case cef_menu_id_t.MENU_ID_USER_FIRST + 2: // copy link url
1239 					auto what = params.get_link_url().toGCAndFree();
1240 
1241 					browser.runOnWebView((widget) {
1242 						auto event = new CopyRequestedEvent(what, widget);
1243 						event.dispatch();
1244 					});
1245 					return 1;
1246 				case cef_menu_id_t.MENU_ID_USER_FIRST + 3: // open media in new window
1247 					auto what = params.get_source_url().toGCAndFree();
1248 
1249 					browser.runOnWebView((widget) {
1250 						auto event = new NewWindowRequestedEvent(what, widget);
1251 						event.dispatch();
1252 					});
1253 					return 1;
1254 				case cef_menu_id_t.MENU_ID_USER_FIRST + 4: // copy source url
1255 					auto what = params.get_source_url().toGCAndFree();
1256 
1257 					browser.runOnWebView((widget) {
1258 						auto event = new CopyRequestedEvent(what, widget);
1259 						event.dispatch();
1260 					});
1261 					return 1;
1262 				case cef_menu_id_t.MENU_ID_USER_FIRST + 5: // download media
1263 					auto str = cef_string_t(params.get_source_url().toGCAndFree());
1264 					browser.get_host().start_download(&str);
1265 					return 1;
1266 				default:
1267 					return 0;
1268 			}
1269 		}
1270 		override void on_context_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow {
1271 			// to close the custom display
1272 		}
1273 
1274 		override int run_quick_menu(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_point_t)*, const(cef_size_t)*, cef_quick_menu_edit_state_flags_t, RC!(cef_run_quick_menu_callback_t)) nothrow {
1275 			return 0;
1276 		}
1277 		override int on_quick_menu_command(RC!(cef_browser_t), RC!(cef_frame_t), int, cef_event_flags_t) nothrow {
1278 			return 0;
1279 		}
1280 		override void on_quick_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow {
1281 
1282 		}
1283 	}
1284 
1285 	class MiniguiFocusHandler : CEF!cef_focus_handler_t {
1286 		override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
1287 			browser.runOnWebView(delegate(wv) {
1288 				Widget f;
1289 				if(next) {
1290 					f = Window.getFirstFocusable(wv.parentWindow);
1291 				} else {
1292 					foreach(w; &wv.parentWindow.focusableWidgets) {
1293 						if(w is wv)
1294 							break;
1295 						f = w;
1296 					}
1297 				}
1298 				if(f)
1299 					f.focus();
1300 			});
1301 		}
1302 		override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
1303 			/+
1304 			browser.runOnWebView((ev) {
1305 				ev.focus(); // even this can steal focus from other parts of my application!
1306 			});
1307 			+/
1308 			//sdpyPrintDebugString("setting");
1309 
1310 			return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
1311 			// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
1312 			// even though things work fine if i always cancel except
1313 			// it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh
1314 			// it also breaks its own pop up menus and drop down boxes to allow this! wtf
1315 		}
1316 		override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
1317 			browser.runOnWebView((ev) {
1318 				// this sometimes steals from the app too but it is relatively acceptable
1319 				// steals when i mouse in from the side of the window quickly, but still
1320 				// i want the minigui state to match so i'll allow it
1321 				ev.focus();
1322 			});
1323 		}
1324 	}
1325 
1326 	class MiniguiCefClient : CEF!cef_client_t {
1327 
1328 		void delegate(scope OpenNewWindowParams) openNewWindow;
1329 
1330 		MiniguiCefLifeSpanHandler lsh;
1331 		MiniguiLoadHandler loadHandler;
1332 		MiniguiDialogHandler dialogHandler;
1333 		MiniguiDisplayHandler displayHandler;
1334 		MiniguiDownloadHandler downloadHandler;
1335 		MiniguiKeyboardHandler keyboardHandler;
1336 		MiniguiFocusHandler focusHandler;
1337 		MiniguiRequestHandler requestHandler;
1338 		MiniguiContextMenuHandler contextMenuHandler;
1339 		this(void delegate(scope OpenNewWindowParams) openNewWindow) {
1340 			this.openNewWindow = openNewWindow;
1341 			lsh = new MiniguiCefLifeSpanHandler(this);
1342 			loadHandler = new MiniguiLoadHandler();
1343 			dialogHandler = new MiniguiDialogHandler();
1344 			displayHandler = new MiniguiDisplayHandler();
1345 			downloadHandler = new MiniguiDownloadHandler();
1346 			keyboardHandler = new MiniguiKeyboardHandler();
1347 			focusHandler = new MiniguiFocusHandler();
1348 			requestHandler = new MiniguiRequestHandler();
1349 			contextMenuHandler = new MiniguiContextMenuHandler(this);
1350 		}
1351 
1352 		override cef_audio_handler_t* get_audio_handler() {
1353 			return null;
1354 		}
1355 		override cef_context_menu_handler_t* get_context_menu_handler() {
1356 			return contextMenuHandler.returnable;
1357 		}
1358 		override cef_dialog_handler_t* get_dialog_handler() {
1359 			return dialogHandler.returnable;
1360 		}
1361 		override cef_display_handler_t* get_display_handler() {
1362 			return displayHandler.returnable;
1363 		}
1364 		override cef_download_handler_t* get_download_handler() {
1365 			return downloadHandler.returnable;
1366 		}
1367 		override cef_drag_handler_t* get_drag_handler() {
1368 			return null;
1369 		}
1370 		override cef_find_handler_t* get_find_handler() {
1371 			return null;
1372 		}
1373 		override cef_focus_handler_t* get_focus_handler() {
1374 			return focusHandler.returnable;
1375 		}
1376 		override cef_jsdialog_handler_t* get_jsdialog_handler() {
1377 			// needed for alert etc.
1378 			return null;
1379 		}
1380 		override cef_keyboard_handler_t* get_keyboard_handler() {
1381 			// this can handle keyboard shortcuts etc
1382 			return keyboardHandler.returnable;
1383 		}
1384 		override cef_life_span_handler_t* get_life_span_handler() {
1385 			return lsh.returnable;
1386 		}
1387 		override cef_load_handler_t* get_load_handler() {
1388 			return loadHandler.returnable;
1389 		}
1390 		override cef_render_handler_t* get_render_handler() {
1391 			// this thing might work for an off-screen thing
1392 			// like to an image or to a video stream maybe
1393 			//
1394 			// might be useful to have it render here then send it over too for remote X sharing a process
1395 			return null;
1396 		}
1397 		override cef_request_handler_t* get_request_handler() {
1398 			return requestHandler.returnable;
1399 		}
1400 		override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
1401 			return 0; // return 1 if you can actually handle the message
1402 		}
1403 		override cef_frame_handler_t* get_frame_handler() nothrow {
1404 			return null;
1405 		}
1406 		override cef_print_handler_t* get_print_handler() nothrow {
1407 			return null;
1408 		}
1409 
1410 		override cef_command_handler_t* get_command_handler() {
1411 			return null;
1412 		}
1413 
1414 		override cef_permission_handler_t* get_permission_handler() {
1415 			return null;
1416 		}
1417 
1418 	}
1419 }
1420 
1421 class BrowserClosedEvent : Event {
1422 	enum EventString = "browserclosed";
1423 
1424 	this(Widget target) { super(EventString, target); }
1425 	override bool cancelable() const { return false; }
1426 }
1427 
1428 class CopyRequestedEvent : Event {
1429 	enum EventString = "browsercopyrequested";
1430 
1431 	string what;
1432 
1433 	this(string what, Widget target) { this.what = what; super(EventString, target); }
1434 	override bool cancelable() const { return false; }
1435 }
1436 
1437 class NewWindowRequestedEvent : Event {
1438 	enum EventString = "browserwindowrequested";
1439 
1440 	string url;
1441 
1442 	this(string url, Widget target) { this.url = url; super(EventString, target); }
1443 	override bool cancelable() const { return false; }
1444 }
1445 
1446 
1447 
1448 /+
1449 pragma(mangle, "_ZN12CefWindowX115FocusEv")
1450 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE")
1451 extern(C++)
1452 export void _ZN12CefWindowX115FocusEv() {
1453 	sdpyPrintDebugString("OVERRIDDEN");
1454 }
1455 +/