Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enqueue launch events for every app launch #33

Closed
alancutter opened this issue Jun 23, 2021 · 20 comments
Closed

Enqueue launch events for every app launch #33

alancutter opened this issue Jun 23, 2021 · 20 comments

Comments

@alancutter
Copy link
Collaborator

alancutter commented Jun 23, 2021

In the same vein as w3c/web-share-target#84 (comment):

We can just enqueue the launch event always without any manifest API changes.

I propose that we always enqueue the launch event for any app launch (link capturing, OS app launch, menu shortcuts, file handlers, share target...).

What does this mean for existing-client-navigate vs existing-client-event? It removes the "event" aspect and leaves behind "navigation".
If we always enqueue launch events then the behavioural options for the developer become "which app window gets used" and "whether the browser automatically navigates the app window to the launch URL".

Possible restructuring of the API:

"launch_handler": {
  "route_to": "new-client" | "existing-client" | "service-worker",
  "navigate_client": true | false
},
"new_client_url": null | <in-scope url>

new_client_url defaults to start_url if unset/null.

This decomposes the "launching" of the app into two configurable pipeline steps:

  • route_to
    Choose which app client to launch in (possibly new).

    • new-client (default): Opens a new app client to receive the launch.
    • existing-client: Selects the most recently used app client to receive the launch if any are open otherwise does the same as new-client.
    • service-worker: Fires an event in the app service worker, provides it an API to select a client to use to receive the launch. Does the same as new-client if no client selected.
  • navigate_client
    Choose whether to navigate that window to the launch URL or let JS handle that.

    • true (default): App clients that receive a launch get navigated to the launch URL before having the launch event enqueued in them (launch URL not provided).
    • false: New app clients are opened at new_client_url. App clients that receive a launch have a launch event enqueued containing the launch URL.
@alancutter
Copy link
Collaborator Author

It would be reasonable for the service worker path to override the navigate_client behaviour when it selects a client.

@mgiuca
Copy link
Member

mgiuca commented Jun 23, 2021

I think this is a reasonable take.

I'm not sure what the point of new_client_url is. If I set it to something other than the default, then when would start_url ever be used? Isn't this just ... the same as start URL?

@alancutter
Copy link
Collaborator Author

alancutter commented Jun 23, 2021

start_url will be the launch URL for OS app list launches. new_client_url is for sites that want a routing page to process launch event URLs that's more lightweight than their start_url. It's not a necessary component of this proposal though.

@mgiuca
Copy link
Member

mgiuca commented Jun 24, 2021

I'm really liking this. It's good to periodically rethink a design that was rather hastily put together and hasn't really been questioned since I wrote it.

Things that I like about it:

  • Always firing the launch event (rather than only doing it in the *-event modes) is consistent with how the web's event model works: events always fire, you don't need to specifically enable them, you just either listen for them or ignore them.
  • Framing it as a "launch" handler makes it clear that it's covering all sorts of launches, not just clicking on links -- something which was overlooked when we named capture_links.
  • Makes it a bit easier to reason about existing-client-navigate versus existing-client-event. First, you decide whether to find an existing client or open a new one, then you decide whether to force a navigation or let an event handler act.
  • It brings us closer to having the elusive Service Worker launch event designed. A lot of the design decisions we were debating about are already resolved by the fact that navigate-client is a separate choice.

Some other thoughts:

  • "new-client" + false is a pretty weird thing. (When the user navigates to a specific page, open a different URL and fire an event at it.) It does have utility; it gives sites a lot of control over what URLs can be navigated to in the window context. But it's a weird thing nonetheless.
  • I now understand what new_client_url is for (distinct from start_url). For example, you could use it with "new-client" + false which would mean when the all is launched normally, navigate to new_client_url but send an event containing start_url. But again, "new-client" + false is a weird thing and I'm not sure we need a feature specifically designed around it. Maybe new_client_url doesn't need to be in the initial version.
  • I assume the default value for launch_handler is the equivalent of no link capturing. But is it bad that there's no way to express that default explicitly in the value of route_to?
  • The default value for navigate_client should be true, which gives "mostly normal" behaviour consistent with how the web already works (without needing to write JS event handlers).

For completeness, pasting a private email from @alancutter here, which explains the mapping from the previous design to the above proposal:

  • "capture_links": "new-client"
    • => "launch_handler": { "route_to": "new-client", "navigate_client": true }
    • navigate_client can be omitted (defaults to true).
  • "capture_links": "existing-client-navigate"
    • => "launch_handler": { "route_to": "existing-client", "navigate_client": true }
    • navigate_client can be omitted (defaults to true).
  • "capture_links": "existing-client-event"
    • => "launch_handler": { "route_to": "existing-client", "navigate_client": false }
  • "capture_links": "serviceworker"
    • => "launch_handler": { "route_to": "service-worker", "navigate_client": true | false | <service worker may override> }

@alancutter
Copy link
Collaborator Author

alancutter commented Jun 24, 2021

I assume the default value for launch_handler is the equivalent of no link capturing

