cef2gtk

package module
v0.8.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 19 Imported by: 0

README

purego-cef2gtk

GTK4 widget bridge for purego-cef accelerated off-screen rendering.

Status

Early bootstrap. The renderer is Wayland-only and GPU-first:

  • CEF accelerated OSR with shared textures/DMABUFs
  • GTK4 GdkDmabufTextureBuilder + GtkPicture/GtkGraphicsOffload backend for GTK/GSK rendering
  • GTK4 GtkGLArea EGL/OpenGL import-and-copy backend as fallback/debug path
  • no X11 target
  • no --disable-gpu or software rendering fallback
  • CEF OnPaint is treated as a diagnostic/error path, not as a renderer

Renderer stacks

NewView() defaults to the Vulkan GPU stack and fails during view setup if the selected stack cannot be constructed. Use ResolveRenderStack to choose a coherent stack explicitly:

plan, err := cef2gtk.ResolveRenderStack(cef2gtk.RenderStackVulkan) // or RenderStackEGL
if err != nil {
    return err
}
cef2gtk.ConfigureRenderStackEnvironment(plan) // call before GTK initialization
cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{RenderStackPlan: plan})
view := cef2gtk.NewViewWithOptions(cef2gtk.ViewOptions{RenderStackPlan: plan})

Supported stacks:

  • vulkan: GDK DMABUF presentation with ANGLE Vulkan and GSK Vulkan.
  • egl: GtkGLArea presentation with ANGLE GL/EGL and GSK OpenGL.

Environment overrides remain available as diagnostics/backcompat escape hatches:

PUREGO_CEF2GTK_BACKEND=gdk-dmabuf|glarea
PUREGO_CEF2GTK_ANGLE_BACKEND=vulkan|gl-egl|none
PUREGO_CEF2GTK_OSR_BACKING_SCALE=auto|on|off

PUREGO_CEF2GTK_OSR_BACKING_SCALE=auto enables the Linux accelerated-OSR HiDPI compatibility path only when the GTK surface scale is greater than 1. In that mode CEF receives a device-sized OSR view rect with a 1x screen scale, because current CEF shared-texture OSR builds can otherwise report a fractional device_scale_factor while still emitting 1x/logical DMABUF frames. Applications that call BrowserHost.SetZoomLevel should divide their page zoom by cef2gtk.OSRBackingScaleFactorForScale(float64(view.DeviceScaleFactor())) before converting it to CEF's logarithmic zoom level; this keeps the page's CSS viewport at the GTK logical size while the OSR backing remains device-sized.

Recommended local checks:

GSK_RENDERER=vulkan PUREGO_CEF2GTK_BACKEND=gdk-dmabuf go run ./examples/simple-browser
GSK_RENDERER=opengl PUREGO_CEF2GTK_BACKEND=glarea PUREGO_CEF2GTK_ANGLE_BACKEND=gl-egl go run ./examples/simple-browser

Usage sketch

plan, err := cef2gtk.ResolveRenderStack(cef2gtk.RenderStackVulkan)
if err != nil {
    return err
}

cef2gtk.ConfigureRenderStackEnvironment(plan) // before GTK initialization

// In cef.App.OnBeforeCommandLineProcessing:
cef2gtk.ConfigureCommandLine(commandLine, cef2gtk.CommandLineOptions{RenderStackPlan: plan})

view := cef2gtk.NewViewWithOptions(cef2gtk.ViewOptions{RenderStackPlan: plan})
window.SetChild(view.Widget())
window.Present()
view.Widget().Realize()
if err := view.PrepareOnGTKThread(); err != nil {
    return err
}

info := cef.NewWindowInfo()
// After the GTK widget/surface is realized, extract its native platform handle
// with the appropriate GTK/platform API and pass that handle as Parent.
cef2gtk.ConfigureWindowInfo(&info, cef2gtk.WindowInfoOptions{Parent: /* realized native handle */})
settings := cef.NewBrowserSettings()
cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: 144})
client := cef.NewClient(myClient{render: view.RenderHandler(cef2gtk.Hooks{})})
cef.BrowserHostCreateBrowser(&info, client, "https://example.com/", &settings, nil, nil)

