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

Support user contexts #570

Merged
merged 44 commits into from
Jan 22, 2024
Merged
Changes from 38 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5259fa5
WIP partitions
OrKoN Oct 10, 2023
8cee793
Update index.bs
OrKoN Oct 10, 2023
84db4ee
Rename to UserContext; add a note about User Agents
OrKoN Oct 30, 2023
d8ffee7
define getUserContexts
OrKoN Oct 30, 2023
44341cf
Add jgraham's suggestions
OrKoN Oct 30, 2023
8ab3f0b
draft browser.closeUserContext
OrKoN Oct 30, 2023
4a4a0ca
draft create user context
OrKoN Oct 30, 2023
59e608e
add types and commands
OrKoN Nov 8, 2023
5f98005
trying to improve the definitions
OrKoN Nov 8, 2023
f7c612d
fix the outline
OrKoN Nov 8, 2023
a7a71a2
Update index.bs
OrKoN Nov 9, 2023
1a0c963
update steps
OrKoN Nov 9, 2023
aa2c0ec
Update index.bs
OrKoN Nov 27, 2023
59d2c51
Update index.bs
OrKoN Nov 27, 2023
32b8efd
Update index.bs
OrKoN Nov 27, 2023
4733bd5
clarify how default contexts work
OrKoN Nov 27, 2023
ff8b3f1
fix types
OrKoN Nov 27, 2023
45aa6db
remove null from userContext
OrKoN Dec 8, 2023
fc94b9f
update undefined to null
OrKoN Dec 8, 2023
3e7a0ec
update undefined to null
OrKoN Dec 8, 2023
5816e9e
Update index.bs
jrandolf-2 Dec 18, 2023
bc19673
Use `removeUserContext`
jrandolf-2 Dec 18, 2023
4a4aaa6
Address comments
jrandolf-2 Dec 18, 2023
ded90b6
Update index.bs
jrandolf-2 Dec 18, 2023
2c0c421
Update wording.
jrandolf-2 Dec 18, 2023
a30c574
review comments
OrKoN Jan 4, 2024
9342771
replace user agent with remote end
OrKoN Jan 4, 2024
d4976cf
fix cddl
OrKoN Jan 4, 2024
4f93268
fix phrasing
OrKoN Jan 4, 2024
492089e
Remove GetUserContexts
OrKoN Jan 5, 2024
4e1af81
no such context error
OrKoN Jan 12, 2024
49f7ba2
Use reference context's user context
OrKoN Jan 12, 2024
7f221d1
Prevent removal of the default context
OrKoN Jan 15, 2024
e384195
no such context error
OrKoN Jan 12, 2024
af0d0c2
remove duplicate error
OrKoN Jan 18, 2024
8a2bb4b
fix formatting
OrKoN Jan 18, 2024
2d1a835
Update the definition of user contexts
jgraham Jan 17, 2024
93acadb
Add a browser.getUserContext command
jgraham Jan 17, 2024
88196bd
Fix UserContextInfo
OrKoN Jan 18, 2024
3542e7d
Apply suggestions to browsingContext creation
OrKoN Jan 18, 2024
1fcda96
Reuse UserContextInfo
OrKoN Jan 19, 2024
0db4d0d
Extract the get user context algorithm
OrKoN Jan 19, 2024
0336819
Fix userContextId
OrKoN Jan 19, 2024
253d3eb
Add missing +BrowserResult
OrKoN Jan 19, 2024
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
278 changes: 265 additions & 13 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@ WebDriver BiDi extends the set of [=error codes=] from [[WEBDRIVER|WebDriver]]
with the following additional codes:

<dl>
<dt><dfn>invalid user context id</dfn>
<dd>Tried to use a [=user context=] that contradicts the provided reference context.

<dt><dfn>no such handle</dfn>
<dd>Tried to deserialize an unknown <code>RemoteObjectReference</code>.