In Chrome's implementation there is no such thing as no link capturing. It's a user choice that the site has no control over (similar to users forcing web apps to open in a window/browser tab).
I would reserve this kind of link capturing site configuration to the capture_links manifest field. launch_handler doesn't know about link capturing, link capturing is an app launch callsite that invokes launch handling.

@dmurph
Copy link
Collaborator

dmurph commented Jun 24, 2021

I like this structure.

Questions:

  • Do we imagine that developers want the same settings for all launch events (share, file handlers, etc)? If not, how would we want the API to look to accomplish this?
  • Can you put examples of things this API CAN specify that the old one can't?

@alancutter
Copy link
Collaborator Author

alancutter commented Jun 25, 2021

Do we imagine that developers want the same settings for all launch events (share, file handlers, etc)? If not, how would we want the API to look to accomplish this?

If we want to provide that granularity I envision just embedding this launch_handler structure in whatever launch surface API wants it to override the general launch_handler behaviour.

"capture_links": {
  "exclude_paths": ["/example"],
  "launch_handler: { "route_to": "existing-client" }
}

Can you put examples of things this API CAN specify that the old one can't?

The old one can't:

  • Specify a behaviour to use should the serviceworker handler do nothing.
  • Block navigation for new-client (though could have easily been added).

This change is less about adding more capabilities and more about decoupling orthogonal concepts from the original DLC concept.

@alancutter
Copy link
Collaborator Author

Great suggestion from glenrob: Remove "route_to": "service-worker" and just always fire the service worker event. The route_to value is what to do if the service worker noops (same for navigate_client).

This makes the service worker event a way of "explaining" the declarative behaviour as a more procedural browser pipeline primitive.

@phoglenix
Copy link