// In OnAfterCreated(browser cef.Browser), attach the BrowserHost:
_ = view.AttachInput(browser.GetHost(), cef2gtk.InputOptions{Scale: 0})

See examples/simple-browser for a complete accelerated-only GTK+CEF setup.

Development

The module depends on the published github.com/bnema/puregotk module. During local puregotk development, you can temporarily add your own replace github.com/bnema/puregotk => ../puregotk directive.

Local smoke execution

The integration smoke suite exercises GTK/EGL context probing, the deterministic DMABUF import-copy pipeline (synthetic GL texture through FBO render-to-texture and swap), and a build of the example browser. Smoke tests are skipped by default and require both WAYLAND_DISPLAY and the opt-in environment variable:

PUREGO_CEF2GTK_CEF_RUNTIME_SMOKE=1 rtk go test ./integration/ -run TestRuntimeSmokeGate -v

The probe-import-copy step validates the accelerated frame import/swap path deterministically: it allocates a real DRM dumb buffer dma-buf through /dev/dri/card0 (falling back to a synthetic 4×4 GL texture when DRM access is unavailable), fills it with a deterministic 4×4 RGBA pattern, imports it through eglImporter.ImportDMABUF + glEGLImageTargetTexture2DOES, and exercises the CopyImportedToOwned FBO render-to-texture pipeline and DrawTextureToCurrentFramebuffer swap path. After the copy pipeline, the result is read back through an FBO and compared with the original pixel data to verify data integrity. All GL errors, framebuffer completeness, and shader compilation are checked, providing strict end-to-end coverage of the accelerated EGL import + copy pipeline.

rtk make check

Documentation

Overview

Package cef2gtk provides a GTK4 widget bridge for purego-cef accelerated off-screen rendering.

The project is Wayland-only and GPU-first. It is intended to use CEF accelerated OSR shared textures/DMABUFs; software OnPaint rendering is a diagnostic/error path, not a product renderer.

Index

Constants

This section is empty.

Variables

View Source
var ErrInputNotAttached = errors.New("input bridge is not attached")
View Source
var ErrNilView = errors.New("nil View")
View Source
var ErrViewNotInitialized = errors.New("view not initialized")

Functions

func ConfigureBrowserSettings added in v0.3.0

func ConfigureBrowserSettings(settings *cef.BrowserSettings, opts BrowserSettingsOptions)

ConfigureBrowserSettings applies purego-cef2gtk browser-setting options.

func ConfigureCommandLine

func ConfigureCommandLine(commandLine cef.CommandLine, opts CommandLineOptions)

ConfigureCommandLine configures the CEF command line for Wayland accelerated rendering. It is intended to be called from App.OnBeforeCommandLineProcessing.

func ConfigureRenderStackEnvironment added in v0.3.0

func ConfigureRenderStackEnvironment(plan RenderStackPlan)

ConfigureRenderStackEnvironment applies process-global GTK/OSR environment hints from a resolved render stack plan. Call it before GTK is initialized when the host application wants purego-cef2gtk to own the coherent stack setup instead of setting those variables itself.

func ConfigureWindowInfo

func ConfigureWindowInfo(info *cef.WindowInfo, opts WindowInfoOptions)

ConfigureWindowInfo configures CEF window info for accelerated windowless rendering with shared textures.

func OSRBackingScaleEnabledForScale added in v0.2.0

func OSRBackingScaleEnabledForScale(scale float64) bool

OSRBackingScaleEnabledForScale reports whether the Linux accelerated OSR HiDPI compatibility path is active for the provided GTK surface scale.

Current CEF shared-texture OSR builds can accept a fractional CefScreenInfo.device_scale_factor while still emitting 1x/logical DMABUF frames. When this compatibility path is active, purego-cef2gtk asks CEF for a device-sized OSR view rect and reports a 1x screen scale. Applications that expose page zoom should compensate their internal CEF zoom level by multiplying the user-facing page zoom by OSRBackingScaleFactorForScale, then divide CEF zoom readback by the same factor. This keeps the page's CSS viewport at the GTK logical size while the OSR backing is device-sized.

func OSRBackingScaleFactorForScale added in v0.2.0

func OSRBackingScaleFactorForScale(scale float64) float64

