diff --git a/permission-element.bs b/permission-element.bs index 6278cf7..27d0937 100644 --- a/permission-element.bs +++ b/permission-element.bs @@ -9,7 +9,6 @@ Level: 0 Boilerplate: omit conformance Markup Shorthands: markdown on Editor: Daniel Vogelheim, Google LLC, vogelheim@google.com, https://www.google.com/ -Editor: Andy Paicu, Google LLC Abstract: A `` HTML element to request browser permissions in-page. Suitable styling and UI constraints on this new element ensure that the user @@ -19,7 +18,408 @@ Abstract: A `` HTML element to request browser permissions in-page. the current permission flows. + + # Introduction # {#intro} -# Framework # {#framwork} -# Algorithms # {#algorithms} +# The permission element. # {#the-permission-element} + +
+
[=Categories=]:
+
[=Flow content=].
+
[=Phrasing content=].
+
[=Interactive content=].
+
[=Palpable content=].
+
[=Contexts in which this element can be used=]:
+
Where [=phrasing content=] is expected.
+
[=Content model=]:
+
[=Nothing=].
+
[=Content attributes=]:
+
[=Global attributes=]
+
{{HTMLPermissionElement/type}} — Type of permission this element applies to.
+
{{HTMLPermissionElement/isValid}} — query whether the element can currently be activated.
+
{{HTMLPermissionElement/invalidReason}} — Return a string representation of why the element currently cannot be activated.
+
{{HTMLPermissionElement/ondismiss}} — notifies when the user has dismissed the permission prompt.
+
{{HTMLPermissionElement/onresolve}} — notifies when a permission prompt has been answered by the user (positively or negatively).
+
{{HTMLPermissionElement/onvalidationstatuschange}} — notifies when the validation status changes.
+
[=Accessibility considerations=]:
+
+
[=DOM interface=]:
+
+
+    [Exposed=Window]
+    interface HTMLPermissionElement : HTMLElement {
+      [HTMLConstructor] constructor();
+      [CEReactions, Reflect] attribute DOMString type;
+
+      readonly attribute boolean isValid;
+      readonly attribute PermissionElementInvalidReason invalidReason;
+
+      attribute EventHandler onresolve;
+      attribute EventHandler ondismiss;
+      attribute EventHandler onvalidationstatuschange;
+    };
+   
+
+
+ +ISSUE: Add accessibility considerations. + +ISSUE: Check attribute & event handler & invalid reason names against + current proposal(s). + +ISSUE: The onvalidationstatuschange event has (Or should have) parameters + about the blocker state. + +The {{HTMLPermissionElement/type}} attribute controls the behavior of the +permission element when it is activated. Is is an [=enumerated attribute=], +whose values are the [=powerful feature/names=] of [=powerful features=]. It +has neither a +[=missing value default=] state nor a [=invalid value default=] state. + +ISSUE: Is `type` one permission, or a space seperated list of permissions? + + +The {{HTMLPermissionElement/isValid}} attribute reflects whether a the +permission element is not currently blocked. + +The {{HTMLPermissionElement/invalidReason}} attribute is an +[=enumerated attribute=] that reflects the internal state of the permission +element. It's value set are {{PermissionElementInvalidReason}} + +The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported on <{permission}> elements [=event handler IDL attributes=]: + +
+onresolve: Event
+ondismiss: Event
+onvalidationstatuschange: Event
+
+ +ISSUE: onvalidationstatuschange is probably not a simple Event. + + +## <{permission}> element internal state ## {#permission-element-internal-state} + +The <{permission}> element [=DOM/represents=] a user-requestable [=permission=], +which the user can activate to enable (or disable) a particular permission or +set of permissions. It is core to the <{permission}> element that these +requests are triggered by the user, and not by the page's script. To enforce +this, the element checks whether the activation event is {{Event/isTrusted|trusted}}. Additionally it watches a number of conditions, like whether the element is +(partially) occluded, or if it has recently been moved. The element maintains +an internal {{[[BlockerList]]}} to keep track of this. + +The <{permission}> element has the following internal slots: + +* The \[[BlockerList]] is a + list of records, containing a + blocker timestamp and a + blocker reason. The [=blocker + reason=] is a {{PermissionElementInvalidReason}}, but not the empty string. + +* \[[IntersectionObserver]] + is a reference to an {{IntersectionObserver}}. + +* \[[Types]] is null + or an [=ordered set=] of [=powerful features=]. Null represents the + uninitialized state, which allows the value to be modified. The empty + list «[]» is the state in which no permission applies, and which + will no longer allow modification. Note that the + {{HTMLPermissionElement/type}} property reflects this internal state. + +## <{permission}> element interesting behaviours ## {#permission-element-very-interesting} + +### The {{HTMLPermissionElement/type}} property ### {#permission-element-type-property} + +The <{permission}> element has a few surprising behaviours, to support its +security properties: + +The permission type cannot be modified. Modifying the permission type at will +may lead to user confusion, and hence we'd like to prevent it. Since, however, +a page may create a <{permission}> element dynamically we still need to offer +an API to modify it. To do do, we distinguish between a freshly initialized and +an empty or invalid (no permission) state, where the former allows setting the +type and the latter does not. + +Example: +```js +// Changing a valid type: +var pepc = document.createElement("permission"); +pepc.type = "camera"; // Okay. +pepc.type; // "camera". +pepc.type = "geolocation"; // Not okay. Would have been okay as initial assignment. +pepc.type; // "camera". Reflects the internal state, which has not changed. + +// Setting an invalid type: +pepc = document.createElement("permission"); +pepc.type = "icecream"; // Ice cream is not a powerful browser feature. Not okay. +pepc.type; // "". Reflects the internal state. +pepc.type = "camera"; // Still Not okay, because type as already been set. + // Would have been okay as initial assignment. +pepc.type; // "". Reflects the internal state, which has not changed. + +``` + +
+The HTMLPermissionElement's {{HTMLPermissionElement/type}} getter steps are: + +1. If {{[[Types]]}} is null: Return `""`. +1. Return a string, containing the concatenation of all [=powerful feature=] + names in {{[[Types]]}}, seperated by " ". + +
+ +
+The HTMLPermissionElement's {{HTMLPermissionElement/type}} setter steps are: + +1. If {{[[Types]]}} is not null: Return. +1. Set {{[[Types]]}} to «[]». +1. Parse the input as a string of [=powerful feature=] names, seperated by whitespace. +1. If any errors occured, return. +1. Check if the set of [=powerful features=] is supported for the {{HTMLPermissionElement}} by the user agent. If not, return. +1. Append each [=powerful feature=] name to the {{[[Types]]}} [=ordered set=]. + +
+ +### Activation blockers ### {#permission-element-activation-blockers} + +
+enum PermissionElementInvalidReason {
+  "",  // No blocker reason.
+  "style", "type_count", "illegal_subframe", "covered",
+  "recently_moved", "recently_created"
+};
+
+ +Issue: Is covered and visible the same type of blocker? + +Issue: Are unsupported types also a blocker? + +
+In the Chrome implementation, I find these reasons: + * type_invalid + * illegal_subframe + * unsuccesful_registration + * recently_attached + * intersection_visible + * intersection_changed + * intersection_out_of_viewport_or_clipped + * intersection_occluded_or_distorted + * style_invalid + +
+ +The permission element keeps track of "blockers", reasons why the element (currently) cannot be activated. These blockers come with three lifetimes: Permanent, temporary, and expiring. + +: Permanent blocker +:: Once an element has a permanent blocker, it will be disabled permanently. + There are used for issues that the website owner is expected to fix. + An example is a <{permission}> element inside a <{fencedframe}>. +: Temporary blocker +:: This is a blocker that will only be valid until the blocking condition no + no longer occurs. An example is a <{permission}> element that is not + currently in view. +: Expiring blocker +:: This is a blocker that is only valid for a fixed period of time. This is + used to block abuse scenarios like "click jacking". An example is + a <{permission}> element that has recently been moved. + +
+ + +
style +permanent (???) +Something something. +
type_count +permanent +When another <{permission}> for the same [=powerful feature|permission type=] is used on the same page. +
illegal_subframe +permanent +When the <{permission}> is used inside a <{fencedframe}>. +
covered +temporary +When the <{permission}> is partially occluded, or not visible at all. +
recently_moved +expiring +When the <{permission}> is being moved on the screen. +
recently_created +expiring +When <{permission}> is attached to the document. +
+
+ +
+To add a blocker with a +{{PermissionElementInvalidReason}} |reason| and an optional flag |expires|: + +1. [=Assert=]: |reason| is not `""`. + (The empty string in {{PermissionElementInvalidReason}} signals no blocker + is present. Why would you add a non-blocking blockern empty string?) +1. Let |timestamp| be None. +1. If |expires|, then let |timestamp| be [=current high resolution time=] + plus the [=blocker delay=]. +1. [=list/Append=] an entry to the internal {{[[BlockerList]]}} with |reason| + and |timestamp|. + +
+ +
+The blocker delay is 500ms. +
+ +
+To add an expiring blocker with a +{{PermissionElementInvalidReason}} |reason|: + +1. [=Assert=]: |reason| is listed as "expiring" in the [=blocker reason table=]. +1. [=Add a blocker=] with |reason| and true. + +
+ +
+To add a temporary blocker with a +{{PermissionElementInvalidReason}} |reason|: + +1. [=Assert=]: |reason| is listed as "temporary" in the [=blocker reason table=]. +1. [=Add a blocker=] with |reason| and false. + +
+ +
+To add a permament blocker with a +{{PermissionElementInvalidReason}} |reason|: + +1. [=Assert=]: |reason| is listed as "permanent" in the [=blocker reason table=]. +1. [=Add a blocker=] with |reason| and false. + +
+ +
+To remove blockers with +{{PermissionElementInvalidReason}} |reason| from an |element|: + +1. [=Assert=]: |reason| is listed as "temporary" in the [=blocker reason table=]. +1. [=list/iterate|For each=] |entry| in |element|'s {{[[BlockerList]]}}: + 1. If |entry|'s reason [=string/is|equals=] |reason|, then [=list/remove=] + |entry| from |element|'s {{[[BlockerList]]}}. + +
+ +
+To determine a {{HTMLPermissionElement}} |element|'s +blocker: + +1. [=list/iterate|For each=] |entry| in |element|'s {{[[BlockerList]]}}: + 1. If |entry| is [=HTMLPermissionElement/valid=], then return |entry|. +1. Return nothing. + +
+ +
+An {{HTMLPermissionElement}}'s [=blocker=] list's |entry| is +valid if: + +1. |entry| has no timestamp, +1. or |entry| has a timestamp, and the timestamp is greater or equal to the + [=current high resolution time=]. + +
+ +NOTE: The spec does not define a way for expiring blockers that have expired + to be removed, so {{[[BlockerList]]}} may grow indefinitely. + Since the presence of expired blockers is not observable, user agents + should feel free to clean up the blocker list. + +## <{permission}> element algorithms ## {#permission-element-algorithms} + +
+The {{HTMLPermissionElement}} constructor steps are: + +1. Initialize the internal {{[[Types]]}} slot to null. +1. Initialize the internal {{[[BlockerList]]}} to «[]». + +
+ +
+The {{HTMLPermissionElement}} [=insertion steps=] are: + +1. If {{[[Types]]}} is null, set {{[[Types]]}} to «[]». +1. Initialize the internal {{[[BlockerList]]}} to «[]» +1. Initialize the internal {{[[IntersectionObserver]]}} with the result of + constructing a new {{IntersectionObserver}}, with + [=HTMLPermissionElement/IntersectionObserver callback=]. +1. Call {{[[IntersectionObserver]]}}.observe([=this=]). +1. [=Add an expiring blocker=] with reason `"recently_created"`. + +
+ +
+HTMLPermissionElement |element|'s isValid getter steps are: + +1. Return whether |element|'s [=HTMLPermissionElement/blocker=] is Nothing. + +
+ +
+HTMLPermissionElement |element|'s invalidReason getter steps are: + +1. If |element| {{HTMLPermissionElement/isValid}}, then return + [=HTMLPermissionElement/blocker=]'s reason string. +1. Otherwise, return `""`. + +
+ +
+A <{permission}> |element|'s [=EventTarget/activation behavior=] given |event| is: + +1. [=Assert=]: |element|'s {{[[Types]]}} is not null. +1. If |element|'s {{[[Types]]}} is [=list/empty=], then return. +1. If |event|'s {{Event/isTrusted}} attribute is not true, then return. +1. If |element|'s {{HTMLPermissionElement/isValid}} attribute is false, + then return. +1. [=Request permission to use=] the powerful features named in |element|'s + {{[[Types]]}}. + +Issue: What about event handlers? +
+ +
+The HTMLPermissionElement's IntersectionObserver callback implements {{IntersectionObserverCallback}} and runs the following steps: + +1. [=Assert=]: The {{IntersectionObserver}}'s {{IntersectionObserver/root}} is the [=Document=] +1. Let |entries| be the value of the first callback parameter, + the [=/list=] of {{IntersectionObserverEntry|intersection observer entries}}. +1. Initialize |covered| with false. +1. Initialize |visible| with true. +1. [=list/iterate|For any=] |item| in |entries|: + 1. If |item|[{{IntersectionObserverEntry/isVisible}}] is false: Set |covered| to true. + 1. If |item|[{{IntersectionObserverEntry/isIntersecting}}] is false: Set |visible| to false. +1. If |covered|: + 1. Then: [=Add a temporary blocker=] with {{PermissionElementInvalidReason}} "covered". + 1. Otherwise: [=Remove blockers=] with {{PermissionElementInvalidReason}} "covered". +1. If |visible|: + 1. Then: [=Add a temporary blocker=] with {{PermissionElementInvalidReason}} "visible". + 1. Otherwise: [=Remove blockers=] with {{PermissionElementInvalidReason}} "visible". + +
+ +# CSS Integration # {#algorithms} # Security & Privacy Considerations # {#secpriv}