Skip to content

Commit

Permalink
Add support for the crossorigin attribute. (#137)
Browse files Browse the repository at this point in the history
Closes #136
  • Loading branch information
jverkoey authored Aug 10, 2024
1 parent 179e67f commit 17b9a20
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Sources/Slipstream/Documentation.docc/Slipstream.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ print(try renderHTML(HelloWorld()))

### W3C

- <doc:W3CViews>
- <doc:W3CAttributes>
- <doc:W3CViews>

### Rendering views

Expand Down
4 changes: 4 additions & 0 deletions Sources/Slipstream/Documentation.docc/W3C/W3CAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
### Images

- ``View/accessibilityLabel(_:)``

### Attribute types

- ``CrossOrigin``
23 changes: 23 additions & 0 deletions Sources/Slipstream/W3C/Attributes/CrossOrigin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// Constants defining how a view handles cross-origin requests.
///
/// - SeeAlso: W3C [crossorigin](https://html.spec.whatwg.org/#cors-settings-attributes) guidance.
///
/// ## See Also
///
/// - ``Image``
/// - ``Script``
/// - ``Stylesheet``
@available(iOS 17.0, macOS 14.0, *)
public enum CrossOrigin: String {
/// Request uses CORS headers and credentials flag is set to 'same-origin'.
///
/// There is no exchange of user credentials via cookies, client-side TLS
/// certificates or HTTP authentication, unless destination is the same
/// origin.
case anonymous = ""

/// Request uses CORS headers and credentials flag is set to 'include'.
///
/// User credentials are always included.
case useCredentials = "use-credentials"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extension View {
/// ## See Also
///
/// - ``Image``
@available(iOS 17.0, macOS 14.0, *)
public func accessibilityLabel(_ string: String) -> some View {
modifier(AttributeModifier("alt", value: string))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ import SwiftSoup
@available(iOS 17.0, macOS 14.0, *)
public struct Stylesheet: View {
/// Creates a Stylesheet view.
public init(_ url: URL?) {
///
/// - Parameters:
/// - url: The stylesheet will be loaded from this URL.
/// - crossOrigin: If provided, configures the Cross-Origin Resource Sharing (CORS)
/// behavior for the request of this resource. If not provided, then the No CORS state is
/// implied.
public init(_ url: URL?, crossOrigin: CrossOrigin? = nil) {
self.url = url
self.crossOrigin = crossOrigin
}

@_documentation(visibility: private)
Expand All @@ -30,7 +37,11 @@ public struct Stylesheet: View {
let element = try container.appendElement("link")
try element.attr("rel", "stylesheet")
try element.attr("href", url.absoluteString)
if let crossOrigin {
try element.attr("crossorigin", crossOrigin.rawValue)
}
}

private let url: URL?
private let crossOrigin: CrossOrigin?
}
13 changes: 12 additions & 1 deletion Sources/Slipstream/W3C/Elements/EmbeddedContent/Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ import SwiftSoup
@available(iOS 17.0, macOS 14.0, *)
public struct Image: View {
/// Creates a Stylesheet view.
public init(_ url: URL?) {
///
/// - Parameters:
/// - url: The image will be loaded from this URL.
/// - crossOrigin: If provided, configures the Cross-Origin Resource Sharing (CORS)
/// behavior for the request of this resource. If not provided, then the No CORS state is
/// implied.
public init(_ url: URL?, crossOrigin: CrossOrigin? = nil) {
self.url = url
self.crossOrigin = crossOrigin
}

@_documentation(visibility: private)
Expand All @@ -35,7 +42,11 @@ public struct Image: View {
}
let element = try container.appendElement("img")
try element.attr("src", url.absoluteString)
if let crossOrigin {
try element.attr("crossorigin", crossOrigin.rawValue)
}
}

private let url: URL?
private let crossOrigin: CrossOrigin?
}
19 changes: 15 additions & 4 deletions Sources/Slipstream/W3C/Elements/Scripting/Script.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ public enum ScriptExecutionMode: String {
@available(iOS 17.0, macOS 14.0, *)
public struct Script: View {
/// Creates a script view pointing to a URL.
public init(_ url: URL?, executionMode: ScriptExecutionMode? = nil) {
self.storage = .url(url, executionMode: executionMode)
///
/// - Parameters:
/// - url: The script will be loaded from this URL.
/// - crossOrigin: If provided, configures the Cross-Origin Resource Sharing (CORS)
/// behavior for the request of this resource. If not provided, then the No CORS state is
/// implied.
/// - executionMode: The execution mode for this script defines how and when the
/// script should be loaded in relation to the rest of the document.
public init(_ url: URL?, crossOrigin: CrossOrigin? = nil, executionMode: ScriptExecutionMode? = nil) {
self.storage = .url(url, crossOrigin: crossOrigin, executionMode: executionMode)
}

/// Creates a script view with inline source.
Expand All @@ -46,12 +54,15 @@ public struct Script: View {
@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
switch storage {
case .url(let url, let executionMode):
case .url(let url, let crossOrigin, let executionMode):
guard let url else {
return
}
let element = try container.appendElement("script")
try element.attr("src", url.absoluteString)
if let crossOrigin {
try element.attr("crossorigin", crossOrigin.rawValue)
}
if let executionMode {
try element.attr(executionMode.rawValue, "")
}
Expand All @@ -63,7 +74,7 @@ public struct Script: View {
}

private enum Storage {
case url(URL?, executionMode: ScriptExecutionMode?)
case url(URL?, crossOrigin: CrossOrigin?, executionMode: ScriptExecutionMode?)
case inline(String)
}

Expand Down
5 changes: 5 additions & 0 deletions Tests/SlipstreamTests/W3C/ImageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ struct ImageTests {
try #expect(renderHTML(Image(URL(string: "/logo.png"))) == #"<img src="/logo.png" />"#)
}

@Test func crossOrigin() throws {
try #expect(renderHTML(Image(URL(string: "/logo.png"), crossOrigin: .anonymous)) == #"<img src="/logo.png" crossorigin="" />"#)
try #expect(renderHTML(Image(URL(string: "/logo.png"), crossOrigin: .useCredentials)) == #"<img src="/logo.png" crossorigin="use-credentials" />"#)
}

@Test func accessibilityLabel() throws {
try #expect(renderHTML(Image(URL(string: "/logo.png")).accessibilityLabel("My logo")) == #"<img src="/logo.png" alt="My logo" />"#)
}
Expand Down
5 changes: 5 additions & 0 deletions Tests/SlipstreamTests/W3C/ScriptTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ struct ScriptTests {
try #expect(renderHTML(Script(URL(string: "/main.js"), executionMode: .defer)) == #"<script src="/main.js" defer></script>"#)
}

@Test func crossOrigin() throws {
try #expect(renderHTML(Script(URL(string: "/main.js"), crossOrigin: .anonymous)) == #"<script src="/main.js" crossorigin=""></script>"#)
try #expect(renderHTML(Script(URL(string: "/main.js"), crossOrigin: .useCredentials)) == #"<script src="/main.js" crossorigin="use-credentials"></script>"#)
}

@Test func source() throws {
try #expect(renderHTML(Script("""
alert("Hello, world!");
Expand Down
5 changes: 5 additions & 0 deletions Tests/SlipstreamTests/W3C/StylesheetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ struct StylesheetTests {
@Test func validURL() throws {
try #expect(renderHTML(Stylesheet(URL(string: "/main.css"))) == #"<link rel="stylesheet" href="/main.css" />"#)
}

@Test func crossOrigin() throws {
try #expect(renderHTML(Stylesheet(URL(string: "/main.css"), crossOrigin: .anonymous)) == #"<link rel="stylesheet" href="/main.css" crossorigin="" />"#)
try #expect(renderHTML(Stylesheet(URL(string: "/main.css"), crossOrigin: .useCredentials)) == #"<link rel="stylesheet" href="/main.css" crossorigin="use-credentials" />"#)
}
}

0 comments on commit 17b9a20

Please sign in to comment.