Expand All @@ -598,6 +601,9 @@ with the following additional codes:
<dt><dfn>no such storage partition</dfn>
<dd>Tried to access data in a non-existent storage partition.

<dt><dfn>no such user context</dfn>
<dd>Tried to reference an unknown [=user context=].

<dt><dfn>unable to close browser</dfn>
<dd>Tried to close the browser, but failed to do so.

Expand All @@ -614,6 +620,7 @@ with the following additional codes:
<pre class="cddl local-cddl">
ErrorCode = "invalid argument" /
"invalid session id" /
"invalid user context id" /
"move target out of bounds" /
"no such alert" /
"no such element" /
Expand All @@ -625,6 +632,7 @@ ErrorCode = "invalid argument" /
"no such request" /
"no such script" /
"no such storage partition" /
"no such user context" /
"session not created" /
"unable to capture screen" /
"unable to close browser" /
Expand Down Expand Up @@ -1291,6 +1299,64 @@ access to that data in a {{Window}} global.

Issue: Define how this works.

# User Contexts # {#user-contexts}

A <dfn>user context</dfn> represents a collection of zero or more
[=/top-level traversables=] within a [=remote end=]. Each [=user context=] has
an associated [=storage partition=], so that [=remote end=] data is not shared
between different [=user contexts=].

Issue: Unclear that this is the best way to formally define the concept of a
user context or the interaction with storage.

Note: The infra spec uses the term "user agent" to refer to the same concept as
[=user context|user contexts=]. However, this is not compatible with usage of
the term "user agent" to mean the entire web client with multiple [=user
context|user contexts=]. Although this difference is not visible to web content,
it is observed via WebDriver, so we avoid using this terminology.

A [=user context=] has a <dfn>user context id</dfn>, which is a unique string
set upon the user context creation.

A [=navigable=] has an <dfn>associated user context</dfn>, which is a
[=user context=].

When a new [=/top-level traversable=] is created its [=associated user context=]
is set to a user context in the [=set of user contexts=].

Note: In some cases the user context is set by specification when the
[=/top-level traversable=] is created, however in cases where no such
requirements are present, the [=associated user context=] for a [=/top-level
traversable=] is implemenation-defined.

Issue: Should we specify that [=/top-level traversables=] with a non-null
opener have the same [=associated user context=] as their opener?
Need to check if this is something existing implementations enforce.

A [=child navigable=]'s [=associated user context=] is it's
[=navigable/parent=]'s [=associated user context=].

A [=user context=] which isn't the [=associated user context=] for any
[=/top-level traversable=] is an <dfn>empty user context</dfn>.

The <dfn>default user context</dfn> is a [=user context=] with [=user context
id=] <code>"default"</code>.

An implementation has a <dfn>set of user contexts</dfn>, which is a [=/set=] of
[=user contexts=]. Initially this contains the [=default user context=].

Implementations may [=set/append=] new [=user contexts=] to the [=set of user
contexts=] at any time, for example in response to user actions.

Note: "At any time" here includes during implementation startup, so a given
implementation might always have multiple entries in the [=set of user contexts=].

Implementations may [=set/remove=] any [=empty user context=], with exception of
the [=default user context=], from the [=set of user contexts=] at any
time. However they are not required to remove such [=user contexts=]. [=User
contexts=] that are not [=empty user contexts=] must not be removed from the
[=set of user contexts=].

# Modules # {#modules}

## The session Module ## {#module-session}
Expand Down Expand Up @@ -1865,7 +1931,10 @@ managing the remote end browser process.

<pre class="cddl remote-cddl">
BrowserCommand = (
browser.Close
browser.Close //
browser.CreateUserContext //
browser.GetUserContexts //
browser.RemoveUserContext
)
</pre>