So IIUC there are now 4 possible behaviours (please correct me if I'm describing it wrong):

  1. route_to: new-client + navigate_client: true - default+existing behaviour: launches a new instance and navigates to URL
  2. route_to: existing-client + navigate_client: true - "simple single window" behaviour: navigates an existing client to URL
  3. route_to: new-client + navigate_client: false - opens new_client_url and fires an event at it
  4. route_to: existing-client + navigate_client: false - "real app-y" behaviour: fires an event at an existing client

It seems to me that 3 and 4 would be better handled by a service worker event - less overhead and no declaration needed assuming we always fire the SW event, and able to customise the behaviour based on what URL is coming in.
What is the advantage of firing the event inside a client instead?

@mgiuca
Copy link
Member

mgiuca commented Jun 25, 2021

So there would still be capture_links? For path overrides, and potentially for putting link-capturing-specific launch_handler in there as well?

In Chrome's implementation there is no such thing as no link capturing. It's a user choice that the site has no control over (similar to users forcing web apps to open in a window/browser tab).

OK, so then what is the default value of launch_handler?

It seems that in Chrome's implementation, launch_handler: undefined or {} will have different behaviour to any explicit value in route_to, right? So maybe even though it's implementation-specific whether capturing happens or not, you are still providing a hint to the browser that you want capturing, by specifying route_to.

@alancutter
Copy link
Collaborator Author

alancutter commented Jun 25, 2021

It seems to me that 3 and 4 would be better handled by a service worker event

This would make "navigate_client": false behaviour only accessible from the service worker launch event handler.
I think existing-client-event is a common enough desire to warrant a declarative way of getting it without needing SW code. It's important for apps that don't want to lose existing page state.

So there would still be capture_links? For path overrides, and potentially for putting link-capturing-specific launch_handler in there as well?

Yes, if we have any link capturing specific configurations we want to provide e.g. excluding sub paths and link-capture-specific launch_handler as you said.

OK, so then what is the default value of launch_handler?

It should probably be null (or whatever the manifest's equivalent of CSS auto is) so the user agent can have its own default appropriate for the platform its running on (e.g. Android would only support "existing-client").

@mgiuca
Copy link
Member

mgiuca commented Jun 25, 2021

I think existing-client-event is a common enough desire to warrant a declarative way of getting it without needing SW code. It's important for apps that don't want to lose existing page state.

Yes. This was essentially the whole insight behind DLC in the first place, as an evolution on the older sw-launch plans (see the Overview section of the DLC explainer):

sw-launch has been proposed for a number of years and has never made it past a proposal stage, largely due to the complexity involved in both spec and implementation (a complex effort spanning PWAs, service workers, and HTML navigation stack).

We found that almost all launch use cases could be covered by a handful of fixed rules (for example, "choose an existing window in the same app, focus it, and navigate it to the launched URL"). Thus, this proposal, "Declarative Link Capturing", allows sites to choose one of those fixed rules without having to implement custom launch event logic, which should satisfy most use cases, and simplify the implementation in the browser and in all the sites. We leave open the option of expanding into the full launch event later on.

It's not only what we think will be the overwhelmingly common case, but it also means we can ship it without having to do a major implementation project on the guts of the navigation flow (& that follows for all the browser engines).

@phoglenix
Copy link

alancutter@ and I chatted about this. I can see the value in the decomposition into 2x2 properties now. They make more sense than the old 3 names (new client navigate (default), existing client event, existing client navigate) in the context that we are always sending the event.

My only remaining note is that "3. route_to: new-client + navigate_client: false" is quite an odd option, and most likely to be handled better by a service worker eventually, so we should avoid adding new_client_url to support that case, just use start_url.

I think the service worker approach wouldn't be much more complex for sites to implement but I can see how it would be more complex for the browser if we have to drop in to SW code to check if the event is handled during the navigation. Possibly we'd want to make that part declarative somehow. This is just musing though. The proposal (except new_client_url) LGTM.

@mgiuca
Copy link
Member

mgiuca commented Jul 7, 2021

@phoglenix Re new_client_url: that was my line of thinking in this comment as well:

But again, "new-client" + false is a weird thing and I'm not sure we need a feature specifically designed around it. Maybe new_client_url doesn't need to be in the initial version.

But I thought Alan explained (must've been privately): new_client_url isn't just used for new_client. It's also used for existing_client when there's no existing client. So it makes sense to have "existing_client" + false (which we expect to be common: it's the new equivalent of existing_client_event), but still have the common case where no window is open, we would spawn a new window at new_client_url. I think most sites will just want this to open start_url, but there is a case for having a lightweight page at new_client_url that does routing to whichever page is being requested, which may be start_url or it could be a share target, shortcut URL, etc. So I definitely see the value of new_client_url.

Having said that, I'm fine if it doesn't appear in the MVP. It isn't crucial.

@alancutter
Copy link
Collaborator Author

I think start_url was intended for use by "app launched from OS app launch surface", I don't think it's very relevent for any of the other ~6 ways of launching a web app. IMO the start_url member is a conceptual sibiling to share_target, new_note_url, shortcuts etc in that it's just configuring its own particular app launch entrypoint.

@mgiuca
Copy link
Member

mgiuca commented Jul 7, 2021

Yes, I agree, in theory / conceptually @alancutter .

However, in practice, I think the start_url is going to be the "main page" of an app, which most apps will want to load before they show any specific content for subpages. I think for the first version, we can always load start_url, then we can always add new_client_url later, with a default value of start_url.

Edit: Which I see you have done in the explainer (NCU is listed as a future work).

@phoglenix
Copy link

I think if you want a lightweight page to fire an event at, you're better off with a service worker. new_client_url seems like an awkward midpoint: not necessary for the initial version and not desirable for the ideal version.

@mgiuca
Copy link
Member

mgiuca commented Jul 7, 2021

Well let's talk practical terms.

Say you've got a video chat client with two pages:

  1. The home page that lets users enter a meeting ID, and
  2. The page where meetings take place.

Let's assume both of these pages load non-trivial resources that are unique to that page and not the other. And you want the single-window mode option so if you start a new video call, you focus the existing window and show a dialog asking if the user wants to switch to the new call.

I think in this case the ideal architecture is:

  • The start_url is the home page.
  • Launch handler is {"route_to": "existing-client", "navigate-client": false}.
  • You probably won't want to use "navigate-client": "true", because then you have no control over the dialog that the browser shows asking the user if they want to navigate.
  • Now when you link capture from a meeting URL, when there's no existing window open, you'll want to avoid loading the home page and go straight to the meeting page. But you can't do that if the browser always loads the start_url first. So we need new_client_url to be a lightweight page that can decide which to navigate to.

In light of that ... maybe new_client_url is actually needed in the MVP? Or ......

Hurr.... okay so actually, I think this has introduced a bit of a design flaw, @alancutter , when compared to my DLC proposal. In the DLC proposal, existing_client_event would not have navigated to start_url if no window was open; it would just directly open the intended URL. Basically, if no window is open, it behaves as if "navigate-client": true, but if a window is open, it behaves as if "navigate-client": false. There's no way to get that in the new design. Which leads to the above architecture, which let's face it, is pretty complex to reason about and implement (you need to build another lightweight page, which as we've seen with install URLs, is pretty difficult for complex sites, even if it's basically just blank HTML).

So maybe what we need is for navigate-client to be three-valued:

  • "always"
  • "never"
  • "if-opening"
  • (and "auto")

(As a bonus, it removes the awkward "string or Boolean" type that you had with "auto", true and false.)

Now we can map the previous DLC proposal onto the new one:

  • "capture_links": "new-client"
    • => "launch_handler": { "route_to": "new-client", "navigate_client": "always" }
  • "capture_links": "existing-client-navigate"
    • => "launch_handler": { "route_to": "existing-client", "navigate_client": "always" }
  • "capture_links": "existing-client-event"
    • => "launch_handler": { "route_to": "existing-client", "navigate_client": "if-opening" }

Then maybe we don't need new_client_url. It would only be used for navigate_client: never, and TBH I'm not sure what you'd ever want that for, though for completeness I'd be OK with including it.

@alancutter
Copy link
Collaborator Author

In CSS spec land they don't let you use true/false, you have to use enum values like visibility: visible and for good reason; booleans are not extensible. I think switching to "always" etc. is a good move and I'm on board with capturing the more complex "navigate if client is new" behaviour.

@alancutter
Copy link
Collaborator Author

This was added to the explainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants