Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion go/examples/demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ func mustMap(value any, label string) map[string]any {
return result
}

func mustString(value any, label string) string {
result, ok := value.(string)
if !ok || result == "" {
log.Fatalf("%s returned non-string value: %v", label, value)
}
return result
}

func int64Value(value any) (int64, bool) {
switch typed := value.(type) {
case int64:
Expand Down Expand Up @@ -292,6 +300,47 @@ func main() {
fmt.Println("Mod.evaluate ->", string(b))
}

topologyChecked := false
if mode != "direct" {
topologyRaw, err := cdp.Mod.GetTopology(nil)
if err != nil {
log.Fatalf("Mod.getTopology: %v", err)
}
topology := mustMap(topologyRaw, "Mod.getTopology")
rootFrameID := mustString(topology["rootFrameId"], "Mod.getTopology.rootFrameId")
frames := mustMap(topology["frames"], "Mod.getTopology.frames")
roots := mustMap(topology["roots"], "Mod.getTopology.roots")
contexts := mustMap(topology["contexts"], "Mod.getTopology.contexts")
if _, ok := frames[rootFrameID]; !ok {
log.Fatalf("Mod.getTopology frames missing root frame %s: %v", rootFrameID, frames)
}
hasDocumentRoot := false
for _, root := range roots {
rootMap, ok := root.(map[string]any)
if ok && rootMap["kind"] == "document" {
hasDocumentRoot = true
}
}
hasPiercerContext := false
for _, context := range contexts {
contextMap, ok := context.(map[string]any)
if ok && contextMap["world"] == "piercer" {
hasPiercerContext = true
}
}
if !hasDocumentRoot || !hasPiercerContext {
log.Fatalf("unexpected Mod.getTopology result: %v", topology)
}
topologyChecked = true
b, _ := json.Marshal(map[string]any{
"rootFrameId": rootFrameID,
"frames": len(frames),
"roots": len(roots),
"contexts": len(contexts),
})
fmt.Println("Mod.getTopology ->", string(b))
}

responseMiddlewareRegistrationRaw, err := cdp.Mod.AddMiddleware(modcdp.CustomMiddleware{
Name: "Custom.echo",
Phase: "response",
Expand Down Expand Up @@ -376,7 +425,11 @@ func main() {
runtimeJSON, _ := json.Marshal(runtimeEval)
fmt.Println("Runtime.evaluate ->", string(runtimeJSON))

fmt.Printf("\nSUCCESS (%s/%s): native command, custom commands, custom event, and middleware all passed\n", mode, upstreamMode)
topologyLabel := ""
if topologyChecked {
topologyLabel = "topology, "
}
fmt.Printf("\nSUCCESS (%s/%s): native command, %scustom commands, custom event, and middleware all passed\n", mode, upstreamMode, topologyLabel)

// TTY-only REPL. Lets you poke at the live browser interactively;
// subscribed events print as they arrive. Skip when stdin is not a tty
Expand Down
4 changes: 4 additions & 0 deletions go/modcdp/client/ModCDPClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,10 @@ func (d ModDomain) Ping(params map[string]any) (any, error) {
return d.client.Send("Mod.ping", params)
}

func (d ModDomain) GetTopology(params map[string]any) (any, error) {
return d.client.Send("Mod.getTopology", params)
}

func (c *ModCDPClient) sendCommand(method string, params map[string]any, cdpSessionID string, validateSchema bool) (any, error) {
startedAt := time.Now().UnixMilli()
if params == nil {
Expand Down
46 changes: 46 additions & 0 deletions go/modcdp/client/ModCDPClientRoutedDefaultOverrides_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,52 @@ func TestModCDPClientRoutedDefaultOverrides(t *testing.T) {
t.Fatal("expected at least one page target to be matched to a chrome.tabs tab id")
}

topologyRaw, err := cdp.Mod.GetTopology(nil)
if err != nil {
t.Fatal(err)
}
topology, ok := topologyRaw.(map[string]any)
if !ok {
t.Fatalf("Mod.getTopology returned %T: %#v", topologyRaw, topologyRaw)
}
rootFrameID, ok := topology["rootFrameId"].(string)
if !ok || rootFrameID == "" {
t.Fatalf("Mod.getTopology rootFrameId = %#v", topology["rootFrameId"])
}
frames, ok := topology["frames"].(map[string]any)
if !ok {
t.Fatalf("Mod.getTopology frames = %T: %#v", topology["frames"], topology["frames"])
}
if _, ok := frames[rootFrameID]; !ok {
t.Fatalf("Mod.getTopology frames missing rootFrameId %q: %#v", rootFrameID, frames)
}
roots, ok := topology["roots"].(map[string]any)
if !ok {
t.Fatalf("Mod.getTopology roots = %T: %#v", topology["roots"], topology["roots"])
}
hasDocumentRoot := false
for _, root := range roots {
if rootMap, ok := root.(map[string]any); ok && rootMap["kind"] == "document" {
hasDocumentRoot = true
}
}
if !hasDocumentRoot {
t.Fatalf("Mod.getTopology should include at least one document root: %#v", roots)
}
contexts, ok := topology["contexts"].(map[string]any)
if !ok {
t.Fatalf("Mod.getTopology contexts = %T: %#v", topology["contexts"], topology["contexts"])
}
hasPiercerContext := false
for _, context := range contexts {
if contextMap, ok := context.(map[string]any); ok && contextMap["world"] == "piercer" {
hasPiercerContext = true
}
}
if !hasPiercerContext {
t.Fatalf("Mod.getTopology should include a piercer execution context: %#v", contexts)
}

if _, err := cdp.Mod.AddCustomEvent(CustomEvent{Name: "Target.targetCreated"}); err != nil {
t.Fatal(err)
}
Expand Down
Binary file modified go/modcdp/injector/extension.zip
Binary file not shown.
22 changes: 21 additions & 1 deletion js/examples/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,26 @@ async function main() {
throw new Error(`unexpected Mod.evaluate result ${JSON.stringify(modcdpEval)}`);
console.log("Mod.evaluate ->", modcdpEval);

let topologyChecked = false;
if (mode !== "direct") {
const topology = assertObject(await cdp.Mod.getTopology(), "Mod.getTopology");
if (
typeof topology.rootFrameId !== "string" ||
!topology.frames?.[topology.rootFrameId] ||
!Object.values(topology.roots || {}).some((root: any) => root?.kind === "document") ||
!Object.values(topology.contexts || {}).some((context: any) => context?.world === "piercer")
) {
throw new Error(`unexpected Mod.getTopology result ${JSON.stringify(topology)}`);
}
topologyChecked = true;
console.log("Mod.getTopology ->", {
rootFrameId: topology.rootFrameId,
frames: Object.keys(topology.frames || {}).length,
roots: Object.keys(topology.roots || {}).length,
contexts: Object.keys(topology.contexts || {}).length,
});
}

const responseMiddlewareRegistration = assertObject(
await cdp.Mod.addMiddleware({
name: "Custom.echo",
Expand Down Expand Up @@ -373,7 +393,7 @@ async function main() {
console.log("Runtime.evaluate ->", runtimeEval);

console.log(
`\nSUCCESS (${mode}/${upstream_mode}): native command, custom commands, custom event, and middleware all passed`,
`\nSUCCESS (${mode}/${upstream_mode}): native command, ${topologyChecked ? "topology, " : ""}custom commands, custom event, and middleware all passed`,
);

// Drop into an interactive prompt when stdin is a TTY. Lets you poke at
Expand Down
2 changes: 2 additions & 0 deletions js/src/client/ModCDPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ export class ModCDPClient extends ModCDPEventEmitter {
}
this.command_params_schemas.set("Mod.evaluate", Mod.EvaluateParams);
this.command_result_schemas.set("Mod.evaluate", Mod.EvaluateResponse);
this.command_params_schemas.set("Mod.getTopology", Mod.GetTopologyParams);
this.command_result_schemas.set("Mod.getTopology", Mod.GetTopologyResponse);
this.command_params_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandParams);
this.command_result_schemas.set("Mod.addCustomCommand", Mod.AddCustomCommandResponse);
this.command_params_schemas.set("Mod.addCustomEvent", Mod.AddCustomEventParams);
Expand Down
Loading
Loading