Expand All @@ -1876,6 +1945,27 @@ BrowserCommand = (
</pre>


### Types ### {#module-browser-types}

#### The browser.UserContext Type #### {#type-browser-UserContext}

<pre class="cddl remote-cddl local-cddl">
browser.UserContext = text;
</pre>

The <code>browser.UserContext</code> unique identifies a [=user context=].

#### The browser.UserContextInfo Type #### {#type-browser-UserContextInfo}

<pre class="cddl remote-cddl local-cddl">
browser.UserContextInfo = (
userContext: browser.UserContext
)
</pre>

The <code>browser.UserContextInfo</code> type represents properties of a [=user
context=].

### Commands ### {#module-browser-commands}

#### The browser.close Command #### {#command-browser-close}
Expand Down Expand Up @@ -1936,9 +2026,6 @@ The [=remote end steps=] with |session| and <var ignore>command parameters</var>

1. [=Close=] any [=top-level browsing contexts=] without [=prompting to unload=].

Note: This implicitly only affects browsing contexts that were under
automation in the first place.

1. Perform implementation defined steps to clean up resources associated with
the [=remote end=] under automation.

Expand All @@ -1951,6 +2038,149 @@ The [=remote end steps=] with |session| and <var ignore>command parameters</var>

</div>

#### The browser.createUserContext Command #### {#command-browser-createUserContext}

The <dfn export for=commands>browser.createUserContext</dfn> command creates a
[=user context=].

<dl>
<dt>Command Type</dt>
<dd>
<pre class="cddl remote-cddl">
browser.CreateUserContext = (
method: "browser.createUserContext",
params: EmptyParams,
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
)
</pre>
</dd>
<dt>Return Type</dt>
<dd>
<pre class="cddl local-cddl">
browser.createUserContextResult = {
userContext: browser.UserContext
}
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
</pre>
</dd>
</dl>

<div algorithm="remote end steps for browser.createUserContext">

The [=remote end steps=] are:

1. Let |user context| be a new [=user context=].

1. [=set/Append=] |user context| to the [=set of user contexts=].

1. Let |result| be a [=/map=] matching the
<code>browser.createUserContextResult</code> production with the
<code>userContext</code> field set to |user context|'s [=user context id=].

1. Return [=success=] with data |result|.

</div>


#### The browser.getUserContexts Command #### {#command-browser-getUserContexts}

The <dfn export for=commands>browser.getUserContexts</dfn> command returns a
list of [=user context=]s.

<dl>
<dt>Command Type</dt>
<dd>
<pre class="cddl remote-cddl">
browser.GetUserContexts = (
method: "browser.getUserContexts",
params: EmptyParams,
)
</pre>
</dd>
<dt>Return Type</dt>
<dd>
<pre class="cddl local-cddl">
browser.getUserContextsResult = {
userContexts: [ + browser.UserContextInfo]
}
</pre>
</dd>
</dl>

<div algorithm="remote end steps for browser.getUserContexts">

The [=remote end steps=] are:

1. Let |user contexts| be an empty [=/list=].

1. For each |user context| in the [=set of user contexts=]:

1. Let |user context info| be a [=/map=] matching the
<code>browser.UserContextInfo</code> production with the
<code>userContext</code> field set to |user context|'s [=user context id=].

1. [=list/Append=] |user context info| to |user contexts|.

1. Let |result| be a [=/map=] matching the
<code>browser.getUserContextsResult</code> production with the
<code>userContexts</code> field set to |user contexts|.

1. Return [=success=] with data |result|.

</div>


#### The browser.removeUserContext Command #### {#command-browser-removeUserContext}

The <dfn export for=commands>browser.removeUserContext</dfn> command closes a
user context and all browsing contexts in it without running
<code>beforeunload</code> handlers.

<dl>
<dt>Command Type</dt>
<dd>
<pre class="cddl remote-cddl">
browser.RemoveUserContext = (
method: "browser.removeUserContext",
params: {
userContext: browser.UserContext
},
)
</pre>
</dd>
<dt>Return Type</dt>
<dd>
<pre class="cddl">
EmptyResult
</pre>
</dd>
</dl>

<div algorithm="remote end steps for browser.removeUserContext">

The [=remote end steps=] with |command parameters| are:

1. Let |user context id| be |command parameters|["<code>userContext</code>"].

1. If |user context id| is <code>"default"</code>, return [=error=] with [=error
code=] [=invalid argument=].

1. For each |user context| in the [=set of user contexts=]:

1. If |user context|'s [=user context id=] is |user context id|:

1. For each [=/top-level traversable=] |navigable|:

1. If |navigable|'s [=associated user context=] is |user context|:

1. [=Close=] |navigable| without [=prompting to unload=].

1. [=set/Remove=] |user context| for the [=set of user contexts=].

1. Return [=success=] with data null.

1. Return [=error=] with [=error code=] [=no such user context=].

</div>

## The browsingContext Module ## {#module-browsingContext}

The <dfn export for=modules>browsingContext</dfn> module contains commands and
Expand Down Expand Up @@ -2065,9 +2295,10 @@ To <dfn export>get a browsing context</dfn> given |context id|:
browsingContext.InfoList = [*browsingContext.Info]

browsingContext.Info = {
children: browsingContext.InfoList / null,
context: browsingContext.BrowsingContext,
url: text,
children: browsingContext.InfoList / null
userContext: browser.UserContext
? parent: browsingContext.BrowsingContext / null,
}
</pre>
Expand Down Expand Up @@ -2142,18 +2373,22 @@ To <dfn>get the browsing context info</dfn> given |context|,

1. Set |child infos| to an empty [=/list=].

1. For each |context| of |child contexts|:
1. For each |child context| of |child contexts|:

1. Let |info| be the result of [=get the browsing context info=] given
|context|, |child depth|, and false.
|child context|, |child depth|, and false.

1. Append |info| to |child infos|

1. Let |user context| be |context|'s [=associated user context=].

1. Let |context info| be a [=/map=] matching the
<code>browsingContext.Info</code> production with the <code>context</code>
field set to |context id|, the <code>parent</code> field set to |parent id|
if |is root| is <code>true</code>, or unset otherwise, the <code>url</code>
field set to |url|, and the <code>children</code> field set to |child infos|.
field set to |url|, the <code>userContext</code> field set to
|user context|'s [=user context id=], and the <code>children</code> field
set to |child infos|.

1. Return |context info|.

Expand Down Expand Up @@ -2769,7 +3004,8 @@ The <dfn export for=commands>browsingContext.create</dfn> command creates a new
browsingContext.CreateParameters = {
type: browsingContext.CreateType,
? referenceContext: browsingContext.BrowsingContext,
? background: bool .default false
? background: bool .default false,
? userContext: browser.UserContext / null
}
</pre>
</dd>
Expand Down Expand Up @@ -2802,13 +3038,29 @@ The [=remote end steps=] with |command parameters| are:
1. If the implementation is unable to create a new browsing context for any
reason then return [=error=] with [=error code=] [=unsupported operation=].

1. Let |user context| be the [=default user context=] if |reference context| is null, and |reference context|' [=user context=] otherwise.

1. If |command parameters|[<code>userContext</code>] is present and is not null:

1. Let |user context id| be |command parameters|[<code>userContext</code>].

1. If the [=user context=] with the [=user context id=] equal to |user context id| does
not exist in the [=remote end=], return [=error=] with [=error code=] [=no such user context=].

1. Set |user context| to the [=user context=] with the [=user context id=] equal
to |user context id|.

1. If |reference context| is not null and |reference context|'s [=user context=] is not |user context|,
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
return [=error=] with [=error code=] [=invalid user context id=].
Copy link
Contributor

Choose a reason for hiding this comment

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

In Firefox, we can open have tabs from several user contexts (containers) next to each other in the same window. I imagine it's fine to enforce this limitation if other vendors don't have this flexibility.

Does that mean that in Chrome, if you have 1 window with 1 tab in the default context and you use browsingContext.create with type=tab, it will force creating a new window on the fly? In that case it's not compliant with the following step of the type=tab section:

The new browsing context should reuse an existing OS window, if any.

So I'm wondering if we really need to be strict and fail here. We could modify the text of the "if type is tab" section below to say we will "attempt" to reuse an existing window (containing the reference context, if relevant), but it's not guaranteed.

cc @jgraham

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Firefox, we can open have tabs from several user contexts (containers) next to each other in the same window. I imagine it's fine to enforce this limitation if other vendors don't have this flexibility.

Yes, that is not supported with Chrome.

Does that mean that in Chrome, if you have 1 window with 1 tab in the default context and you use browsingContext.create with type=tab, it will force creating a new window on the fly? In that case it's not compliant with the following step of the type=tab section:

The new browsing context should reuse an existing OS window, if any.

Yes, it will create a tab in a new window. I think "should" allows for that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah maybe, I thought the "should" was just there to cover the case where there is no existing window ("if any"). But maybe this also covers the cases where existing windows are not compatible with the tab we are trying to add.

The spec about the reference context uses the same phrasing with "should":

If reference context is not null, the new browsing context should reuse the window containing reference context, if any.

So if the "window containing the reference context" is not compatible with the tab we are trying to add, then it should be fine to add the tab in another window?

Again, not sure we should throw here, if the spec is flexible enough to allow to pick another window if needed?

Since nothing in Firefox prevents from having tabs from several containers in the same window, it seems a bit arbitrary to throw. Imagine we have 1 window with 1 tab "tabA" owned by "contextA". If someone uses "browsingContext.create(type=tab, userContext=contextB)" with no reference context, Firefox will add this tab next to tabA. But if the user passes referenceContext=tabA, we would throw because contexts don't match?

If we really want to have a consistent behavior across all browsers, I think we need to enforce that all tabs in a window all have the same user context, but that seems difficult/impossible to implement.

Copy link
Member

Choose a reason for hiding this comment

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

"should" in specs is defined as "unless there's a valid reason not to". To me the "should reuse the window containing the reference context" is fine; it more or less macro expands to "shall reuse the window containing the reference context, unless there's a valid reason not to", and "we don't support multiple user contexts in the same window" seems like potentially a valid reason.

I also agree that Firefox in general can't enforce the condition that each OS window only gets tabs with a single user context and therefore we can't in WebDriver. For example we might connect to a browser where the user already created multiple containers in the same window.

Given that implementation differences seem impossible to avoid here, I think it might be OK to just allow whatever behaviour the implementation does? In practice clients can always request a new window when creating a tab in a different context, so for test automation purposes it's possible to build a stable, interoperable, foundation despite the underlying differences.


<!-- This is based on step 5 of https://w3c.github.io/webdriver/#new-window,
but without using the "current browsing context" concept. -->
1. Create a new [=top-level browsing context=] by running the [=window open
steps=] with <var ignore>url</var> set to "<code>about:blank</code>",
<var ignore>target</var> set to the empty string, and
<var ignore>features</var> set to "<code>noopener</code>". Which OS window the new [=/browsing context=]
is created in depends on |type| and |reference context|:
steps=] in the |user context| with <var ignore>url</var> set to
"<code>about:blank</code>", <var ignore>target</var> set to the empty
string, and <var ignore>features</var> set to "<code>noopener</code>".
OrKoN marked this conversation as resolved.
Show resolved Hide resolved
Which OS window the new [=/browsing context=] is created in depends on
|type| and |reference context|:
OrKoN marked this conversation as resolved.
Show resolved Hide resolved

* If |type| is "<code>tab</code>" and the implementation supports
multiple browsing contexts in the same OS window:
Expand Down