Skip to content

Commit 5cc338d

Browse files
authored
Merge pull request #609 from lightpanda-io/ddg_compat
Work on DDG support (but still not working)
2 parents eadf188 + 200036e commit 5cc338d

File tree

5 files changed

+156
-91
lines changed

5 files changed

+156
-91
lines changed

src/browser/browser.zig

Lines changed: 97 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ pub const Page = struct {
268268
// load polyfills
269269
try polyfill.load(self.arena, self.scope);
270270

271-
_ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
271+
// _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
272272
}
273273

274274
fn microtaskCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
@@ -290,7 +290,7 @@ pub const Page = struct {
290290
try Dump.writeHTML(self.doc.?, out);
291291
}
292292

293-
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) ![]const u8 {
293+
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) !?[]const u8 {
294294
const self: *Page = @ptrCast(@alignCast(ctx));
295295

296296
log.debug("fetch module: specifier: {s}", .{specifier});
@@ -436,10 +436,18 @@ pub const Page = struct {
436436
// TODO fetch the script resources concurrently but execute them in the
437437
// declaration order for synchronous ones.
438438

439-
// sasync stores scripts which can be run asynchronously.
439+
// async_scripts stores scripts which can be run asynchronously.
440440
// for now they are just run after the non-async one in order to
441441
// dispatch DOMContentLoaded the sooner as possible.
442-
var sasync: std.ArrayListUnmanaged(Script) = .{};
442+
var async_scripts: std.ArrayListUnmanaged(Script) = .{};
443+
444+
// defer_scripts stores scripts which are meant to be deferred. For now
445+
// this doesn't have a huge impact, since normal scripts are parsed
446+
// after the document is loaded. But (a) we should fix that and (b)
447+
// this results in JavaScript being loaded in the same order as browsers
448+
// which can help debug issues (and might actually fix issues if websites
449+
// are expecting this execution order)
450+
var defer_scripts: std.ArrayListUnmanaged(Script) = .{};
443451

444452
const root = parser.documentToNode(doc);
445453
const walker = Walker{};
@@ -456,11 +464,6 @@ pub const Page = struct {
456464

457465
// ignore non-js script.
458466
const script = try Script.init(e) orelse continue;
459-
if (script.kind == .unknown) continue;
460-
461-
// Ignore the defer attribute b/c we analyze all script
462-
// after the document has been parsed.
463-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
464467

465468
// TODO use fetchpriority
466469
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#fetchpriority
@@ -471,22 +474,18 @@ pub const Page = struct {
471474
// > parsing and evaluated as soon as it is available.
472475
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async
473476
if (script.is_async) {
474-
try sasync.append(arena, script);
477+
try async_scripts.append(arena, script);
478+
continue;
479+
}
480+
481+
if (script.is_defer) {
482+
try defer_scripts.append(arena, script);
475483
continue;
476484
}
477485

478486
// TODO handle for attribute
479487
// TODO handle event attribute
480488

481-
// TODO defer
482-
// > This Boolean attribute is set to indicate to a browser
483-
// > that the script is meant to be executed after the
484-
// > document has been parsed, but before firing
485-
// > DOMContentLoaded.
486-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#defer
487-
// defer allow us to load a script w/o blocking the rest of
488-
// evaluations.
489-
490489
// > Scripts without async, defer or type="module"
491490
// > attributes, as well as inline scripts without the
492491
// > type="module" attribute, are fetched and executed
@@ -498,7 +497,11 @@ pub const Page = struct {
498497
try parser.documentHTMLSetCurrentScript(html_doc, null);
499498
}
500499

501-
// TODO wait for deferred scripts
500+
for (defer_scripts.items) |s| {
501+
try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(s.element));
502+
self.evalScript(&s) catch |err| log.warn("evaljs: {any}", .{err});
503+
try parser.documentHTMLSetCurrentScript(html_doc, null);
504+
}
502505

503506
// dispatch DOMContentLoaded before the transition to "complete",
504507
// at the point where all subresources apart from async script elements
@@ -511,7 +514,7 @@ pub const Page = struct {
511514
_ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, html_doc), evt);
512515

513516
// eval async scripts.
514-
for (sasync.items) |s| {
517+
for (async_scripts.items) |s| {
515518
try parser.documentHTMLSetCurrentScript(html_doc, @ptrCast(s.element));
516519
self.evalScript(&s) catch |err| log.warn("evaljs: {any}", .{err});
517520
try parser.documentHTMLSetCurrentScript(html_doc, null);
@@ -535,57 +538,42 @@ pub const Page = struct {
535538
// evalScript evaluates the src in priority.
536539
// if no src is present, we evaluate the text source.
537540
// https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
538-
fn evalScript(self: *Page, s: *const Script) !void {
539-
self.current_script = s;
540-
defer self.current_script = null;
541-
542-
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
543-
const opt_src = try parser.elementGetAttribute(s.element, "src");
544-
if (opt_src) |src| {
545-
log.debug("starting GET {s}", .{src});
546-
547-
self.fetchScript(s) catch |err| {
548-
switch (err) {
549-
FetchError.BadStatusCode => return err,
550-
551-
// TODO If el's result is null, then fire an event named error at
552-
// el, and return.
553-
FetchError.NoBody => return,
541+
fn evalScript(self: *Page, script: *const Script) !void {
542+
const src = script.src orelse {
543+
// source is inline
544+
// TODO handle charset attribute
545+
if (try parser.nodeTextContent(parser.elementToNode(script.element))) |text| {
546+
try script.eval(self, text);
547+
}
548+
return;
549+
};
554550

555-
FetchError.JsErr => {}, // nothing to do here.
556-
else => return err,
557-
}
558-
};
551+
self.current_script = script;
552+
defer self.current_script = null;
559553

560-
// TODO If el's from an external file is true, then fire an event
561-
// named load at el.
554+
log.debug("starting GET {s}", .{src});
562555

556+
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
557+
const body = (try self.fetchData(src, null)) orelse {
558+
// TODO If el's result is null, then fire an event named error at
559+
// el, and return
563560
return;
564-
}
561+
};
565562

566-
// TODO handle charset attribute
567-
const opt_text = try parser.nodeTextContent(parser.elementToNode(s.element));
568-
if (opt_text) |text| {
569-
try s.eval(self, text);
570-
return;
571-
}
563+
script.eval(self, body) catch |err| switch (err) {
564+
error.JsErr => {}, // nothing to do here.
565+
else => return err,
566+
};
572567

573-
// nothing has been loaded.
574-
// TODO If el's result is null, then fire an event named error at
575-
// el, and return.
568+
// TODO If el's from an external file is true, then fire an event
569+
// named load at el.
576570
}
577571

578-
const FetchError = error{
579-
BadStatusCode,
580-
NoBody,
581-
JsErr,
582-
};
583-
584572
// fetchData returns the data corresponding to the src target.
585573
// It resolves src using the page's uri.
586574
// If a base path is given, src is resolved according to the base first.
587575
// the caller owns the returned string
588-
fn fetchData(self: *const Page, src: []const u8, base: ?[]const u8) ![]const u8 {
576+
fn fetchData(self: *const Page, src: []const u8, base: ?[]const u8) !?[]const u8 {
589577
log.debug("starting fetch {s}", .{src});
590578

591579
const arena = self.arena;
@@ -620,7 +608,7 @@ pub const Page = struct {
620608
log.info("fetch {any}: {d}", .{ url, header.status });
621609

622610
if (header.status != 200) {
623-
return FetchError.BadStatusCode;
611+
return error.BadStatusCode;
624612
}
625613

626614
var arr: std.ArrayListUnmanaged(u8) = .{};
@@ -632,17 +620,12 @@ pub const Page = struct {
632620

633621
// check no body
634622
if (arr.items.len == 0) {
635-
return FetchError.NoBody;
623+
return null;
636624
}
637625

638626
return arr.items;
639627
}
640628

641-
fn fetchScript(self: *Page, s: *const Script) !void {
642-
const body = try self.fetchData(s.src, null);
643-
try s.eval(self, body);
644-
}
645-
646629
fn newHTTPRequest(self: *const Page, method: http.Request.Method, url: *const URL, opts: storage.cookie.LookupOpts) !http.Request {
647630
var request = try self.state.http_client.request(method, &url.uri);
648631
errdefer request.deinit();
@@ -738,28 +721,42 @@ pub const Page = struct {
738721
};
739722

740723
const Script = struct {
741-
element: *parser.Element,
742724
kind: Kind,
743725
is_async: bool,
726+
is_defer: bool,
727+
src: ?[]const u8,
728+
element: *parser.Element,
729+
// The javascript to load after we successfully load the script
730+
onload: ?[]const u8,
744731

745-
src: []const u8,
732+
// The javascript to load if we have an error executing the script
733+
// For now, we ignore this, since we still have a lot of errors that we
734+
// shouldn't
735+
//onerror: ?[]const u8,
746736

747737
const Kind = enum {
748-
unknown,
749-
javascript,
750738
module,
739+
javascript,
751740
};
752741

753742
fn init(e: *parser.Element) !?Script {
754743
// ignore non-script tags
755744
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e)));
756-
if (tag != .script) return null;
745+
if (tag != .script) {
746+
return null;
747+
}
748+
749+
const kind = parseKind(try parser.elementGetAttribute(e, "type")) orelse {
750+
return null;
751+
};
757752

758753
return .{
754+
.kind = kind,
759755
.element = e,
760-
.kind = parseKind(try parser.elementGetAttribute(e, "type")),
756+
.src = try parser.elementGetAttribute(e, "src"),
757+
.onload = try parser.elementGetAttribute(e, "onload"),
761758
.is_async = try parser.elementGetAttribute(e, "async") != null,
762-
.src = try parser.elementGetAttribute(e, "src") orelse "inline",
759+
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
763760
};
764761
}
765762

@@ -768,34 +765,47 @@ pub const Page = struct {
768765
// > type indicates that the script is a "classic script", containing
769766
// > JavaScript code.
770767
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
771-
fn parseKind(stype: ?[]const u8) Kind {
772-
if (stype == null or stype.?.len == 0) return .javascript;
773-
if (std.mem.eql(u8, stype.?, "application/javascript")) return .javascript;
774-
if (std.mem.eql(u8, stype.?, "text/javascript")) return .javascript;
775-
if (std.mem.eql(u8, stype.?, "module")) return .module;
768+
fn parseKind(script_type_: ?[]const u8) ?Kind {
769+
const script_type = script_type_ orelse return .javascript;
770+
if (script_type.len == 0) {
771+
return .javascript;
772+
}
776773

777-
return .unknown;
774+
if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
775+
if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
776+
if (std.mem.eql(u8, script_type, "module")) return .module;
777+
778+
return null;
778779
}
779780

780-
fn eval(self: Script, page: *Page, body: []const u8) !void {
781+
fn eval(self: *const Script, page: *Page, body: []const u8) !void {
781782
var try_catch: Env.TryCatch = undefined;
782783
try_catch.init(page.scope);
783784
defer try_catch.deinit();
784785

786+
const src = self.src orelse "inline";
785787
const res = switch (self.kind) {
786-
.unknown => return error.UnknownScript,
787-
.javascript => page.scope.exec(body, self.src),
788-
.module => page.scope.module(body, self.src),
788+
.javascript => page.scope.exec(body, src),
789+
.module => page.scope.module(body, src),
789790
} catch {
790791
if (try try_catch.err(page.arena)) |msg| {
791-
log.info("eval script {s}: {s}", .{ self.src, msg });
792+
log.info("eval script {s}: {s}", .{ src, msg });
792793
}
793-
return FetchError.JsErr;
794+
return error.JsErr;
794795
};
795796

796797
if (builtin.mode == .Debug) {
797798
const msg = try res.toString(page.arena);
798-
log.debug("eval script {s}: {s}", .{ self.src, msg });
799+
log.debug("eval script {s}: {s}", .{ src, msg });
800+
}
801+
802+
if (self.onload) |onload| {
803+
_ = page.scope.exec(onload, "script_on_load") catch {
804+
if (try try_catch.err(page.arena)) |msg| {
805+
log.info("eval script onload {s}: {s}", .{ src, msg });
806+
}
807+
return error.JsErr;
808+
};
799809
}
800810
}
801811
};

src/browser/html/html.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const Window = @import("window.zig").Window;
2323
const Navigator = @import("navigator.zig").Navigator;
2424
const History = @import("history.zig").History;
2525
const Location = @import("location.zig").Location;
26+
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
2627

2728
pub const Interfaces = .{
2829
HTMLDocument,
@@ -34,4 +35,5 @@ pub const Interfaces = .{
3435
Navigator,
3536
History,
3637
Location,
38+
MediaQueryList,
3739
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const parser = @import("../netsurf.zig");
20+
const Callback = @import("../env.zig").Callback;
21+
const EventTarget = @import("../dom/event_target.zig").EventTarget;
22+
23+
// https://drafts.csswg.org/cssom-view/#the-mediaquerylist-interface
24+
pub const MediaQueryList = struct {
25+
pub const prototype = *EventTarget;
26+
27+
// Extend libdom event target for pure zig struct.
28+
// This is not safe as it relies on a structure layout that isn't guaranteed
29+
base: parser.EventTargetTBase = parser.EventTargetTBase{},
30+
31+
matches: bool,
32+
media: []const u8,
33+
34+
pub fn get_matches(self: *const MediaQueryList) bool {
35+
return self.matches;
36+
}
37+
38+
pub fn get_media(self: *const MediaQueryList) []const u8 {
39+
return self.media;
40+
}
41+
42+
pub fn _addListener(_: *const MediaQueryList, _: Callback) void {}
43+
44+
pub fn _removeListener(_: *const MediaQueryList, _: Callback) void {}
45+
};

0 commit comments

Comments
 (0)