1 module awebview.gui.html;
2 
3 import carbon.utils;
4 import awebview.gui.activity;
5 import awebview.gui.methodhandler;
6 import awebview.gui.application;
7 
8 import awebview.wrapper;
9 import awebview.cssgrammar;
10 
11 import std.variant;
12 import std.typecons;
13 
14 import std.array : appender;
15 import std.format : formattedWrite, format;
16 import std.functional : forward;
17 import std.conv : to;
18 import std.datetime;
19 public import core.time : Duration;
20 
21 public import carbon.event : FiredContext;
22 import carbon.templates : Lstr;
23 import carbon.utils : toLiteral;
24 
25 
26 string buildHTMLTagAttr(in string[string] attrs)
27 {
28     auto app = appender!string();
29     foreach(k, v; attrs)
30         app.formattedWrite("%s=%s ", k, toLiteral(v));
31 
32     return app.data;
33 }
34 
35 
36 string buildHTMLTagAttr(string tag, string value)
37 {
38     import std..string : format;
39     return format("%s=%s ", tag, toLiteral(value));
40 }
41 
42 unittest
43 {
44     assert(buildHTMLTagAttr("a", "b") == "a=b ");
45     assert(buildHTMLTagAttr(["a": "b"]) == "a=b ");
46 }
47 
48 
49 abstract class HTMLPage
50 {
51     this(string id)
52     {
53         _id =  id;
54     }
55 
56 
57     final
58     @property
59     inout(Activity) activity() inout pure nothrow @safe @nogc
60     {
61         return _activity;
62     }
63 
64 
65     final
66     @property
67     inout(Application) application() inout pure nothrow @safe @nogc
68     {
69         if(_activity is null)
70             return null;
71         else
72             return _activity.application;
73     }
74 
75 
76     final
77     string id() const pure nothrow @safe @nogc @property { return _id; }
78 
79 
80     string html() @property;
81 
82 
83     inout(HTMLElement[string]) elements() inout @property;
84 
85 
86     final
87     inout(HTMLElement) opIndex(string id) inout
88     {
89         return this.elements[id];
90     }
91 
92 
93     void onStart(Activity activity)
94     {
95         _activity = activity;
96 
97         foreach(key, elem; elements.maybeModified){
98             elem.onStart(this);
99         }
100     }
101 
102 
103     void onAttach(bool isInitPhase)
104     {
105         _activity = activity;
106         foreach(key, elem; elements.maybeModified)
107             elem.onAttach(isInitPhase);
108     }
109 
110 
111     void onLoad(bool isInit)
112     {
113         foreach(key, elem; elements.maybeModified)
114             elem.onLoad(isInit);
115 
116         _clientWidth = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
117         _clientHeight = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
118         _resizeStatements = generateResizeStatements(this.activity);
119         onResize(activity.width, activity.height);
120     }
121 
122 
123     void onUpdate()
124     {
125         auto newW = activity.evalJS(`document.documentElement.clientWidth`).get!uint;
126         auto newH = activity.evalJS(`document.documentElement.clientHeight`).get!uint;
127 
128         if(newW != _clientWidth || newH != _clientHeight)
129             onResize(activity.width, activity.height);
130 
131         _clientWidth = newW;
132         _clientHeight = newH;
133 
134         foreach(k, elem; elements.maybeModified)
135             elem.onUpdate();
136     }
137 
138 
139     void onDetach()
140     {
141         foreach(key, elem; elements.maybeModified)
142             elem.onDetach();
143     }
144 
145 
146     void onDestroy()
147     {
148         foreach(k, elem; elements.maybeModified)
149             elem.onDestroy();
150     }
151 
152 
153     void onResize(size_t w, size_t h)
154     {
155         this.activity.runJS(_resizeStatements);
156 
157         foreach(k, elem; elements.maybeModified)
158             elem.onResize(w, h);
159     }
160 
161 
162     void onReceiveImmediateMessage(ImmediateMessage msg)
163     {
164         foreach(k, e; elements.maybeModified){
165             e.onReceiveImmediateMessage(msg);
166         }
167     }
168 
169 
170   private:
171     Activity _activity;
172     string _id;
173 
174     size_t _clientWidth;
175     size_t _clientHeight;
176 
177     string _resizeStatements;
178 }
179 
180 
181 class WebPage : HTMLPage
182 {
183     this(string id, string url)
184     {
185         super(id);
186         _url = url;
187     }
188 
189 
190     @property
191     void location(string str)
192     {
193         if(activity){
194             _url = str;
195             activity.view.loadURL(WebURL(str));
196         }
197         else
198             _url = str;
199     }
200 
201 
202     @property
203     string location()
204     {
205         if(activity)
206             return activity.evalJS(q{document.location}).to!string;
207         else
208             return _url;
209     }
210 
211 
212     override
213     void onAttach(bool isInitPhase)
214     {
215         _bLoaded = false;
216     }
217 
218 
219     override
220     void onUpdate()
221     {
222         if(!_bLoaded)
223             this.location = _url;
224 
225         _bLoaded = true;
226     }
227 
228 
229     override
230     inout(HTMLElement[string]) elements() inout @property { return null; }
231 
232 
233     override
234     string html() @property
235     {
236         return `<html><head><title>Jump</title></head><body>Now loading...</body></html>`;
237     }
238 
239   private:
240     string _url;
241     bool _bLoaded;
242 }
243 
244 
245 class TemplateHTMLPage(string form) : HTMLPage
246 {
247     this(string id, Variant[string] exts = null)
248     {
249         super(id);
250         _exts = exts;
251     }
252 
253 
254     final
255     auto js(this This)() @property pure nothrow @safe @nogc inout
256     {
257         static struct Result
258         {
259             string html() const
260             {
261                 auto app = appender!string();
262                 foreach(_, e; _this._js)
263                     app.formattedWrite(`<script src="%s"></script>`);
264                 return app.data;
265             }
266 
267 
268             alias html this;
269 
270 
271             void opOpAssign(string op : "~")(string src)
272             {
273                 import std.path : baseName;
274 
275                 auto bn = src.baseName;
276                 _this._js[bn] = src;
277             }
278 
279 
280           private:
281             This _this;
282         }
283 
284 
285         return Result(this);
286     }
287 
288 
289     final
290     auto css(this This)() @property pure nothrow @safe @nogc inout
291     {
292         static struct Result
293         {
294             string html() const
295             {
296                 auto app = appender!string();
297                 foreach(_, e; _this._css)
298                     app.formattedWrite(`<link rel="stylesheet" href="%s">`);
299                 return app.data;
300             }
301 
302 
303             alias html this;
304 
305 
306             void opOpAssign(string op : "~")(string src)
307             {
308                 import std.path : baseName;
309 
310                 auto bn = src.baseName;
311                 _this._css[bn] = src;
312             }
313 
314 
315           private:
316             This _this;
317         }
318 
319 
320         return Result(this);
321     }
322 
323 
324     override
325     @property
326     string html()
327     {
328         return mixin(Lstr!(form));
329     }
330 
331 
332     override
333     @property
334     inout(HTMLElement[string]) elements() inout { return _elems; }
335 
336 
337     void opOpAssign(string op : "~")(HTMLElement element)
338     {
339         addElement(element);
340     }
341 
342 
343     void addElement(HTMLElement element)
344     {
345         _elems[element.id] = element;
346         if(this.activity !is null){
347             element.onStart(this);
348             element.onAttach(true);
349         }
350     }
351 
352 
353     @property
354     inout(Variant[string]) exts() inout { return _exts; }
355 
356     @property
357     inout(T) exts(T)(string str) inout { return *_exts[str].peek!T; }
358 
359 
360   private:
361     HTMLElement[string] _elems;
362     Variant[string] _exts;
363     string[string] _js;
364     string[string] _css;
365 }
366 
367 
368 class HTMLElement
369 {
370     import awebview.jsvariable;
371 
372 
373     this(string id, bool doCreateObject)
374     in{
375         if(id is null)
376             assert(!doCreateObject);
377     }
378     body{
379         _id = id;
380         _hasObj = doCreateObject;
381     }
382 
383 
384     final
385     @property
386     inout(HTMLPage) page() inout pure nothrow @safe @nogc { return _page; }
387 
388 
389     final
390     @property
391     inout(Activity) activity() inout pure nothrow @safe @nogc
392     {
393         if(_page is null)
394             return null;
395         else
396             return _page.activity;
397     }
398 
399 
400     final
401     @property
402     inout(Application) application() inout pure nothrow @safe @nogc
403     {
404         if(this.activity is null)
405             return null;
406         else
407             return this.activity.application;
408     }
409 
410 
411     final
412     @property
413     bool hasObject() const pure nothrow @safe @nogc
414     {
415         return _hasObj;
416     }
417 
418 
419     @property
420     WeakRef!JSObject jsobject()
421     in {
422         assert(_v.isObject);
423     }
424     body {
425         return _v.get!(WeakRef!JSObject);
426     }
427 
428 
429     @property
430     string jsExpr()
431     in {
432         assert(hasObject || hasId);
433     }
434     body {
435         if(this.hasObject)
436             return mixin(Lstr!q{_tmp_%[_id%].domObject});
437         else
438             return `document.getElementById("` ~ id ~ `")`;
439     }
440 
441 
442     final
443     @property
444     auto domObject()
445     {
446         return jsExpression(this.activity, this.jsExpr);
447     }
448 
449 
450     final @property bool hasId() const pure nothrow @safe @nogc { return _id !is null; }
451 
452 
453     final @property string id() const pure nothrow @safe @nogc { return _id; }
454 
455 
456     @property string html() { return ""; }
457     @property string mime() { return "text/html"; }
458     @property const(void)[] rawResource() { return this.html; }
459 
460 
461     void onStart(HTMLPage page)
462     {
463         _page = page;
464 
465         if(this.hasObject && !_v.isObject){
466             _v = activity.createObject(_id);
467         }
468     }
469 
470 
471     void onDestroy()
472     {
473         _page = null;
474         _v = JSValue.null_;
475     }
476 
477 
478     void onAttach(bool isInit)
479     {
480         if(isInit && this.hasObject && !_v.isObject){
481             _v = activity.createObject(_id);
482         }
483     }
484 
485 
486     void onUpdate() {}
487 
488 
489     void onDetach() {}
490 
491 
492     void onLoad(bool isInit)
493     {
494         if(this.hasObject){
495             activity.runJS(mixin(Lstr!q{
496                 _tmp_%[_id%] = {};
497                 _tmp_%[_id%].domObject = document.getElementById("%[_id%]");
498             }));
499         }
500 
501         if(this.hasId || this.hasObject){
502             foreach(key, ref v; _staticProperties)
503                 this.opIndexAssign(v, key);
504         }
505     }
506 
507 
508     void onResize(size_t w, size_t h) {}
509 
510 
511     final
512     @property
513     auto staticProps()
514     {
515         static struct Result
516         {
517             void opIndexAssign(T)(T value, string name)
518             if(is(typeof(JSValue(value)) : JSValue))
519             {
520                 _elem.staticPropsSet(name, value);
521             }
522 
523 
524             void remove(string name)
525             {
526                 _elem.staticPropsRemove(name);
527             }
528 
529 
530             bool opBinaryRight(string op : "in")(string name) inout
531             {
532                 return _elem.inStaticProps(name);
533             }
534 
535           private:
536             HTMLElement _elem;
537         }
538 
539         return Result(this);
540     }
541 
542 
543     void staticPropsSet(T)(string name, T value)
544     if(is(typeof(JSValue(value)) : JSValue))
545     in{
546         assert(this.hasId);
547     }
548     body{
549         JSValue jv = JSValue(value);
550         _staticProperties[name] = jv;
551         if(this.activity){
552             this.opIndexAssign(jv, name);
553         }
554     }
555 
556 
557     final
558     void staticPropsRemove(string name)
559     {
560         _staticProperties.remove(name);
561     }
562 
563 
564     final
565     bool inStaticProps(string name) inout
566     {
567         return !(name !in _staticProperties);
568     }
569 
570 
571     final
572     {
573         mixin JSExprDOMEagerOperators!();
574     }
575 
576 
577     final
578     Tuple!(uint, "x", uint, "y", uint, "width", uint, "height")
579      boundingClientRect() @property
580     {
581         this.activity.runJS(mixin(Lstr!q{
582             var e = %[domObject.jsExpr%].getBoundingClientRect();
583             _carrierObject_.x = e.left;
584             _carrierObject_.y = e.top;
585             _carrierObject_.w = e.width;
586             _carrierObject_.h = e.height;
587         }));
588 
589         auto co = activity.carrierObject;
590         typeof(return) res;
591         res.x = co["x"].get!uint;
592         res.y = co["y"].get!uint;
593         res.width = co["w"].get!uint;
594         res.height = co["h"].get!uint;
595 
596         return res;
597     }
598 
599 
600     final
601     uint posY() @property
602     {
603         return domObject.invoke("getBoundingClientRect")["top"].eval().get!uint;
604     }
605 
606 
607     final
608     uint posX() @property
609     {
610         return domObject.invoke("getBoundingClientRect")["left"].eval().get!uint;
611     }
612 
613 
614     final
615     uint[2] pos() @property
616     {
617         auto rec = boundingClientRect();
618         return [rec.x, rec.y];
619     }
620 
621 
622     final
623     uint width() @property
624     {
625         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
626     }
627 
628 
629     final
630     uint height() @property
631     {
632         return domObject.invoke("getBoundingClientRect")["width"].eval().get!uint;
633     }
634 
635 
636 
637     void onReceiveImmediateMessage(ImmediateMessage msg){}
638 
639 
640   private:
641     string _id;
642     bool _hasObj;
643     JSValue _v;
644     HTMLPage _page;
645     JSValue[string] _staticProperties;
646 }
647 
648 
649 class IDOnlyElement : HTMLElement
650 {
651     this(string id)
652     in{
653         assert(id !is null);
654     }
655     body{
656         super(id, false);
657     }
658 }
659 
660 
661 class TagOnlyElement : HTMLElement
662 {
663     this(string id)
664     in {
665         assert(id !is null);
666     }
667     body {
668         super(id, true);
669     }
670 }
671 
672 
673 /**
674 Example:
675 ----------------------
676 class MyButton : TemplateHTMLElement!(HTMLElement,
677     q{<input type="button" id="%[id%]" value="Click me!">})
678 {
679     this(string id)
680     {
681         super(id, null, false);
682     }
683 }
684 
685 auto btn1 = new MyButton("btn1");
686 assert(btn1.html == q{<input type="button" id="btn1" value="Click me!">});
687 ----------------------
688 */
689 abstract class TemplateHTMLElement(Element, string form) : Element
690 if(is(Element : HTMLElement))
691 {
692     this(T...)(auto ref T args)
693     {
694       static if(is(typeof(super(forward!args[0 .. $-1]))) &&
695                 is(typeof(args[$-1]) : Variant[string]))
696       {
697         super(forward!args[0 .. $-1]);
698         _exts = args[$-1];
699       }
700       else
701         super(forward!args);
702     }
703 
704 
705     @property
706     inout(Variant[string]) exts() inout { return _exts; }
707 
708 
709     override
710     @property
711     string html()
712     {
713         import carbon.templates : Lstr;
714         return mixin(Lstr!(form));
715     }
716 
717 
718   private:
719     Variant[string] _exts;
720 }
721 
722 
723 /// ditto
724 alias TemplateHTMLElement(string form) = TemplateHTMLElement!(HTMLElement, form);
725 
726 
727 /**
728 Example:
729 ----------------
730 class MyButton : DefineSignals!(DeclareSignals!(HTMLElement, "onClick"), "onClick")
731 {
732     this(string id)
733     {
734         super(id, true);
735     }
736 
737     string html() const { ... }
738 }
739 
740 
741 MyButton btn1 = new MyButton("btn1");
742 btn1.onClick.strongConnect(delegate(FiredContext ctx, WeakRef!(const(JSArrayCpp)) arr){
743     assert(ctx.sender == btn1);
744 
745     writeln("fired a signal by ", ctx);
746 });
747 ----------------
748 */
749 abstract class DefineSignals(Element, names...) : Element
750 if(is(Element : HTMLElement) && names.length >= 1)
751 {
752     import carbon.event;
753 
754     this(T...)(auto ref T args)
755     {
756         super(forward!args);
757 
758         foreach(name; names)
759             mixin(format(`_%1$sEvent = new typeof(_%1$sEvent)();`, name));
760     }
761 
762 
763     mixin(genMethod());
764 
765 
766   private:
767     mixin(genField());
768 
769     static
770     {
771         string genField()
772         {
773             auto app = appender!string;
774             foreach(s; names)
775                 app.formattedWrite("EventManager!(WeakRef!(const(JSArrayCpp))) _%sEvent;\n", s);
776 
777             return app.data;
778         }
779 
780 
781         string genMethod()
782         {
783             auto app = appender!string;
784             foreach(s; names){
785                 app.formattedWrite("EventManager!(WeakRef!(const(JSArrayCpp))) %1$s() { return _%1$sEvent; }\n", s);
786                 app.formattedWrite("override void %1$s(WeakRef!(const(JSArrayCpp)) arr) { _%1$sEvent.emit(this, arr); }\n", s);
787             }
788 
789             return app.data;
790         }
791     }
792 }
793 
794 
795 /**
796 Example:
797 -------------------
798 class MyButton : DeclareSignals!(HTMLElement, "onClick")
799 {
800     this(string id)
801     {
802         super(id, true);
803     }
804 
805 
806     override
807     void onClick(WeakRef!(JSArrayCpp) args)
808     {
809         writeln("OK");
810     }
811 }
812 -------------------
813 */
814 abstract class DeclareSignals(Element, names...) : Element
815 if(is(Element : HTMLElement) && names.length >= 1)
816 {
817     this(T...)(auto ref T args)
818     {
819         super(forward!args);
820     }
821 
822 
823     final
824     void doJSInitialize(bool b) @property
825     {
826         _doInit = b;
827     }
828 
829 
830     final
831     void stopPropergation(bool b) @property
832     {
833         _stopProp = b;
834     }
835 
836 
837     mixin(genDeclMethods);
838 
839     override
840     void onStart(HTMLPage page)
841     {
842         super.onStart(page);
843         this.activity.methodHandler.set(this);
844     }
845 
846 
847     override
848     void onLoad(bool init)
849     {
850         super.onLoad(init);
851 
852         if(_doInit){
853             this.activity.runJS(genSettingEventHandlers(this.id, this.domObject.jsExpr, _stopProp));
854         }
855     }
856 
857 
858   private:
859     bool _doInit = true;
860     bool _stopProp = false;
861 
862     static
863     {
864         string genDeclMethods()
865         {
866             auto app = appender!string();
867 
868             foreach(s; names)
869                 app.formattedWrite(`@JSMethodTag("%1$s"w) `"void %1$s(WeakRef!(const(JSArrayCpp)));\n", s);
870 
871             return app.data;
872         }
873 
874 
875         string genSettingEventHandlers(string id, string domExpr, bool stopProp)
876         {
877             import std..string : toLower;
878 
879             auto app = appender!string();
880             app.formattedWrite("var e = %s;", domExpr);
881 
882             foreach(s; names){
883                 if(!stopProp)
884                     app.formattedWrite(q{e.%3$s = function() { %1$s.%2$s(); };}, id, s, toLower(s));
885                 else
886                     app.formattedWrite(q{e.%3$s = function(ev) { ev.stopPropergation(); %1$s.%2$s(); };}, id, s, toLower(s));
887             }
888 
889             return app.data;
890         }
891     }
892 }
893 
894 
895 alias DeclDefSignals(Element, names...) = DefineSignals!(DeclareSignals!(Element, names), names);
896 
897 
898 /**
899 Open context menu when user click right button.
900 */
901 abstract class DeclareContextMenu(Element, setting...) : DeclareSignals!(Element, "onContextMenu", setting)
902 {
903     this(T...)(auto ref T args)
904     {
905         super(forward!args);
906     }
907 
908 
909     HTMLPage menuPage() @property;
910 
911 
912     override
913     void onContextMenu(WeakRef!(const(JSArrayCpp)))
914     {
915         this.activity.popup(this.menuPage);
916     }
917 }
918 
919 
920 /**
921 Mouse hover event
922 */
923 abstract class DeclareHoverSignal(Element) : DeclareSignals!(Element, "onMouseOver", "onMouseOut")
924 {
925     this(T...)(auto ref T args)
926     {
927         super(forward!args);
928     }
929 
930 
931     /**
932     */
933     final
934     bool hover() @property { return _hovered; }
935 
936 
937     final
938     void onHoverImpl()
939     {
940         if(_isStarted)
941             onHover(_hovered, Clock.currTime - _startTime);
942     }
943 
944 
945     /**
946     */
947     void onHover(bool bOver, Duration dur);
948 
949 
950     override
951     void onUpdate()
952     {
953         super.onUpdate();
954 
955         onHoverImpl();
956     }
957 
958 
959     override
960     void onMouseOver(WeakRef!(const(JSArrayCpp)))
961     {
962         _isStarted = true;
963         _hovered = true;
964         _startTime = Clock.currTime;
965         onHoverImpl();
966     }
967 
968 
969     override
970     void onMouseOut(WeakRef!(const(JSArrayCpp)))
971     {
972         _hovered = false;
973         _startTime = Clock.currTime;
974         onHoverImpl();
975     }
976 
977 
978   private:
979     bool _isStarted;
980     bool _hovered;
981     SysTime _startTime;
982 }
983 
984 
985 /**
986 EventPipe
987 */
988 final class PageSignalHandler : HTMLElement
989 {
990     import carbon.event;
991 
992     this(string id = "_event_signal_pipe")
993     {
994         super(id, false);
995         _onStartEvent = new EventManager!(Activity)();
996         _onAttachEvent = new EventManager!bool();
997         _onLoadEvent = new EventManager!bool();
998         _onUpdateEvent = new EventManager!();
999         _onDetachEvent = new EventManager!();
1000         _onDestroyEvent = new EventManager!();
1001         _onResizeEvent = new EventManager!(size_t, size_t)();
1002     }
1003 
1004   @property
1005   {
1006     EventManager!(Activity)       onStartHandler  () { return _onStartEvent; }
1007     EventManager!(bool)           onAttachHandler () { return _onAttachEvent; }
1008     EventManager!(bool)           onLoadHandler   () { return _onLoadEvent; }
1009     EventManager!()                 onUpdateHandler () { return _onUpdateEvent; }
1010     EventManager!()                 onDetachHandler () { return _onDetachEvent; }
1011     EventManager!()                 onDestroyHandler() { return _onDestroyEvent; }
1012     EventManager!(size_t, size_t) onResizeHandler () { return _onResizeEvent; }
1013   }
1014 
1015     override
1016     void onStart(HTMLPage page)
1017     {
1018         super.onStart(page);
1019         _onStartEvent.emit(this, page.activity);
1020     }
1021 
1022 
1023     override
1024     void onAttach(bool bInit)
1025     {
1026         super.onAttach(bInit);
1027         _onAttachEvent.emit(this, bInit);
1028     }
1029 
1030 
1031     override
1032     void onLoad(bool bInit)
1033     {
1034         super.onLoad(bInit);
1035         _onLoadEvent.emit(this, bInit);
1036     }
1037 
1038 
1039     override
1040     void onUpdate()
1041     {
1042         super.onUpdate();
1043         _onUpdateEvent.emit(this);
1044     }
1045 
1046 
1047     override
1048     void onDetach()
1049     {
1050         super.onDetach();
1051         _onDetachEvent.emit(this);
1052     }
1053 
1054 
1055     override
1056     void onDestroy()
1057     {
1058         super.onDestroy();
1059         _onDestroyEvent.emit(this);
1060     }
1061 
1062 
1063     override
1064     void onResize(size_t w, size_t h)
1065     {
1066         super.onResize(w, h);
1067         _onResizeEvent.emit(this, w, h);
1068     }
1069 
1070 
1071   private:
1072     EventManager!(Activity) _onStartEvent;
1073     EventManager!(bool) _onAttachEvent;
1074     EventManager!(bool) _onLoadEvent;
1075     EventManager!() _onUpdateEvent;
1076     EventManager!() _onDetachEvent;
1077     EventManager!() _onDestroyEvent;
1078     EventManager!(size_t, size_t) _onResizeEvent;
1079 }
1080 
1081 
1082 /**
1083 Selectors API
1084 */
1085 alias querySelector = querySelectorImpl!false;
1086 
1087 
1088 /// ditto
1089 alias querySelectorAll = querySelectorImpl!true;
1090 
1091 
1092 auto querySelectorImpl(bool isAll)(Activity activity, string cssSelector)
1093 {
1094     import awebview.jsvariable;
1095 
1096     static struct QuerySelectorResult
1097     {
1098         string jsExpr() const @property { return _jsExpr; }
1099         Activity activity() @property { return _activity; }
1100 
1101 
1102         mixin JSExprDOMEagerOperators!();
1103 
1104       private:
1105         string _jsExpr;
1106         Activity _activity;
1107     }
1108 
1109     QuerySelectorResult res;
1110     res._jsExpr = mixin(Lstr!q{document.%[isAll ? "querySelectorAll" : "querySelector"%](%[toLiteral(cssSelector)%])});
1111     res._activity = activity;
1112 
1113     return res;
1114 }