Skip to content

Commit

Permalink
Add scripting and user preferences as Device parameters (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperisager authored Jun 10, 2020
1 parent e7d11d8 commit 547177a
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 16 deletions.
10 changes: 5 additions & 5 deletions packages/alfa-cli/bin/alfa/commands/scrape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { error } from "@oclif/errors";

import * as parser from "@oclif/parser";

import { Device, Display, Viewport } from "@siteimprove/alfa-device";
import { Device, Display, Scripting, Viewport } from "@siteimprove/alfa-device";
import { Header, Cookie } from "@siteimprove/alfa-http";
import {
Awaiter,
Expand Down Expand Up @@ -67,10 +67,10 @@ export default class Scrape extends Command {
description: "The pixel density of the browser",
}),

javascript: flags.boolean({
scripting: flags.boolean({
default: true,
allowNo: true,
description: "Whether or not JavaScript is enabled",
description: "Whether or not scripts, such as JavaScript, are evaluated",
}),

username: flags.string({
Expand Down Expand Up @@ -200,7 +200,8 @@ export default class Scrape extends Command {
const device = Device.of(
Device.Type.Screen,
Viewport.of(flags.width, flags.height, orientation),
Display.of(flags.resolution)
Display.of(flags.resolution),
Scripting.of(flags.scripting)
);

const credentials =
Expand Down Expand Up @@ -279,7 +280,6 @@ export default class Scrape extends Command {
screenshot,
headers,
cookies,
javascript: flags.javascript,
}
);

Expand Down
4 changes: 3 additions & 1 deletion packages/alfa-device/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"dependencies": {
"@siteimprove/alfa-equatable": "^0.2.0",
"@siteimprove/alfa-hash": "^0.2.0",
"@siteimprove/alfa-json": "^0.2.0"
"@siteimprove/alfa-iterable": "^0.2.0",
"@siteimprove/alfa-json": "^0.2.0",
"@siteimprove/alfa-map": "^0.2.0"
},
"devDependencies": {
"@siteimprove/alfa-test": "^0.2.0"
Expand Down
65 changes: 60 additions & 5 deletions packages/alfa-device/src/device.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
import { Equatable } from "@siteimprove/alfa-equatable";
import { Hash, Hashable } from "@siteimprove/alfa-hash";
import { Iterable } from "@siteimprove/alfa-iterable";
import { Serializable } from "@siteimprove/alfa-json";
import { Map } from "@siteimprove/alfa-map";

import * as json from "@siteimprove/alfa-json";

import { Display } from "./display";
import { Preference } from "./preference";
import { Scripting } from "./scripting";
import { Viewport } from "./viewport";

export class Device implements Equatable, Hashable, Serializable {
/**
* @remarks
* If the iterable of preferences contains preferences with duplicate names,
* the last preference with a given name will take precedence.
*/
public static of(
type: Device.Type,
viewport: Viewport,
display: Display
display: Display,
scripting: Scripting = Scripting.of(true),
preferences: Iterable<Preference> = []
): Device {
return new Device(type, viewport, display);
return new Device(
type,
viewport,
display,
scripting,
Map.from(
Iterable.map(preferences, (preference) => [preference.name, preference])
)
);
}

private readonly _type: Device.Type;
private readonly _viewport: Viewport;
private readonly _display: Display;
private readonly _scripting: Scripting;
private readonly _preferences: Map<Preference.Name, Preference>;

private constructor(type: Device.Type, viewport: Viewport, display: Display) {
private constructor(
type: Device.Type,
viewport: Viewport,
display: Display,
scripting: Scripting,
preferences: Map<Preference.Name, Preference>
) {
this._type = type;
this._viewport = viewport;
this._display = display;
this._scripting = scripting;
this._preferences = preferences;
}

public get type(): Device.Type {
Expand All @@ -38,12 +67,28 @@ export class Device implements Equatable, Hashable, Serializable {
return this._display;
}

public get scripting(): Scripting {
return this._scripting;
}

public get preferences(): Iterable<Preference> {
return this._preferences.values();
}

public preference<N extends Preference.Name>(name: N): Preference<N> {
return this._preferences
.get(name)
.getOrElse(() => Preference.initial(name)) as Preference<N>;
}

public equals(value: unknown): value is this {
return (
value instanceof Device &&
value._type === this._type &&
value._viewport.equals(this._viewport) &&
value._display.equals(this._display)
value._display.equals(this._display) &&
value._scripting.equals(this._scripting) &&
value._preferences.equals(this._preferences)
);
}

Expand All @@ -61,13 +106,19 @@ export class Device implements Equatable, Hashable, Serializable {

this._viewport.hash(hash);
this._display.hash(hash);
this._scripting.hash(hash);
this._preferences.hash(hash);
}

public toJSON(): Device.JSON {
return {
type: this._type,
viewport: this._viewport.toJSON(),
display: this._display.toJSON(),
scripting: this._scripting.toJSON(),
preferences: [...this._preferences.values()].map((preferece) =>
preferece.toJSON()
),
};
}
}
Expand All @@ -84,13 +135,17 @@ export namespace Device {
type: Type;
viewport: Viewport.JSON;
display: Display.JSON;
scripting: Scripting.JSON;
preferences: Array<Preference.JSON>;
}

export function from(json: JSON): Device {
return Device.of(
json.type,
Viewport.from(json.viewport),
Display.from(json.display)
Display.from(json.display),
Scripting.from(json.scripting),
json.preferences.map((json) => Preference.from(json))
);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/alfa-device/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./device";
export * from "./display";
export * from "./preference";
export * from "./scripting";
export * from "./viewport";
135 changes: 135 additions & 0 deletions packages/alfa-device/src/preference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Equatable } from "@siteimprove/alfa-equatable";
import { Hash, Hashable } from "@siteimprove/alfa-hash";
import { Serializable } from "@siteimprove/alfa-json";

import * as json from "@siteimprove/alfa-json";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences
*/
export class Preference<N extends Preference.Name = Preference.Name>
implements Equatable, Hashable, Serializable {
public static of<N extends Preference.Name>(
name: N,
value: Preference.Value<N>
): Preference<N> {
return new Preference(name, value);
}

private readonly _name: N;
private readonly _value: Preference.Value<N>;

private constructor(name: N, value: Preference.Value<N>) {
this._name = name;
this._value = value;
}

public get name(): N {
return this._name;
}

public get value(): Preference.Value<N> {
return this._value;
}

public equals(value: unknown): value is this {
return (
value instanceof Preference &&
value._name === this._name &&
value._value === this._value
);
}

public hash(hash: Hash): void {
Hash.writeString(hash, this._name);
Hash.writeString(hash, this._value);
}

public toJSON(): Preference.JSON {
return {
name: this._name,
value: this._value,
};
}
}

export namespace Preference {
export interface JSON {
[key: string]: json.JSON;
name: string;
value: string;
}

export function isPreference<N extends Name>(
value: unknown,
name?: N
): value is Preference<N> {
return (
value instanceof Preference && (name === undefined || value.name === name)
);
}

export function from<N extends Name>(json: JSON): Preference<N> {
return Preference.of(json.name as N, json.value as Preference.Value<N>);
}

export type Name = keyof Preferences;

export type Value<N extends Name = Name> = Preferences[N];

interface Preferences {
/**
* @see https://drafts.csswg.org/mediaqueries-5/#forced-colors
*/
"forced-colors": "none" | "active";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#inverted
*/
inverted: "none" | "inverted";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
*
* @remarks
* For consistency, "no-preference" is also included.
*/
"prefers-color-scheme": "no-preference" | "light" | "dark";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
*
* @remarks
* For consistency, "no-preference" is also included.
*/
"prefers-contrast": "no-preference" | "high" | "low";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
*/
"prefers-reduced-motion": "no-preference" | "reduce";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency
*/
"prefers-reduced-transparency": "no-preference" | "reduce";
}

export function initial<N extends Name>(name: N): Value<N> {
function initial(name: Name): Value {
switch (name) {
case "forced-colors":
case "inverted":
return "none";

case "prefers-color-scheme":
case "prefers-contrast":
case "prefers-reduced-motion":
case "prefers-reduced-transparency":
return "no-preference";
}
}

return initial(name) as Value<N>;
}
}
59 changes: 59 additions & 0 deletions packages/alfa-device/src/scripting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Equatable } from "@siteimprove/alfa-equatable";
import { Hash, Hashable } from "@siteimprove/alfa-hash";
import { Serializable } from "@siteimprove/alfa-json";

import * as json from "@siteimprove/alfa-json";

/**
* @see https://drafts.csswg.org/mediaqueries-5/#mf-scripting
*
* @remarks
* As noted in Media Queries Level 5, a future level of CSS may extend the
* scripting feature to allow fine-grained detection of which script is allowed
* to run. While the `Scripting` class therefore currently seems very sparse, we
* foresee a need to extend it in the future.
*/
export class Scripting implements Equatable, Hashable, Serializable {
public static of(enabled: boolean): Scripting {
return new Scripting(enabled);
}

private readonly _enabled: boolean;

private constructor(enabled: boolean) {
this._enabled = enabled;
}

public get enabled(): boolean {
return this._enabled;
}

public equals(value: unknown): value is this {
return value instanceof Scripting && value._enabled === this._enabled;
}

public hash(hash: Hash): void {
Hash.writeBoolean(hash, this._enabled);
}

public toJSON(): Scripting.JSON {
return {
enabled: this._enabled,
};
}
}

export namespace Scripting {
export interface JSON {
[key: string]: json.JSON;
enabled: boolean;
}

export function isScripting(value: unknown): value is Scripting {
return value instanceof Scripting;
}

export function from(json: JSON): Scripting {
return Scripting.of(json.enabled);
}
}
Loading

0 comments on commit 547177a

Please sign in to comment.