Class: Dommy::Js::ScriptBooter
- Inherits:
-
Object
- Object
- Dommy::Js::ScriptBooter
- Defined in:
- lib/dommy/js/script_boot.rb
Overview
One document's script-boot run. Instantiated per boot by ScriptBoot; the collaborators are ivars so the per-script steps take only what varies.
Constant Summary collapse
- HANDLER_ATTRIBUTES =
Compile each element's event-handler content attributes (onclick="…", oninput="…") into an event handler, reusing the working
el.onclick = fnIDL path. Runs once at boot, after parsing and before scripts (matching the spec, where content attributes are set as the document is parsed), so a listener is live for post-load interaction.new Functionparses the code as a function body with aneventparameter; a syntactically bad handler is skipped, not fatal.Two subtleties: (1) a handler on
/ %w[ onabort onauxclick onbeforeinput onbeforetoggle onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus onformdata oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmousedown onmouseenter onmouseleave onmousemove onmouseout onmouseover onmouseup onpaste onpause onplay onplaying onprogress onratechange onreset onresize onscroll onscrollend onseeked onseeking onselect onslotchange onstalled onsubmit onsuspend ontimeupdate ontoggle onvolumechange onwaiting onwheel onafterprint onbeforeprint onbeforeunload onhashchange onlanguagechange onmessage onmessageerror onoffline ononline onpagehide onpageshow onpopstate onrejectionhandled onstorage onunhandledrejection onunload ].freeze
- WINDOW_REFLECTED_HANDLERS =
Body/frameset handlers for these events reflect onto the Window.
%w[ onafterprint onbeforeprint onbeforeunload onhashchange onlanguagechange onmessage onmessageerror onoffline ononline onpagehide onpageshow onpopstate onrejectionhandled onstorage onunhandledrejection onunload onload onresize onscroll onerror onblur onfocus ].freeze
- WIRE_INLINE_HANDLERS_JS =
<<~JS (() => { const HANDLERS = new Set(#{HANDLER_ATTRIBUTES.to_json}); const REFLECTED = new Set(#{WINDOW_REFLECTED_HANDLERS.to_json}); const selector = [...HANDLERS].map((name) => `[${name}]`).join(","); const body = document.body; // An inline handler runs with a scope chain of [element, form owner, // document] inside the global, per the HTML "compile" algorithm — so // `onclick="getElementById(...)"` (document) or a form-associated // control's bare member resolve. Reflected (body/window) handlers keep // the plain global scope (their `this` is the window, not the element). const scoped = (el, code) => { let src = `with(this){\n${code}\n}`; if (el.form) src = `with(this.form){\n${src}\n}`; return `with(document){\n${src}\n}`; }; for (const el of document.querySelectorAll(selector)) { const onBody = el === body || el.tagName === "FRAMESET"; for (const name of el.getAttributeNames()) { if (!HANDLERS.has(name)) continue; const code = el.getAttribute(name); try { if (onBody && REFLECTED.has(name)) { window.addEventListener(name.slice(2), new Function("event", code)); } else if (typeof el[name] !== "function") { let fn; try { fn = new Function("event", scoped(el, code)); } catch { fn = new Function("event", code); } // fall back to plain scope el[name] = fn; } } catch { // A syntactically invalid handler is skipped, not fatal. } } } })(); JS
Instance Method Summary collapse
-
#initialize(runtime, document, resources: nil, on_error: nil, on_script: nil) ⇒ ScriptBooter
constructor
A new instance of ScriptBooter.
- #run ⇒ Object
-
#run_inserted_external(element, src) ⇒ Object
Fetch + run a dynamically-inserted external script, then fire
load(orerrorif the fetch failed / it threw) so a loader awaiting the script element's onload resolves. - #wire_inline_event_handlers ⇒ Object
Constructor Details
#initialize(runtime, document, resources: nil, on_error: nil, on_script: nil) ⇒ ScriptBooter
Returns a new instance of ScriptBooter.
46 47 48 49 50 51 52 53 |
# File 'lib/dommy/js/script_boot.rb', line 46 def initialize(runtime, document, resources: nil, on_error: nil, on_script: nil) @runtime = runtime @document = document @resources = resources @on_error = on_error @on_script = on_script @loader = nil end |
Instance Method Details
#run ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/dommy/js/script_boot.rb', line 128 def run @runtime.set_document_ready_state("loading") @loader = install_module_loader wire_inline_event_handlers scripts = @document.scripts.to_a # Pass 1: parser-blocking classic scripts, in document order. scripts.each { |element| run_one(element) unless deferred?(element) } # Pass 2: deferred scripts (modules + classic `defer`), in document order. scripts.each { |element| run_one(element) if deferred?(element) } @runtime.set_document_ready_state("interactive") @runtime.set_document_ready_state("complete") end |
#run_inserted_external(element, src) ⇒ Object
Fetch + run a dynamically-inserted external script, then fire load (or
error if the fetch failed / it threw) so a loader awaiting the script
element's onload resolves. The src was already taken from the element by
the mutation coordinator, so this does not re-consume pending state.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/dommy/js/script_boot.rb', line 151 def run_inserted_external(element, src) ran = false if @resources && (url = resolve_url(src)) && (response = @resources.get(url)) && response.success? with_current_script(element) { @runtime.load_script_cached(response.body, cache_key: url) } ran = true end dispatch_script_event(element, ran ? "load" : "error") rescue StandardError => e @on_error&.call(e) dispatch_script_event(element, "error") end |
#wire_inline_event_handlers ⇒ Object
141 142 143 144 145 |
# File 'lib/dommy/js/script_boot.rb', line 141 def wire_inline_event_handlers @runtime.execute(WIRE_INLINE_HANDLERS_JS) rescue StandardError => e @on_error&.call(e) end |