001package Torello.Browser.BrowserAPI;
002
003import java.util.*;
004import javax.json.*;
005import javax.json.stream.*;
006import java.io.*;
007
008import java.lang.reflect.Method;
009import java.lang.reflect.Parameter;
010import java.util.function.Function;
011
012import Torello.Browser.BrowserEvent;
013import Torello.Browser.JavaScriptAPI.*;
014import Torello.Browser.helper.*;
015
016import Torello.Java.Additional.*;
017import Torello.Java.JSON.*;
018
019import static Torello.Java.JSON.JFlag.*;
020
021import Torello.Java.StrCmpr;
022import Torello.JavaDoc.StaticFunctional;
023import Torello.JavaDoc.JDHeaderBackgroundImg;
024import Torello.JavaDoc.Excuse;
025
026/**
027 * <SPAN CLASS=COPIEDJDK><B>This domain allows interacting with the browser to control PWAs.</B></SPAN>
028 * 
029 * <EMBED CLASS='external-html' DATA-FILE-ID=CODE_GEN_NOTE>
030 */
031@StaticFunctional(Excused={"counter"}, Excuses={Excuse.CONFIGURATION})
032@JDHeaderBackgroundImg(EmbedTagFileID="WOOD_PLANK_NOTE")
033public class PWA
034{
035    // ********************************************************************************************
036    // ********************************************************************************************
037    // Class Header Stuff
038    // ********************************************************************************************
039    // ********************************************************************************************
040
041
042    // No Pubic Constructors
043    private PWA () { }
044
045    // These two Vector's are used by all the "Methods" exported by this class.  java.lang.reflect
046    // is used to generate the JSON String's.  It saves thousands of lines of Auto-Generated Code.
047    private static final Map<String, Vector<String>>    parameterNames = new HashMap<>();
048    private static final Map<String, Vector<Class<?>>>  parameterTypes = new HashMap<>();
049
050    // Some Methods do not take any parameters - for instance all the "enable()" and "disable()"
051    // I simply could not get ride of RAW-TYPES and UNCHECKED warnings... so there are now,
052    // offically, two empty-vectors.  One for String's, and the other for Classes.
053
054    private static final Vector<String>     EMPTY_VEC_STR = new Vector<>();
055    private static final Vector<Class<?>>   EMPTY_VEC_CLASS = new Vector<>();
056
057    static
058    {
059        for (Method m : PWA.class.getMethods())
060        {
061            // This doesn't work!  The parameter names are all "arg0" ... "argN"
062            // It works for java.lang.reflect.Field, BUT NOT java.lang.reflect.Parameter!
063            //
064            // Vector<String> parameterNamesList = new Vector<>(); -- NOPE!
065
066            Vector<Class<?>> parameterTypesList = new Vector<>();
067        
068            for (Parameter p : m.getParameters()) parameterTypesList.add(p.getType());
069
070            parameterTypes.put(
071                m.getName(),
072                (parameterTypesList.size() > 0) ? parameterTypesList : EMPTY_VEC_CLASS
073            );
074        }
075    }
076
077    static
078    {
079        Vector<String> v = null;
080
081        v = new Vector<String>(1);
082        parameterNames.put("getOsAppState", v);
083        Collections.addAll(v, new String[]
084        { "manifestId", });
085
086        v = new Vector<String>(2);
087        parameterNames.put("install", v);
088        Collections.addAll(v, new String[]
089        { "manifestId", "installUrlOrBundleUrl", });
090
091        v = new Vector<String>(1);
092        parameterNames.put("uninstall", v);
093        Collections.addAll(v, new String[]
094        { "manifestId", });
095
096        v = new Vector<String>(2);
097        parameterNames.put("launch", v);
098        Collections.addAll(v, new String[]
099        { "manifestId", "url", });
100
101        v = new Vector<String>(2);
102        parameterNames.put("launchFilesInApp", v);
103        Collections.addAll(v, new String[]
104        { "manifestId", "files", });
105
106        v = new Vector<String>(1);
107        parameterNames.put("openCurrentPageInApp", v);
108        Collections.addAll(v, new String[]
109        { "manifestId", });
110
111        v = new Vector<String>(3);
112        parameterNames.put("changeAppUserSettings", v);
113        Collections.addAll(v, new String[]
114        { "manifestId", "linkCapturing", "displayMode", });
115    }
116
117
118    // ********************************************************************************************
119    // ********************************************************************************************
120    // Types - Static Inner Classes
121    // ********************************************************************************************
122    // ********************************************************************************************
123
124    /** If user prefers opening the app in browser or an app window. */
125    public static final String[] DisplayMode =
126    { "standalone", "browser", };
127    
128    /**
129     * The following types are the replica of
130     * https://crsrc.org/c/chrome/browser/web_applications/proto/web_app_os_integration_state.proto;drc=9910d3be894c8f142c977ba1023f30a656bc13fc;l=67
131     */
132    public static class FileHandlerAccept
133        extends BaseType
134        implements java.io.Serializable
135    {
136        /** For Object Serialization.  java.io.Serializable */
137        protected static final long serialVersionUID = 1;
138        
139        public boolean[] optionals()
140        { return new boolean[] { false, false, }; }
141        
142        /**
143         * New name of the mimetype according to
144         * https://www.iana.org/assignments/media-types/media-types.xhtml
145         */
146        public final String mediaType;
147        
148        /** <CODE>[No Description Provided by Google]</CODE> */
149        public final String[] fileExtensions;
150        
151        /**
152         * Constructor
153         *
154         * @param mediaType 
155         * New name of the mimetype according to
156         * https://www.iana.org/assignments/media-types/media-types.xhtml
157         * 
158         * @param fileExtensions -
159         */
160        public FileHandlerAccept(String mediaType, String[] fileExtensions)
161        {
162            // Exception-Check(s) to ensure that if any parameters which are not declared as
163            // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
164            
165            if (mediaType == null)      THROWS.throwNPE("mediaType");
166            if (fileExtensions == null) THROWS.throwNPE("fileExtensions");
167            
168            this.mediaType       = mediaType;
169            this.fileExtensions  = fileExtensions;
170        }
171        
172        /**
173         * JSON Object Constructor
174         * @param jo A Json-Object having data about an instance of {@code 'FileHandlerAccept'}.
175         */
176        public FileHandlerAccept (JsonObject jo)
177        {
178            this.mediaType       = ReadJSON.getString(jo, "mediaType", false, true);
179            this.fileExtensions = (jo.getJsonArray("fileExtensions") == null)
180                ? null
181                : RJArrIntoStream.strArr(jo.getJsonArray("fileExtensions"), null, 0).toArray(String[]::new);
182        
183        }
184        
185        
186        /** Checks whether {@code 'this'} equals an input Java-{@code Object} */
187        public boolean equals(Object other)
188        {
189            if (this == other)                       return true;
190            if (other == null)                       return false;
191            if (other.getClass() != this.getClass()) return false;
192        
193            FileHandlerAccept o = (FileHandlerAccept) other;
194        
195            return
196                    Objects.equals(this.mediaType, o.mediaType)
197                &&  Arrays.deepEquals(this.fileExtensions, o.fileExtensions);
198        }
199        
200        /** Generates a Hash-Code for {@code 'this'} instance */
201        public int hashCode()
202        {
203            return
204                    Objects.hashCode(this.mediaType)
205                +   Arrays.deepHashCode(this.fileExtensions);
206        }
207    }
208    
209    /** <CODE>[No Description Provided by Google]</CODE> */
210    public static class FileHandler
211        extends BaseType
212        implements java.io.Serializable
213    {
214        /** For Object Serialization.  java.io.Serializable */
215        protected static final long serialVersionUID = 1;
216        
217        public boolean[] optionals()
218        { return new boolean[] { false, false, false, }; }
219        
220        /** <CODE>[No Description Provided by Google]</CODE> */
221        public final String action;
222        
223        /** <CODE>[No Description Provided by Google]</CODE> */
224        public final PWA.FileHandlerAccept[] accepts;
225        
226        /** <CODE>[No Description Provided by Google]</CODE> */
227        public final String displayName;
228        
229        /**
230         * Constructor
231         *
232         * @param action -
233         * 
234         * @param accepts -
235         * 
236         * @param displayName -
237         */
238        public FileHandler(String action, PWA.FileHandlerAccept[] accepts, String displayName)
239        {
240            // Exception-Check(s) to ensure that if any parameters which are not declared as
241            // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
242            
243            if (action == null)      THROWS.throwNPE("action");
244            if (accepts == null)     THROWS.throwNPE("accepts");
245            if (displayName == null) THROWS.throwNPE("displayName");
246            
247            this.action       = action;
248            this.accepts      = accepts;
249            this.displayName  = displayName;
250        }
251        
252        /**
253         * JSON Object Constructor
254         * @param jo A Json-Object having data about an instance of {@code 'FileHandler'}.
255         */
256        public FileHandler (JsonObject jo)
257        {
258            this.action       = ReadJSON.getString(jo, "action", false, true);
259            this.accepts = (jo.getJsonArray("accepts") == null)
260                ? null
261                : RJArrIntoStream.objArr(jo.getJsonArray("accepts"), null, 0, PWA.FileHandlerAccept.class).toArray(PWA.FileHandlerAccept[]::new);
262        
263            this.displayName  = ReadJSON.getString(jo, "displayName", false, true);
264        }
265        
266        
267        /** Checks whether {@code 'this'} equals an input Java-{@code Object} */
268        public boolean equals(Object other)
269        {
270            if (this == other)                       return true;
271            if (other == null)                       return false;
272            if (other.getClass() != this.getClass()) return false;
273        
274            FileHandler o = (FileHandler) other;
275        
276            return
277                    Objects.equals(this.action, o.action)
278                &&  Arrays.deepEquals(this.accepts, o.accepts)
279                &&  Objects.equals(this.displayName, o.displayName);
280        }
281        
282        /** Generates a Hash-Code for {@code 'this'} instance */
283        public int hashCode()
284        {
285            return
286                    Objects.hashCode(this.action)
287                +   Arrays.deepHashCode(this.accepts)
288                +   Objects.hashCode(this.displayName);
289        }
290    }
291    
292    
293    // Counter for keeping the WebSocket Request ID's distinct.
294    private static int counter = 1;
295    
296    /**
297     * Returns the following OS state for the given manifest id.
298     * 
299     * @param manifestId 
300     * The id from the webapp's manifest file, commonly it's the url of the
301     * site installing the webapp. See
302     * https://web.dev/learn/pwa/web-app-manifest.
303     * 
304     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
305     * {@link Ret2}&gt;</CODE>
306     *
307     * <BR /><BR />This {@link Script} may be <B STYLE='color:red'>executed</B> (using 
308     * {@link Script#exec()}), and a {@link Promise} returned.
309     *
310     * <BR /><BR />When the {@code Promise} is <B STYLE='color: red'>awaited</B>
311     * (using {@link Promise#await()}), the {@code Ret2} will subsequently
312     * be returned from that call.
313     * 
314     * <BR /><BR />The <B STYLE='color: red'>returned</B> values are encapsulated
315     * in an instance of <B>{@link Ret2}</B>
316     *
317     * <BR /><BR /><UL CLASS=JDUL>
318     * <LI><CODE><B>Ret2.a:</B> Integer (<B>badgeCount</B>)</CODE>
319     *     <BR />-
320     *     <BR /><BR /></LI>
321     * <LI><CODE><B>Ret2.b:</B> {@link PWA.FileHandler}[] (<B>fileHandlers</B>)</CODE>
322     *     <BR />-
323     *     </LI>
324     * </UL>
325     */
326    public static Script<String, JsonObject, Ret2<Integer, PWA.FileHandler[]>> getOsAppState
327        (String manifestId)
328    {
329        // Exception-Check(s) to ensure that if any parameters which are not declared as
330        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
331        
332        if (manifestId == null) THROWS.throwNPE("manifestId");
333        
334        final int       webSocketID = 52000000 + counter++;
335        final boolean[] optionals   = { false, };
336        
337        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
338        String requestJSON = WriteJSON.get(
339            parameterTypes.get("getOsAppState"),
340            parameterNames.get("getOsAppState"),
341            optionals, webSocketID,
342            "PWA.getOsAppState",
343            manifestId
344        );
345        
346        // 'JSON Binding' ... Converts Browser Response-JSON into Java-Type 'Ret2'
347        Function<JsonObject, Ret2<Integer, PWA.FileHandler[]>>
348            responseProcessor = (JsonObject jo) -> new Ret2<>(
349                ReadBoxedJSON.getInteger(jo, "badgeCount", true),
350                (jo.getJsonArray("fileHandlers") == null)
351                    ? null
352                    : RJArrIntoStream.objArr(jo.getJsonArray("fileHandlers"), null, 0, PWA.FileHandler.class).toArray(PWA.FileHandler[]::new)
353            );
354        
355        return new Script<>(webSocketID, requestJSON, responseProcessor);
356    }
357    
358    /**
359     * Installs the given manifest identity, optionally using the given installUrlOrBundleUrl
360     * 
361     * IWA-specific install description:
362     * manifestId corresponds to isolated-app:// + web_package::SignedWebBundleId
363     * 
364     * File installation mode:
365     * The installUrlOrBundleUrl can be either file:// or http(s):// pointing
366     * to a signed web bundle (.swbn). In this case SignedWebBundleId must correspond to
367     * The .swbn file's signing key.
368     * 
369     * Dev proxy installation mode:
370     * installUrlOrBundleUrl must be http(s):// that serves dev mode IWA.
371     * web_package::SignedWebBundleId must be of type dev proxy.
372     * 
373     * The advantage of dev proxy mode is that all changes to IWA
374     * automatically will be reflected in the running app without
375     * reinstallation.
376     * 
377     * To generate bundle id for proxy mode:
378     * 1. Generate 32 random bytes.
379     * 2. Add a specific suffix 0x00 at the end.
380     * 3. Encode the entire sequence using Base32 without padding.
381     * 
382     * If Chrome is not in IWA dev
383     * mode, the installation will fail, regardless of the state of the allowlist.
384     * 
385     * @param manifestId -
386     * 
387     * @param installUrlOrBundleUrl 
388     * The location of the app or bundle overriding the one derived from the
389     * manifestId.
390     * <BR /><B CLASS=Opt>OPTIONAL</B>
391     * 
392     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
393     * {@link Ret0}&gt;</CODE>
394     *
395     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
396     * browser receives the invocation-request.
397     *
398     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
399     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
400     * {@code >} to ensure the Browser Function has run to completion.
401     */
402    public static Script<String, JsonObject, Ret0> install
403        (String manifestId, String installUrlOrBundleUrl)
404    {
405        // Exception-Check(s) to ensure that if any parameters which are not declared as
406        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
407        
408        if (manifestId == null) THROWS.throwNPE("manifestId");
409        
410        final int       webSocketID = 52001000 + counter++;
411        final boolean[] optionals   = { false, true, };
412        
413        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
414        String requestJSON = WriteJSON.get(
415            parameterTypes.get("install"),
416            parameterNames.get("install"),
417            optionals, webSocketID,
418            "PWA.install",
419            manifestId, installUrlOrBundleUrl
420        );
421        
422        // This Remote Command does not have a Return-Value.
423        return new Script<>
424            (webSocketID, requestJSON, VOID_RETURN.NoReturnValues);
425    }
426    
427    /**
428     * Uninstalls the given manifest_id and closes any opened app windows.
429     * 
430     * @param manifestId -
431     * 
432     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
433     * {@link Ret0}&gt;</CODE>
434     *
435     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
436     * browser receives the invocation-request.
437     *
438     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
439     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
440     * {@code >} to ensure the Browser Function has run to completion.
441     */
442    public static Script<String, JsonObject, Ret0> uninstall(String manifestId)
443    {
444        // Exception-Check(s) to ensure that if any parameters which are not declared as
445        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
446        
447        if (manifestId == null) THROWS.throwNPE("manifestId");
448        
449        final int       webSocketID = 52002000 + counter++;
450        final boolean[] optionals   = { false, };
451        
452        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
453        String requestJSON = WriteJSON.get(
454            parameterTypes.get("uninstall"),
455            parameterNames.get("uninstall"),
456            optionals, webSocketID,
457            "PWA.uninstall",
458            manifestId
459        );
460        
461        // This Remote Command does not have a Return-Value.
462        return new Script<>
463            (webSocketID, requestJSON, VOID_RETURN.NoReturnValues);
464    }
465    
466    /**
467     * Launches the installed web app, or an url in the same web app instead of the
468     * default start url if it is provided. Returns a page Target.TargetID which
469     * can be used to attach to via Target.attachToTarget or similar APIs.
470     * 
471     * @param manifestId -
472     * 
473     * @param url -
474     * <BR /><B CLASS=Opt>OPTIONAL</B>
475     * 
476     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
477     * String&gt;</CODE>
478     * 
479     * <BR /><BR />This <B>script</B> may be <B STYLE='color: red'>executed</B>, using
480     * {@link Script#exec()}, and afterwards, a {@link Promise}<CODE>&lt;JsonObject,
481     * String&gt;</CODE> will be returned.
482     *
483     * <BR /><BR />Finally, the <B>{@code Promise}</B> may be <B STYLE='color: red'>awaited</B>,
484     * using {@link Promise#await()}, <I>and the returned result of this Browser Function may
485      * may be retrieved.</I>
486     *
487     * <BR /><BR />This Browser Function <B STYLE='color: red'>returns</B>
488     * <BR /><BR /><UL CLASS=JDUL>
489     * <LI><CODE>String (<B>targetId</B></CODE>)
490     *     <BR />ID of the tab target created as a result.
491     * </LI>
492     * </UL> */
493    public static Script<String, JsonObject, String> launch(String manifestId, String url)
494    {
495        // Exception-Check(s) to ensure that if any parameters which are not declared as
496        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
497        
498        if (manifestId == null) THROWS.throwNPE("manifestId");
499        
500        final int       webSocketID = 52003000 + counter++;
501        final boolean[] optionals   = { false, true, };
502        
503        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
504        String requestJSON = WriteJSON.get(
505            parameterTypes.get("launch"),
506            parameterNames.get("launch"),
507            optionals, webSocketID,
508            "PWA.launch",
509            manifestId, url
510        );
511        
512        // 'JSON Binding' ... Converts Browser Response-JSON to 'String'
513        Function<JsonObject, String> responseProcessor = (JsonObject jo) ->
514            ReadJSON.getString(jo, "targetId", false, true);
515        
516        return new Script<>(webSocketID, requestJSON, responseProcessor);
517    }
518    
519    /**
520     * Opens one or more local files from an installed web app identified by its
521     * manifestId. The web app needs to have file handlers registered to process
522     * the files. The API returns one or more page Target.TargetIDs which can be
523     * used to attach to via Target.attachToTarget or similar APIs.
524     * If some files in the parameters cannot be handled by the web app, they will
525     * be ignored. If none of the files can be handled, this API returns an error.
526     * If no files are provided as the parameter, this API also returns an error.
527     * 
528     * According to the definition of the file handlers in the manifest file, one
529     * Target.TargetID may represent a page handling one or more files. The order
530     * of the returned Target.TargetIDs is not guaranteed.
531     * 
532     * TODO(crbug.com/339454034): Check the existences of the input files.
533     * 
534     * @param manifestId -
535     * 
536     * @param files -
537     * 
538     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
539     * String[]&gt;</CODE>
540     * 
541     * <BR /><BR />This <B>script</B> may be <B STYLE='color: red'>executed</B>, using
542     * {@link Script#exec()}, and afterwards, a {@link Promise}<CODE>&lt;JsonObject,
543     * String[]&gt;</CODE> will be returned.
544     *
545     * <BR /><BR />Finally, the <B>{@code Promise}</B> may be <B STYLE='color: red'>awaited</B>,
546     * using {@link Promise#await()}, <I>and the returned result of this Browser Function may
547      * may be retrieved.</I>
548     *
549     * <BR /><BR />This Browser Function <B STYLE='color: red'>returns</B>
550     * <BR /><BR /><UL CLASS=JDUL>
551     * <LI><CODE>String[] (<B>targetIds</B></CODE>)
552     *     <BR />IDs of the tab targets created as the result.
553     * </LI>
554     * </UL> */
555    public static Script<String, JsonObject, String[]> launchFilesInApp
556        (String manifestId, String[] files)
557    {
558        // Exception-Check(s) to ensure that if any parameters which are not declared as
559        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
560        
561        if (manifestId == null) THROWS.throwNPE("manifestId");
562        if (files == null)      THROWS.throwNPE("files");
563        
564        final int       webSocketID = 52004000 + counter++;
565        final boolean[] optionals   = { false, false, };
566        
567        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
568        String requestJSON = WriteJSON.get(
569            parameterTypes.get("launchFilesInApp"),
570            parameterNames.get("launchFilesInApp"),
571            optionals, webSocketID,
572            "PWA.launchFilesInApp",
573            manifestId, files
574        );
575        
576        // 'JSON Binding' ... Converts Browser Response-JSON to 'String[]'
577        Function<JsonObject, String[]> responseProcessor = (JsonObject jo) ->
578            (jo.getJsonArray("targetIds") == null)
579                ? null
580                : RJArrIntoStream.strArr(jo.getJsonArray("targetIds"), null, 0).toArray(String[]::new);
581        
582        return new Script<>(webSocketID, requestJSON, responseProcessor);
583    }
584    
585    /**
586     * Opens the current page in its web app identified by the manifest id, needs
587     * to be called on a page target. This function returns immediately without
588     * waiting for the app to finish loading.
589     * 
590     * @param manifestId -
591     * 
592     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
593     * {@link Ret0}&gt;</CODE>
594     *
595     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
596     * browser receives the invocation-request.
597     *
598     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
599     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
600     * {@code >} to ensure the Browser Function has run to completion.
601     */
602    public static Script<String, JsonObject, Ret0> openCurrentPageInApp(String manifestId)
603    {
604        // Exception-Check(s) to ensure that if any parameters which are not declared as
605        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
606        
607        if (manifestId == null) THROWS.throwNPE("manifestId");
608        
609        final int       webSocketID = 52005000 + counter++;
610        final boolean[] optionals   = { false, };
611        
612        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
613        String requestJSON = WriteJSON.get(
614            parameterTypes.get("openCurrentPageInApp"),
615            parameterNames.get("openCurrentPageInApp"),
616            optionals, webSocketID,
617            "PWA.openCurrentPageInApp",
618            manifestId
619        );
620        
621        // This Remote Command does not have a Return-Value.
622        return new Script<>
623            (webSocketID, requestJSON, VOID_RETURN.NoReturnValues);
624    }
625    
626    /**
627     * Changes user settings of the web app identified by its manifestId. If the
628     * app was not installed, this command returns an error. Unset parameters will
629     * be ignored; unrecognized values will cause an error.
630     * 
631     * Unlike the ones defined in the manifest files of the web apps, these
632     * settings are provided by the browser and controlled by the users, they
633     * impact the way the browser handling the web apps.
634     * 
635     * See the comment of each parameter.
636     * 
637     * @param manifestId -
638     * 
639     * @param linkCapturing 
640     * If user allows the links clicked on by the user in the app's scope, or
641     * extended scope if the manifest has scope extensions and the flags
642     * {@code DesktopPWAsLinkCapturingWithScopeExtensions} and
643     * {@code WebAppEnableScopeExtensions} are enabled.
644     * 
645     * Note, the API does not support resetting the linkCapturing to the
646     * initial value, uninstalling and installing the web app again will reset
647     * it.
648     * 
649     * TODO(crbug.com/339453269): Setting this value on ChromeOS is not
650     * supported yet.
651     * <BR /><B CLASS=Opt>OPTIONAL</B>
652     * 
653     * @param displayMode -
654     * <BR /><B CLASS=Opt>OPTIONAL</B>
655     * 
656     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
657     * {@link Ret0}&gt;</CODE>
658     *
659     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
660     * browser receives the invocation-request.
661     *
662     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
663     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
664     * {@code >} to ensure the Browser Function has run to completion.
665     */
666    public static Script<String, JsonObject, Ret0> changeAppUserSettings
667        (String manifestId, Boolean linkCapturing, String displayMode)
668    {
669        // Exception-Check(s) to ensure that if any parameters which are not declared as
670        // 'Optional', but have a 'null' value anyway, that a NullPointerException shall throw.
671        
672        if (manifestId == null) THROWS.throwNPE("manifestId");
673        
674        // Exception-Check(s) to ensure that if any parameters which must adhere to a
675        // provided List of Enumerated Values, fails, then IllegalArgumentException shall throw.
676        
677        THROWS.checkIAE("displayMode", displayMode, "PWA.DisplayMode", PWA.DisplayMode);
678        
679        final int       webSocketID = 52006000 + counter++;
680        final boolean[] optionals   = { false, true, true, };
681        
682        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
683        String requestJSON = WriteJSON.get(
684            parameterTypes.get("changeAppUserSettings"),
685            parameterNames.get("changeAppUserSettings"),
686            optionals, webSocketID,
687            "PWA.changeAppUserSettings",
688            manifestId, linkCapturing, displayMode
689        );
690        
691        // This Remote Command does not have a Return-Value.
692        return new Script<>
693            (webSocketID, requestJSON, VOID_RETURN.NoReturnValues);
694    }
695    
696}