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