OSRBackingScaleFactorForScale returns the backing scale that should be used for CEF OSR view/input coordinates. It returns 1 when compatibility scaling is disabled for the provided surface scale.

func WindowlessFrameRateForMonitorRefresh added in v0.4.0

func WindowlessFrameRateForMonitorRefresh(refreshRateMilliHz int, opts RefreshRateOptions) int32

WindowlessFrameRateForMonitorRefresh converts a GDK monitor refresh rate (milli-Hz) into a CEF OSR windowless frame-rate, with fallback and clamp handling for unknown or extreme monitor values.

Types

type Backend

type Backend string

Backend selects the GTK presentation backend used by a View.

const (
	// BackendAuto tries the GDK DMABUF backend first and falls back to GLArea
	// when GDK DMABUF construction is unavailable.
	BackendAuto Backend = "auto"
	// BackendGDKDMABUF presents CEF DMABUF frames as GDK textures.
	BackendGDKDMABUF Backend = "gdk-dmabuf"
	// BackendGLArea presents CEF DMABUF frames through GtkGLArea/EGL/OpenGL.
	BackendGLArea Backend = "glarea"
)

func (Backend) String

func (b Backend) String() string

String returns the environment/API spelling for b.

type BrowserSettingsOptions added in v0.3.0

type BrowserSettingsOptions struct {
	// WindowlessFrameRate is CEF's maximum frame rate for off-screen rendering.
	// Values <= 0 leave the caller's setting unchanged.
	WindowlessFrameRate int32
}

BrowserSettingsOptions configures CEF browser settings used by the GTK bridge.

type CommandLineOptions

type CommandLineOptions struct {
	// RenderStackPlan provides the resolved render stack. When empty,
	// ConfigureCommandLine defaults to the Vulkan stack unless the diagnostic
	// PUREGO_CEF2GTK_ANGLE_BACKEND environment override is set.
	RenderStackPlan RenderStackPlan
}

CommandLineOptions configures CEF command-line setup for the GTK bridge.

type DiagnosticEvent

type DiagnosticEvent struct {
	Time    time.Time
	Kind    string
	Message string
}

DiagnosticEvent records notable rendering-path events and failures.

type Diagnostics

type Diagnostics struct {
	Backend                 string
	AcceleratedPaints       int
	AcceleratedPaintErrors  int
	StaleAcceleratedPaints  int
	UnsupportedPaints       int
	ImportFailures          int
	RenderFailures          int
	TexturesBuilt           int
	TextureBuildFailures    int
	FDDupFailures           int
	UnsupportedFormats      int
	PaintableSwaps          int
	PendingFrame            bool
	PendingScheduled        bool
	PendingAge              time.Duration
	PendingSourceID         uint
	PendingReschedules      int
	PendingScheduleFailures int
	PendingIdleCallbacks    int
	Events                  []DiagnosticEvent
}

Diagnostics is a snapshot of View rendering diagnostics. Events contains the most recent diagnostic events, capped to avoid unbounded growth.

type DurationStats

type DurationStats = internalprofile.DurationStats

DurationStats aggregates duration samples for one profiling window.

type GCStats

type GCStats = internalprofile.GCStats

GCStats summarizes Go runtime GC state for one profiling window.

type Hooks

type Hooks struct {
	OnUnsupportedPaint     func()
	OnError                func(error)
	OnTextSelectionChanged func(selectedText string, selectedRange *cef.Range)
	// OnFirstAcceleratedPaint is invoked once when CEF first supplies an accelerated frame.
	OnFirstAcceleratedPaint func()
	// OnFirstDMABUFTextureSwap is invoked once after GtkPicture.SetPaintable succeeds.
	OnFirstDMABUFTextureSwap func()
	// OnFirstPresentation is invoked once at the first GTK frame-clock after-paint following the swap.
	OnFirstPresentation func()
	// OnDMABUFUnsupported is invoked once when the active renderer cannot complete a DMABUF texture swap.
	OnDMABUFUnsupported func()
}

Hooks contains optional callbacks invoked by the public CEF render adapter.

type InputOptions

