mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Add a "pre" polyfill
This is always run, but only the full webcomponents polyfill, it's very small and isn't intrusive. This introduces a layer of indirection so that, if the full polyfill is loaded, its monkeypatched constructor will be called
This commit is contained in:
@@ -120,6 +120,7 @@ pub const Page = struct {
|
|||||||
.main_context = undefined,
|
.main_context = undefined,
|
||||||
};
|
};
|
||||||
self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader));
|
self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader));
|
||||||
|
try polyfill.preload(self.arena, self.main_context);
|
||||||
|
|
||||||
// message loop must run only non-test env
|
// message loop must run only non-test env
|
||||||
if (comptime !builtin.is_test) {
|
if (comptime !builtin.is_test) {
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ pub const Loader = struct {
|
|||||||
if (!self.done.webcomponents and isWebcomponents(name)) {
|
if (!self.done.webcomponents and isWebcomponents(name)) {
|
||||||
const source = @import("webcomponents.zig").source;
|
const source = @import("webcomponents.zig").source;
|
||||||
self.load("webcomponents", source, js_context);
|
self.load("webcomponents", source, js_context);
|
||||||
|
|
||||||
// We return false here: We want v8 to continue the calling chain
|
// We return false here: We want v8 to continue the calling chain
|
||||||
// to finally find the polyfill we just inserted. If we want to
|
// to finally find the polyfill we just inserted. If we want to
|
||||||
// return false and stops the call chain, we have to use
|
// return false and stops the call chain, we have to use
|
||||||
@@ -103,3 +102,19 @@ pub const Loader = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn preload(allocator: Allocator, js_context: *Env.JsContext) !void {
|
||||||
|
var try_catch: Env.TryCatch = undefined;
|
||||||
|
try_catch.init(js_context);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
const name = "webcomponents-pre";
|
||||||
|
const source = @import("webcomponents.zig").pre;
|
||||||
|
_ = js_context.exec(source, name) catch |err| {
|
||||||
|
if (try try_catch.err(allocator)) |msg| {
|
||||||
|
defer allocator.free(msg);
|
||||||
|
log.fatal(.app, "polyfill error", .{ .name = name, .err = msg });
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,21 +6,51 @@
|
|||||||
// This is the `webcomponents-ce.js` bundle
|
// This is the `webcomponents-ce.js` bundle
|
||||||
pub const source = @embedFile("webcomponents.js");
|
pub const source = @embedFile("webcomponents.js");
|
||||||
|
|
||||||
|
// The main webcomponents.js is lazilly loaded when window.customElements is
|
||||||
|
// called. But, if you look at the test below, you'll notice that we declare
|
||||||
|
// our custom element (LightPanda) before we call `customElements.define`. We
|
||||||
|
// _have_ to declare it before we can register it.
|
||||||
|
// That causes an issue, because the LightPanda class extends HTMLElement, which
|
||||||
|
// hasn't been monkeypatched by the polyfill yet. If you were to try it as-is
|
||||||
|
// you'd get an "Illegal Constructor", because that's what the Zig HTMLElement
|
||||||
|
// constructor does (and that's correct).
|
||||||
|
// However, once HTMLElement is monkeypatched, it'll work. One simple solution
|
||||||
|
// is to run the webcomponents.js polyfill proactively on each page, ensuring
|
||||||
|
// that HTMLElement is monkeypatched before any other JavaScript is run. But
|
||||||
|
// that adds _a lot_ of overhead.
|
||||||
|
// So instead of always running the [large and intrusive] webcomponents.js
|
||||||
|
// polyfill, we'll always run this little snippet. It wraps the HTMLElement
|
||||||
|
// constructor. When the Lightpanda class is created, it'll extend our little
|
||||||
|
// wrapper. But, unlike the Zig default constructor which throws, our code
|
||||||
|
// calls the "real" constructor. That might seem like the same thing, but by the
|
||||||
|
// time our wrapper is called, the webcomponents.js polyfill will have been
|
||||||
|
// loaded and the "real" constructor will be the monkeypatched version.
|
||||||
|
// TL;DR creates a layer of indirection for the constructor, so that, when it's
|
||||||
|
// actually instantiated, the webcomponents.js polyfill will have been loaded.
|
||||||
|
pub const pre =
|
||||||
|
\\ (() => {
|
||||||
|
\\ const HE = window.HTMLElement;
|
||||||
|
\\ const b = function() { return HE.prototype.constructor.call(this); }
|
||||||
|
\\ b.prototype = HE.prototype;
|
||||||
|
\\ window.HTMLElement = b;
|
||||||
|
\\ })();
|
||||||
|
;
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "Browser.webcomponents" {
|
test "Browser.webcomponents" {
|
||||||
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html = "<div id=main></div>" });
|
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html = "<div id=main></div>" });
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try @import("polyfill.zig").preload(testing.allocator, runner.page.main_context);
|
||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{
|
.{
|
||||||
\\ window.customElements; // temporarily needed, lazy loading doesn't work!
|
|
||||||
\\
|
|
||||||
\\ class LightPanda extends HTMLElement {
|
\\ class LightPanda extends HTMLElement {
|
||||||
\\ constructor() {
|
\\ constructor() {
|
||||||
\\ super();
|
\\ super();
|
||||||
\\ }
|
\\ }
|
||||||
\\ connectedCallback() {
|
\\ connectedCallback() {
|
||||||
\\ this.append('connected')
|
\\ this.append('connected');
|
||||||
\\ }
|
\\ }
|
||||||
\\ }
|
\\ }
|
||||||
\\ window.customElements.define("lightpanda-test", LightPanda);
|
\\ window.customElements.define("lightpanda-test", LightPanda);
|
||||||
|
|||||||
@@ -284,6 +284,9 @@ pub fn pageCreated(bc: anytype, page: *Page) !void {
|
|||||||
if (bc.isolated_world) |*isolated_world| {
|
if (bc.isolated_world) |*isolated_world| {
|
||||||
// We need to recreate the isolated world context
|
// We need to recreate the isolated world context
|
||||||
try isolated_world.createContext(page);
|
try isolated_world.createContext(page);
|
||||||
|
|
||||||
|
const polyfill = @import("../../browser/polyfill/polyfill.zig");
|
||||||
|
try polyfill.preload(bc.arena, &isolated_world.executor.js_context.?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ fn run(
|
|||||||
});
|
});
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
|
try polyfill.preload(arena, runner.page.main_context);
|
||||||
|
|
||||||
// loop over the scripts.
|
// loop over the scripts.
|
||||||
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
const doc = parser.documentHTMLToDocument(runner.page.window.document);
|
||||||
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
const scripts = try parser.documentGetElementsByTagName(doc, "script");
|
||||||
|
|||||||
Reference in New Issue
Block a user