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 int on_before_popup(
768 			RC!cef_browser_t browser,
769 			RC!cef_frame_t frame,
770 			const(cef_string_t)* target_url,
771 			const(cef_string_t)* target_frame_name,
772 			cef_window_open_disposition_t target_disposition,
773 			int user_gesture,
774 			const(cef_popup_features_t)* popupFeatures,
775 			cef_window_info_t* windowInfo,
776 			cef_client_t** client,
777 			cef_browser_settings_t* browser_settings,
778 			cef_dictionary_value_t** extra_info,
779 			int* no_javascript_access
780 		) {
781 		sdpyPrintDebugString("on_before_popup");
782 			if(this.client.openNewWindow is null)
783 				return 1; // new windows disabled
784 
785 			try {
786 				int ret;
787 
788 				import core.thread;
789 				try { thread_attachThis(); } catch(Exception e) {}
790 
791 				// FIXME: change settings here
792 
793 				runInGuiThread({
794 					ret = 1;
795 					scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
796 						ret = 0;
797 						if(parent !is null) {
798 							auto widget = new WebViewWidget_CEF(this.client, parent, false);
799 							(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
800 
801 							passed_settings.set(browser_settings);
802 
803 							return widget;
804 						}
805 						return null;
806 					};
807 					this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept));
808 					return;
809 				});
810 
811 				return ret;
812 			} catch(Exception e) {
813 				return 1;
814 			}
815 			/+
816 			if(!user_gesture)
817 				return 1; // if not created by the user, cancel it; basic popup blocking
818 			+/
819 		}
820 		override void on_after_created(RC!cef_browser_t browser) {
821 			auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle();
822 			auto ptr = browser.passable; // this adds to the refcount until it gets inside
823 
824 			import core.thread;
825 			try { thread_attachThis(); } catch(Exception e) {}
826 
827 			// the only reliable key (at least as far as i can tell) is the window handle
828 			// so gonna look that up and do the sync mapping that way.
829 			runInGuiThreadAsync({
830 				version(Windows) {
831 					auto parent = GetParent(handle);
832 				} else static if(UsingSimpledisplayX11) {
833 					import arsd.simpledisplay : Window;
834 					Window root;
835 					Window parent;
836 					Window ozone;
837 					uint c = 0;
838 					auto display = XDisplayConnection.get;
839 					Window* children;
840 					XQueryTree(display, handle, &root, &parent, &children, &c);
841 					if(c == 1)
842 						ozone = children[0];
843 					XFree(children);
844 				} else static assert(0);
845 
846 				if(auto wvp = parent in WebViewWidget.mapping) {
847 					auto wv = *wvp;
848 					wv.browserWindow = handle;
849 					wv.browserHandle = RC!cef_browser_t(ptr);
850 					wv.ozone = ozone ? ozone : handle;
851 
852 					wv.browserHostWrapped = new SimpleWindow(handle);
853 					// XSelectInput(XDisplayConnection.get, handle, EventMask.StructureNotifyMask);
854 
855 					wv.browserHostWrapped.onDestroyed = delegate{
856 						import std.stdio; writefln("browser host %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow);
857 
858 						auto bce = new BrowserClosedEvent(wv);
859 						bce.dispatch();
860 					};
861 
862 					// need this to forward key events to
863 					wv.browserWindowWrapped = new SimpleWindow(wv.ozone);
864 
865 					/+
866 					XSelectInput(XDisplayConnection.get, wv.ozone, EventMask.StructureNotifyMask);
867 					wv.browserWindowWrapped.onDestroyed = delegate{
868 						import std.stdio; writefln("browser core %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow);
869 
870 						//auto bce = new BrowserClosedEvent(wv);
871 						//bce.dispatch();
872 					};
873 					+/
874 
875 					/+
876 					XSelectInput(XDisplayConnection.get, ozone, EventMask.FocusChangeMask);
877 					wv.browserWindowWrapped.onFocusChange = (bool got) {
878 						import std.format;
879 						sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window));
880 					};
881 					+/
882 
883 					wv.registerMovementAdditionalWork();
884 
885 					WebViewWidget.browserMapping[handle] = wv;
886 				} else assert(0);
887 			});
888 		}
889 		override int do_close(RC!cef_browser_t browser) {
890 						import std.stdio;
891 						debug writeln("do_close");
892 			/+
893 			browser.runOnWebView((wv) {
894 				wv.browserWindowWrapped.close();
895 				.destroy(wv.browserHandle);
896 			});
897 
898 			return 1;
899 			+/
900 
901 			return 0;
902 		}
903 		override void on_before_close(RC!cef_browser_t browser) {
904 			import std.stdio; debug writeln("notify");
905 			browser.runOnWebView((wv) {
906 				.destroy(wv.browserHandle);
907 			});
908 			/+
909 			try
910 			semaphore.notify;
911 			catch(Exception e) { assert(0); }
912 			+/
913 		}
914 	}
915 
916 	class MiniguiLoadHandler : CEF!cef_load_handler_t {
917 		override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) {
918 			/+
919 			browser.runOnWebView((WebViewWidget wvw) {
920 				wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree;
921 			});
922 			+/
923 		}
924 		override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) {
925 		}
926 		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)*) {
927 		}
928 		override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) {
929 		}
930 	}
931 
932 	class MiniguiDialogHandler : CEF!cef_dialog_handler_t {
933 		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) {
934 			try {
935 				auto ptr = callback.passable();
936 				browser.runOnWebView((wv) {
937 					getOpenFileName((string name) {
938 						auto callback = RC!cef_file_dialog_callback_t(ptr);
939 						auto list = libcef.string_list_alloc();
940 						auto item = cef_string_t(name);
941 						libcef.string_list_append(list, &item);
942 						callback.cont(list);
943 					}, null, null, () {
944 						auto callback = RC!cef_file_dialog_callback_t(ptr);
945 						callback.cancel();
946 					}, "/home/me/");
947 				});
948 			} catch(Exception e) {}
949 
950 			return 1;
951 		}
952 	}
953 
954 	class MiniguiDownloadHandler : CEF!cef_download_handler_t {
955 		override void on_before_download(
956 			RC!cef_browser_t browser,
957 			RC!cef_download_item_t download_item,
958 			const(cef_string_t)* suggested_name,
959 			RC!cef_before_download_callback_t callback
960 		) nothrow
961 		{
962 			// FIXME: different filename and check if exists for overwrite etc
963 			auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length]));
964 			sdpyPrintDebugString(fn.toGC);
965 			callback.cont(&fn, false);
966 		}
967 
968 		override void on_download_updated(
969 			RC!cef_browser_t browser,
970 			RC!cef_download_item_t download_item,
971 			RC!cef_download_item_callback_t cancel
972 		) nothrow
973 		{
974 			sdpyPrintDebugString(download_item.get_percent_complete());
975 			// FIXME
976 		}
977 
978 		override int can_download(RC!(cef_browser_t), const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) {
979 			return 1;
980 		}
981 	}
982 
983 	class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t {
984 		override int on_pre_key_event(
985 			RC!(cef_browser_t) browser,
986 			const(cef_key_event_t)* event,
987 			XEvent* osEvent,
988 			int* isShortcut
989 		) nothrow {
990 		/+
991 			sdpyPrintDebugString("---pre---");
992 			sdpyPrintDebugString(event.focus_on_editable_field);
993 			sdpyPrintDebugString(event.windows_key_code);
994 			sdpyPrintDebugString(event.modifiers);
995 			sdpyPrintDebugString(event.unmodified_character);
996 		+/
997 			//*isShortcut = 1;
998 			return 0; // 1 if handled, which cancels sending it to browser
999 		}
1000 
1001 		override int on_key_event(
1002 			RC!(cef_browser_t) browser,
1003 			const(cef_key_event_t)* event,
1004 			XEvent* osEvent
1005 		) nothrow {
1006 		/+
1007 			sdpyPrintDebugString("---key---");
1008 			sdpyPrintDebugString(event.focus_on_editable_field);
1009 			sdpyPrintDebugString(event.windows_key_code);
1010 			sdpyPrintDebugString(event.modifiers);
1011 		+/
1012 			return 0; // 1 if handled
1013 		}
1014 	}
1015 
1016 	class MiniguiDisplayHandler : CEF!cef_display_handler_t {
1017 		override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) {
1018 			auto url = address.toGC;
1019 			browser.runOnWebView((wv) {
1020 				wv.url = url;
1021 			});
1022 		}
1023 		override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) {
1024 			auto t = title.toGC;
1025 			browser.runOnWebView((wv) {
1026 				wv.title = t;
1027 			});
1028 		}
1029 		override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) {
1030 			string url;
1031 			auto size = libcef.string_list_size(urls);
1032 			if(size > 0) {
1033 				cef_string_t str;
1034 				libcef.string_list_value(urls, 0, &str);
1035 				url = str.toGC;
1036 
1037 				static class Thing : CEF!cef_download_image_callback_t {
1038 					RC!cef_browser_t browserHandle;
1039 					this(RC!cef_browser_t browser) nothrow {
1040 						this.browserHandle = browser;
1041 					}
1042 					override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow {
1043 						int width;
1044 						int height;
1045 						if(image.getRawPointer is null) {
1046 							browserHandle.runOnWebView((wv) {
1047 								wv.favicon = null;
1048 							});
1049 							return;
1050 						}
1051 
1052 						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);
1053 
1054 						if(data.getRawPointer is null || width == 0 || height == 0) {
1055 							browserHandle.runOnWebView((wv) {
1056 								wv.favicon = null;
1057 							});
1058 						} else {
1059 							auto s = data.get_size();
1060 							auto buffer = new ubyte[](s);
1061 							auto got = data.get_data(buffer.ptr, buffer.length, 0);
1062 							auto slice = buffer[0 .. got];
1063 
1064 							auto img = new TrueColorImage (width, height, slice);
1065 
1066 							browserHandle.runOnWebView((wv) {
1067 								wv.favicon = img;
1068 							});
1069 						}
1070 					}
1071 				}
1072 
1073 				if(url.length) {
1074 					auto callback = new Thing(browser);
1075 
1076 					browser.get_host().download_image(&str, true, 16, 0, callback.passable);
1077 				} else {
1078 					browser.runOnWebView((wv) {
1079 						wv.favicon = null;
1080 					});
1081 				}
1082 			}
1083 
1084 			browser.runOnWebView((wv) {
1085 				wv.favicon_url = url;
1086 			});
1087 		}
1088 		override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) {
1089 		}
1090 		override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) {
1091 			return 0;
1092 		}
1093 		override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) {
1094 			auto status = msg.toGC;
1095 			browser.runOnWebView((wv) {
1096 				wv.status = status;
1097 			});
1098 		}
1099 		override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) {
1100 			// progress is from 0.0 to 1.0
1101 			browser.runOnWebView((wv) {
1102 				wv.loadingProgress = cast(int) (progress * 100);
1103 			});
1104 		}
1105 		override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) {
1106 			return 1; // 1 means to suppress it being automatically output
1107 		}
1108 		override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) {
1109 			return 0;
1110 		}
1111 		override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) {
1112 			return 0;
1113 		}
1114 		override void on_media_access_change(RC!(cef_browser_t), int, int) {
1115 
1116 		}
1117 	}
1118 
1119 	class MiniguiRequestHandler : CEF!cef_request_handler_t {
1120 		override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow {
1121 			return 0;
1122 		}
1123 		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 {
1124 			return 0;
1125 		}
1126 		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 {
1127 			return null;
1128 		}
1129 		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 {
1130 			// this is for http basic auth popup.....
1131 			return 0;
1132 		}
1133 		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 {
1134 			return 0;
1135 		}
1136 		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 {
1137 			return 0;
1138 		}
1139 		override void on_render_view_ready(RC!(cef_browser_t) p) nothrow {
1140 
1141 		}
1142 		override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow {
1143 
1144 		}
1145 		override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow {
1146 			browser.runOnWebView(delegate(wv) {
1147 				wv.executeJavascript("console.log('here');");
1148 			});
1149 
1150 		}
1151 	}
1152 
1153 	class MiniguiContextMenuHandler : CEF!cef_context_menu_handler_t {
1154 		private MiniguiCefClient client;
1155 		this(MiniguiCefClient client) {
1156 			this.client = client;
1157 		}
1158 
1159 		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 {
1160 			// FIXME: should i customize these? it is kinda specific to my browser
1161 			int itemNo;
1162 
1163 			void addItem(string label, int commandNo) {
1164 				auto lbl = cef_string_t(label);
1165 				model.insert_item_at(/* index */ itemNo, /* command id */ cef_menu_id_t.MENU_ID_USER_FIRST + commandNo, &lbl);
1166 				itemNo++;
1167 			}
1168 
1169 			void addSeparator() {
1170 				model.insert_separator_at(itemNo);
1171 				itemNo++;
1172 			}
1173 
1174 			auto flags = params.get_type_flags();
1175 
1176 			if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_LINK) {
1177 				// cef_string_userfree_t linkUrl = params.get_unfiltered_link_url();
1178 				// toGCAndFree
1179 				addItem("Open link in new window", 1);
1180 				addItem("Copy link URL", 2);
1181 
1182 				// FIXME: open in other browsers
1183 				addSeparator();
1184 			}
1185 
1186 			if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_MEDIA) {
1187 				// cef_string_userfree_t linkUrl = params.get_source_url();
1188 				// toGCAndFree
1189 				addItem("Open media in new window", 3);
1190 				addItem("Copy source URL", 4);
1191 				addItem("Download media", 5);
1192 				addSeparator();
1193 			}
1194 
1195 
1196 			// get_page_url
1197 			// get_title_text
1198 			// has_image_contents ???
1199 			// get_source_url
1200 			// get_xcoord and get_ycoord
1201 			// get_selection_text
1202 
1203 		}
1204 		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 {
1205 			// could do a custom display here if i want but i think it is good enough as it is
1206 			return 0;
1207 		}
1208 		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 {
1209 			switch(commandId) {
1210 				case cef_menu_id_t.MENU_ID_USER_FIRST + 1: // open link in new window
1211 					auto what = params.get_unfiltered_link_url().toGCAndFree();
1212 
1213 					browser.runOnWebView((widget) {
1214 						auto event = new NewWindowRequestedEvent(what, widget);
1215 						event.dispatch();
1216 					});
1217 					return 1;
1218 				case cef_menu_id_t.MENU_ID_USER_FIRST + 2: // copy link url
1219 					auto what = params.get_link_url().toGCAndFree();
1220 
1221 					browser.runOnWebView((widget) {
1222 						auto event = new CopyRequestedEvent(what, widget);
1223 						event.dispatch();
1224 					});
1225 					return 1;
1226 				case cef_menu_id_t.MENU_ID_USER_FIRST + 3: // open media in new window
1227 					auto what = params.get_source_url().toGCAndFree();
1228 
1229 					browser.runOnWebView((widget) {
1230 						auto event = new NewWindowRequestedEvent(what, widget);
1231 						event.dispatch();
1232 					});
1233 					return 1;
1234 				case cef_menu_id_t.MENU_ID_USER_FIRST + 4: // copy source url
1235 					auto what = params.get_source_url().toGCAndFree();
1236 
1237 					browser.runOnWebView((widget) {
1238 						auto event = new CopyRequestedEvent(what, widget);
1239 						event.dispatch();
1240 					});
1241 					return 1;
1242 				case cef_menu_id_t.MENU_ID_USER_FIRST + 5: // download media
1243 					auto str = cef_string_t(params.get_source_url().toGCAndFree());
1244 					browser.get_host().start_download(&str);
1245 					return 1;
1246 				default:
1247 					return 0;
1248 			}
1249 		}
1250 		override void on_context_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow {
1251 			// to close the custom display
1252 		}
1253 
1254 		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 {
1255 			return 0;
1256 		}
1257 		override int on_quick_menu_command(RC!(cef_browser_t), RC!(cef_frame_t), int, cef_event_flags_t) nothrow {
1258 			return 0;
1259 		}
1260 		override void on_quick_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow {
1261 
1262 		}
1263 	}
1264 
1265 	class MiniguiFocusHandler : CEF!cef_focus_handler_t {
1266 		override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
1267 			browser.runOnWebView(delegate(wv) {
1268 				Widget f;
1269 				if(next) {
1270 					f = Window.getFirstFocusable(wv.parentWindow);
1271 				} else {
1272 					foreach(w; &wv.parentWindow.focusableWidgets) {
1273 						if(w is wv)
1274 							break;
1275 						f = w;
1276 					}
1277 				}
1278 				if(f)
1279 					f.focus();
1280 			});
1281 		}
1282 		override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
1283 			/+
1284 			browser.runOnWebView((ev) {
1285 				ev.focus(); // even this can steal focus from other parts of my application!
1286 			});
1287 			+/
1288 			//sdpyPrintDebugString("setting");
1289 
1290 			return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
1291 			// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
1292 			// even though things work fine if i always cancel except
1293 			// it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh
1294 			// it also breaks its own pop up menus and drop down boxes to allow this! wtf
1295 		}
1296 		override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
1297 			browser.runOnWebView((ev) {
1298 				// this sometimes steals from the app too but it is relatively acceptable
1299 				// steals when i mouse in from the side of the window quickly, but still
1300 				// i want the minigui state to match so i'll allow it
1301 				ev.focus();
1302 			});
1303 		}
1304 	}
1305 
1306 	class MiniguiCefClient : CEF!cef_client_t {
1307 
1308 		void delegate(scope OpenNewWindowParams) openNewWindow;
1309 
1310 		MiniguiCefLifeSpanHandler lsh;
1311 		MiniguiLoadHandler loadHandler;
1312 		MiniguiDialogHandler dialogHandler;
1313 		MiniguiDisplayHandler displayHandler;
1314 		MiniguiDownloadHandler downloadHandler;
1315 		MiniguiKeyboardHandler keyboardHandler;
1316 		MiniguiFocusHandler focusHandler;
1317 		MiniguiRequestHandler requestHandler;
1318 		MiniguiContextMenuHandler contextMenuHandler;
1319 		this(void delegate(scope OpenNewWindowParams) openNewWindow) {
1320 			this.openNewWindow = openNewWindow;
1321 			lsh = new MiniguiCefLifeSpanHandler(this);
1322 			loadHandler = new MiniguiLoadHandler();
1323 			dialogHandler = new MiniguiDialogHandler();
1324 			displayHandler = new MiniguiDisplayHandler();
1325 			downloadHandler = new MiniguiDownloadHandler();
1326 			keyboardHandler = new MiniguiKeyboardHandler();
1327 			focusHandler = new MiniguiFocusHandler();
1328 			requestHandler = new MiniguiRequestHandler();
1329 			contextMenuHandler = new MiniguiContextMenuHandler(this);
1330 		}
1331 
1332 		override cef_audio_handler_t* get_audio_handler() {
1333 			return null;
1334 		}
1335 		override cef_context_menu_handler_t* get_context_menu_handler() {
1336 			return contextMenuHandler.returnable;
1337 		}
1338 		override cef_dialog_handler_t* get_dialog_handler() {
1339 			return dialogHandler.returnable;
1340 		}
1341 		override cef_display_handler_t* get_display_handler() {
1342 			return displayHandler.returnable;
1343 		}
1344 		override cef_download_handler_t* get_download_handler() {
1345 			return downloadHandler.returnable;
1346 		}
1347 		override cef_drag_handler_t* get_drag_handler() {
1348 			return null;
1349 		}
1350 		override cef_find_handler_t* get_find_handler() {
1351 			return null;
1352 		}
1353 		override cef_focus_handler_t* get_focus_handler() {
1354 			return focusHandler.returnable;
1355 		}
1356 		override cef_jsdialog_handler_t* get_jsdialog_handler() {
1357 			// needed for alert etc.
1358 			return null;
1359 		}
1360 		override cef_keyboard_handler_t* get_keyboard_handler() {
1361 			// this can handle keyboard shortcuts etc
1362 			return keyboardHandler.returnable;
1363 		}
1364 		override cef_life_span_handler_t* get_life_span_handler() {
1365 			return lsh.returnable;
1366 		}
1367 		override cef_load_handler_t* get_load_handler() {
1368 			return loadHandler.returnable;
1369 		}
1370 		override cef_render_handler_t* get_render_handler() {
1371 			// this thing might work for an off-screen thing
1372 			// like to an image or to a video stream maybe
1373 			//
1374 			// might be useful to have it render here then send it over too for remote X sharing a process
1375 			return null;
1376 		}
1377 		override cef_request_handler_t* get_request_handler() {
1378 			return requestHandler.returnable;
1379 		}
1380 		override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
1381 			return 0; // return 1 if you can actually handle the message
1382 		}
1383 		override cef_frame_handler_t* get_frame_handler() nothrow {
1384 			return null;
1385 		}
1386 		override cef_print_handler_t* get_print_handler() nothrow {
1387 			return null;
1388 		}
1389 
1390 		override cef_command_handler_t* get_command_handler() {
1391 			return null;
1392 		}
1393 
1394 		override cef_permission_handler_t* get_permission_handler() {
1395 			return null;
1396 		}
1397 
1398 	}
1399 }
1400 
1401 class BrowserClosedEvent : Event {
1402 	enum EventString = "browserclosed";
1403 
1404 	this(Widget target) { super(EventString, target); }
1405 	override bool cancelable() const { return false; }
1406 }
1407 
1408 class CopyRequestedEvent : Event {
1409 	enum EventString = "browsercopyrequested";
1410 
1411 	string what;
1412 
1413 	this(string what, Widget target) { this.what = what; super(EventString, target); }
1414 	override bool cancelable() const { return false; }
1415 }
1416 
1417 class NewWindowRequestedEvent : Event {
1418 	enum EventString = "browserwindowrequested";
1419 
1420 	string url;
1421 
1422 	this(string url, Widget target) { this.url = url; super(EventString, target); }
1423 	override bool cancelable() const { return false; }
1424 }
1425 
1426 
1427 
1428 /+
1429 pragma(mangle, "_ZN12CefWindowX115FocusEv")
1430 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE")
1431 extern(C++)
1432 export void _ZN12CefWindowX115FocusEv() {
1433 	sdpyPrintDebugString("OVERRIDDEN");
1434 }
1435 +/