type InputOptions struct {
	// Scale overrides the coordinate transform applied to pointer coordinates.
	// Values <= 0 let the view choose the correct transform for the current OSR
	// backing mode: logical coordinates normally, device coordinates when the
	// OSR backing-scale compatibility path is active.
	Scale float64
	// OnMiddleClick is invoked when GTK receives a middle-button press. Returning
	// true consumes the event before it is forwarded to CEF.
	OnMiddleClick func(x, y float64) bool
	// Scroll configures GTK scroll delta translation before forwarding to CEF.
	Scroll ScrollOptions
	// OnScroll is invoked for scroll begin/update/end/decelerate notifications.
	// Returning ScrollConsume for an update prevents forwarding that event to CEF.
	OnScroll func(ScrollEvent) ScrollDecision
	// NavigationSwipe configures browser-history swipe recognition from precise
	// horizontal touchpad scroll streams.
	NavigationSwipe NavigationSwipeOptions
	// CanNavigateBack reports whether a recognized back swipe may navigate.
	CanNavigateBack func() bool
	// CanNavigateForward reports whether a recognized forward swipe may navigate.
	CanNavigateForward func() bool
	// OnNavigateSwipe is invoked when a back/forward navigation swipe is recognized.
	OnNavigateSwipe func(NavigationSwipeAction)
	// SelectionText returns the current browser selection for explicit clipboard
	// shortcuts. When set with OnClipboardShortcut, Ctrl+C/Ctrl+X can be mirrored
	// to application-level clipboard orchestration before forwarding to CEF.
	SelectionText func() string
	// OnClipboardShortcut is invoked for explicit Ctrl+C/Ctrl+X shortcuts when
	// SelectionText returns non-empty text. action is "copy" or "cut".
	OnClipboardShortcut func(action, text string)
}

InputOptions configures GTK-to-CEF input forwarding.

type NavigationSwipeAction int

NavigationSwipeAction identifies a browser-history swipe action derived from precise horizontal touchpad scrolling.

const (
	NavigationSwipeBack NavigationSwipeAction = iota
	NavigationSwipeForward
)
type NavigationSwipeOptions struct {
	Enabled          bool
	MinDelta         float64
	MaxVerticalRatio float64
}

NavigationSwipeOptions configures WebKitGTK-like back/forward swipe recognition.

type Options

type Options struct {
	// RenderingMode must be RenderingModeAcceleratedDMABUF. It defaults to that
	// mode when left empty.
	RenderingMode RenderingMode
}

Options configures a View.

func (Options) Validate

func (o Options) Validate() error

Validate verifies that the options request the supported accelerated path.

type ProfileOptions

type ProfileOptions struct {
	Enabled    bool
	Interval   time.Duration
	OnSnapshot func(ProfileSnapshot)
	Writer     io.Writer
}

ProfileOptions configures development-only render profiling for a View.

type ProfileSnapshot

type ProfileSnapshot = internalprofile.Snapshot

ProfileSnapshot contains one profiling window of render-pipeline metrics.

type RefreshRateOptions added in v0.4.0

type RefreshRateOptions struct {
	DefaultFPS int32
	MinFPS     int32
	MaxFPS     int32
}

RefreshRateOptions configures conversion from a GDK monitor refresh rate to a CEF windowless frame-rate. GDK reports refresh rates in milli-Hz.

type RenderStack added in v0.3.0

type RenderStack string

RenderStack selects a coherent accelerated CEF/GTK GPU render stack.

const (
	// RenderStackVulkan uses GDK DMABUF presentation with ANGLE Vulkan and GSK Vulkan.
	RenderStackVulkan RenderStack = "vulkan"
	// RenderStackEGL uses GtkGLArea presentation with ANGLE GL/EGL and GSK OpenGL.
	RenderStackEGL RenderStack = "egl"
)

type RenderStackPlan added in v0.3.0

type RenderStackPlan struct {
	Stack           RenderStack
	Backend         Backend
	ANGLEBackend    string
	GSKRenderer     string
	OSRBackingScale string
}

RenderStackPlan is the low-level plan for a selected accelerated render stack.

func ResolveRenderStack added in v0.3.0

func ResolveRenderStack(stack RenderStack) (RenderStackPlan, error)

ResolveRenderStack resolves a public render-stack choice to the low-level purego-cef2gtk options and process-global GTK/CEF hints it requires.

type RenderingMode

type RenderingMode string

