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

Delay activation on initial loading; src="" changes reset portal BC #253

Merged
merged 4 commits into from
Sep 28, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,67 @@ An additional reason for avoiding these mechanisms is that it makes writing port

To conclude, instead of giving embedders this control as iframes do, we believe that the browser can take the role of mitigating any problematic features. For example, instead of requiring embedders to use `sandbox=""` to turn off modal `alert()`/`confirm()`/`prompt()` dialogs, or permissions policy to turn off autoplaying media, those features are [always disabled](https://github.com/WICG/portals#other-restrictions-while-portaled) in pre-activation portals. And because portals are isolated from communicating with their embedder pre-activation, any problems which CSP Embedded Enforcement would attempt to protect against will instead be caught by this communications barrier and prevented from impacting the embedder.

### Activation

The basics of activation are explained [in the intro example](#example): calling `portalElement.activate()` causes the embedding window to navigate to the content which is already loaded into the portal. This section discusses some of the subtler details.

First, note that a portal may be in a "closed" state, when it is not displaying valid, activatable content. This could happen for several reasons:

- The host page author has incorrectly set the portal to a non-HTTP(S) URL, e.g. using `<portal src="data:text/html,hello"></portal>`. Portals can only display HTTP(S) URLs.
- The portaled page cannot be loaded, for reasons outside of the host page author's control. For example, if the portaled content does a HTTP redirect to a `data:` URL, or if the portaled content gives a network error.
- The user is offline, which also causes a network error.

(What, exactly, the `<portal>` element displays in this state is still under discussion: [#251](https://github.com/WICG/portals/issues/251).)

Attempting to activate a closed portal will fail. Activation can also fail if another navigation is in progress, as discussed [above](#session-history-navigation-and-bfcache). In all of these cases, the promise returned by the `activate()` method will be rejected, allowing page authors to gracefully handle the failure with a custom error experience.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a little worried that portal activation rejections can tell you something about a page that you didn't already know, but we can work on that in issues.


Another consideration is how activation behaves when the portal is currently loading content. This breaks down into two cases:

- During the initial load of content into a portal, e.g. given

```js
const portal = document.createElement("portal");
portal.src = "https://slow.example.com/";
document.body.append(portal);
portal.activate();
```

the promise returned by `activate()` will not settle until the navigation is far enough along to determine whether or not it will be successful. This requires waiting for the response to start arriving, to ensure there are no network errors and that the final response URL is a HTTP(S) URL. Once it reaches that point, then the promise will fulfill or reject appropriately. If the promise fulfills, then activation will have completed, and the content will be loading into the newly-activated browsing context. If it rejects, then no activation will have occurred.

- After the initial load of the portal, e.g. given

```js
const portal = getSomeExistingFullyLoadedPortal();
portal.src = "https://different-url.example.com/";
portal.activate();
```

activation of the already-loaded content will happen immediately, and the navigation to the new content will happen in the newly-activated browsing context. In these cases, the promise returned by `activate()` will generally fulfill, as it is almost always possible to activate the already-loaded content. (The exceptions are edge cases like if another user-initiated navigation, or another portal activation, is already ongoing.)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this bypasses CSP rules, but I guess we can continue debating it in the issues.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, didn't we decide that assigning src made a new browsing context? If so, I'd have expected that browsing context to be used, and the old one to be detached immediately when src is assigned. That is, I would expect activate to send you to the last URL assigned to src (+ redirects).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, I forgot about that consideration. I do agree it'll be simpler to just discard when src="" is assigned. OK, so the example I'm really thinking of is an in-page navigation.

This will require a reasonably-big update to this PR. And I'll try to update the spec with discard-on-src-change as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which context the navigation occurs in depends on how we want to interpret activate in this case.
If activate cancels the ongoing navigation in the portal's new context and restarts it in the activated context at the top level, then yes it happens in the newly-activated browsing context.
If activate promotes both the existing context and the context with the pending navigation to the top level, then the navigation continues in said context. When it matures, we navigate the top level to the new context.

That said, that's probably excessive detail for the purposes of this section. The specifics of this are more likely to affect things like CSP, as Jake mentioned. I might just rephrase this to "the navigation to the new content will happen at the top level."


Combined, these behaviors allow authors to write fairly simple code to activate and handle errors. For example, consider a page which wants to use portals to create an [InstantClick](http://instantclick.io/)-like experience, prerendering the content of a link on hover, and activating it onclick. This could look something like the following:

```js
const portal = document.createElement("portal");
portal.src = aElement.href;
portal.hidden = true;

aElement.onmouseover = () => {
document.body.append(portal);
};

aElement.onclick = async e => {
e.preventDefault();

try {
await portal.activate()
} catch (e) {
// Show a custom error toast.
// Or maybe just load the error page/non-HTTP(S) URL,
// using location.href = aElement.href;
}
};
```

## Summary of differences between portals and iframes

Portals are somewhat reminiscent of iframes, but are different in enough significant ways that we propose them as a new element.
Expand Down