RenderingMode selects the rendering pipeline used by the GTK bridge.

const (
	// RenderingModeAcceleratedDMABUF uses CEF accelerated OSR shared textures
	// imported through Wayland DMABUF/EGL and rendered by GtkGLArea.
	RenderingModeAcceleratedDMABUF RenderingMode = "accelerated-dmabuf"
)

type ScrollDecision added in v0.6.0

type ScrollDecision int

ScrollDecision controls whether a scroll update should be forwarded to CEF.

const (
	ScrollForwardToCEF ScrollDecision = iota
	ScrollConsume
)

type ScrollEvent added in v0.6.0

type ScrollEvent struct {
	Phase                ScrollPhase
	X, Y                 float64
	DX, DY               float64
	DeltaX, DeltaY       int32
	Modifiers            uint
	Unit                 gdk.ScrollUnit
	UnitKnown            bool
	VelocityX, VelocityY float64
}

ScrollEvent describes a GTK scroll event after CEF delta translation.

type ScrollOptions added in v0.6.0

type ScrollOptions struct {
	WheelMultiplier      float64
	PreciseMultiplier    float64
	HorizontalMultiplier float64
	VerticalMultiplier   float64
	MaxDelta             int32
}

ScrollOptions configures GTK scroll delta translation before forwarding to CEF. Wheel zero values keep legacy wheel behavior; precise zero values use a WebKitGTK-like touchpad/surface scale.

type ScrollPhase added in v0.6.0

type ScrollPhase int

ScrollPhase identifies the stage of a GTK scroll operation.

const (
	ScrollPhaseUnknown ScrollPhase = iota
	ScrollPhaseBegin
	ScrollPhaseUpdate
	ScrollPhaseEnd
	ScrollPhaseDecelerate
)

type View

type View struct {
	// contains filtered or unexported fields
}

View is a GTK-backed CEF OSR view.

func NewView

func NewView() *View

NewView creates an accelerated CEF view using the default Vulkan/GDK DMABUF render stack or the PUREGO_CEF2GTK_BACKEND override when it is set.

func NewViewWithOptions

func NewViewWithOptions(opts ViewOptions) *View

NewViewWithOptions creates an accelerated CEF view with explicit options. When PUREGO_CEF2GTK_BACKEND is set, it intentionally overrides opts.Backend for diagnostics and deployment-level backend selection.

func (*View) AddSizeObserver

func (v *View) AddSizeObserver(fn func(width, height int32)) func()

AddSizeObserver registers a callback invoked on the GTK thread when the view observes a positive size change. It returns a function that unregisters the observer. If a real positive size has already been observed, the callback is invoked immediately with that size; the synthetic Size() fallback is not emitted as an observer event. Register and unregister from the GTK thread.

func (*View) AttachInput

func (v *View) AttachInput(host cef.BrowserHost, opts InputOptions) error

AttachInput attaches GTK event controllers to the view's render widget and forwards input to host. Call from the GTK/main thread; GTK controller attachment is not goroutine-safe.

func (*View) AttachInputToWidget added in v0.5.2

func (v *View) AttachInputToWidget(host cef.BrowserHost, widget *gtk.Widget, opts InputOptions) error

AttachInputToWidget attaches GTK event controllers to widget and forwards input to host. When widget is nil, the view's render widget is used. This is useful when the render widget is embedded inside another focusable container that should own GTK keyboard focus, such as a GtkOverlay wrapper.

func (*View) Backend

func (v *View) Backend() Backend

Backend returns the selected presentation backend.

func (*View) ConfigureProfiling

func (v *View) ConfigureProfiling(opts ProfileOptions) error

ConfigureProfiling enables or disables development-only render profiling. When enabled, snapshots are emitted at opts.Interval through opts.OnSnapshot and/or opts.Writer. Rendering continues if snapshot writing fails.

func (*View) Destroy

func (v *View) Destroy() error

Destroy releases GL resources owned by the view. Call on the GTK main thread; it disconnects GTK signal handlers and is not safe for concurrent use.

func (*View) DetachInput

func (v *View) DetachInput() error

DetachInput removes input controllers attached by AttachInput.

func (*View) DeviceScaleFactor

func (v *View) DeviceScaleFactor() float32

DeviceScaleFactor returns the last GTK surface scale observed for the view. Values <= 0 are normalized to 1.

func (*View) Diagnostics

func (v *View) Diagnostics() Diagnostics

Diagnostics returns a point-in-time diagnostics snapshot.

func (*View) GLArea

func (v *View) GLArea() *gtk.GLArea

GLArea returns the underlying GtkGLArea for the GLArea backend.

func (*View) HasFocus

func (v *View) HasFocus() bool

HasFocus reports whether the effective GTK input widget currently has focus. Call on the GTK main thread.

func (*View) PrepareOnGTKThread

func (v *View) PrepareOnGTKThread() error

PrepareOnGTKThread initializes renderer resources. Call on GTK main thread.

func (*View) RecordExternalBeginFrameSent added in v0.4.0

func (v *View) RecordExternalBeginFrameSent()

func (*View) RefreshObservedSizeOnGTKThread added in v0.5.1

func (v *View) RefreshObservedSizeOnGTKThread() (int32, int32)

RefreshObservedSizeOnGTKThread synchronously refreshes the cached observed size from the widget's current GTK allocation and returns the resulting size.

func (*View) RenderHandler

func (v *View) RenderHandler(hooks Hooks) cef.RenderHandler

RenderHandler returns a CEF render handler adapter for this view.

func (*View) SetCursorFromName

func (v *View) SetCursorFromName(name string)

SetCursorFromName applies a named cursor to the underlying GTK widget. Call on the GTK main thread.

func (*View) SetInputHost

func (v *View) SetInputHost(host cef.BrowserHost) error

SetInputHost updates the CEF browser host used by the attached input bridge. Must be called on the GTK/main thread. Returns ErrInputNotAttached when v.input is nil. Callers must call AttachInput first.

func (*View) Size

func (v *View) Size() (int32, int32)

Size returns the last positive widget size observed on the GTK thread. Before the widget has a real size, CEF requires a non-zero fallback, so Size returns 1x1.

func (*View) Widget

func (v *View) Widget() *gtk.Widget

Widget returns the GtkWidget for packing into GTK containers.

type ViewOptions

type ViewOptions struct {
	// Backend selects the renderer backend. Empty defaults to the Vulkan stack's
	// GDK DMABUF backend unless RenderStackPlan is provided.
	Backend Backend
	// RenderStackPlan selects the renderer backend from a resolved render stack.
	RenderStackPlan RenderStackPlan
	// Profile optionally enables development render profiling during construction.
	Profile ProfileOptions
	// ScaleMultiplier is an application-level multiplier composed on top of the
	// GTK/GDK surface scale for CEF OSR screen, backing, and input coordinates.
	// Values <= 0, NaN, or Inf are treated as 1.
	ScaleMultiplier float64
}

ViewOptions configures NewViewWithOptions.

func (ViewOptions) Validate

func (o ViewOptions) Validate() error

Validate verifies that the requested backend/render stack is supported by the option schema.

type WindowInfoOptions

type WindowInfoOptions struct {
	// Parent is the native parent window handle passed to CEF.
	Parent uintptr
}

WindowInfoOptions configures CEF window-info setup for the GTK bridge.

Directories

Path Synopsis
cmd
probe-gtk-egl command
examples
simple-browser command
internal
cefadapter
Package cefadapter converts purego-cef callback data into internal models.
Package cefadapter converts purego-cef callback data into internal models.
dmabuf
Package dmabuf models borrowed Linux DMABUF frames received from CEF.
Package dmabuf models borrowed Linux DMABUF frames received from CEF.
egl
Package egl contains small EGL helpers shared by GTK/GL probing and rendering.
Package egl contains small EGL helpers shared by GTK/GL probing and rendering.
gl
Package gl contains purego OpenGL helpers used by the accelerated renderer.
Package gl contains purego OpenGL helpers used by the accelerated renderer.
gtkgdk
Package gtkgdk renders CEF accelerated-paint DMABUF frames through GDK textures.
Package gtkgdk renders CEF accelerated-paint DMABUF frames through GDK textures.
gtkgl
Package gtkgl contains GtkGLArea context probing helpers shared by probes and renderers.
Package gtkgl contains GtkGLArea context probing helpers shared by probes and renderers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL