Skip to content

Commit a21c8b0

Browse files
committed
docs: update docs about iframes and contexts
1 parent 1b86155 commit a21c8b0

File tree

3 files changed

+229
-112
lines changed

3 files changed

+229
-112
lines changed

docs/en/deep-dive/fundamentals/iframes-and-contexts.md

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -717,25 +717,38 @@ async def _find_frame_by_owner(
717717

718718
#### **Step 3: Resolve OOPIF by Parent Frame**
719719

720-
**Goal**: For Out-of-Process Iframes, find the target, attach to it, and obtain the `frameId` from the target's frame tree.
720+
**Goal**: For Out-of-Process Iframes, find the correct target, attach to it, and obtain the `frameId` from the target's frame tree (and the routing `sessionId` when needed).
721+
722+
**When this runs**:
723+
724+
- Same-origin / in-process iframes that already have a `frameId` and **no** `backendNodeId` skip this step (they are handled directly).
725+
- Cross-origin / OOPIF iframes (with `backendNodeId`) or iframes whose `frameId` could not be resolved via Step 2 use this step.
721726

722727
**Strategy**:
723728

724-
**3a. Direct child target lookup**:
729+
**3a. Direct child target lookup (fast path)**:
730+
731+
1. Call `Target.getTargets()` to list all debugging targets.
732+
2. Filter targets where `type` is `"iframe"` or `"page"` and `parentFrameId` matches our parent frame.
733+
3. If there is **exactly one** matching child **and we don't have a `backendNodeId`**, attach directly to that target with `Target.attachToTarget(targetId, flatten=true)`.
734+
4. Fetch `Page.getFrameTree(sessionId)` for that target; the root frame of this tree is our iframe's frame.
735+
736+
When there are **multiple** direct children or we have a `backendNodeId` (typical OOPIF case), Pydoll iterates over each child target:
725737

726-
1. Call `Target.getTargets()` to list all debugging targets
727-
2. Filter targets where `type` is `"iframe"` or `"page"` and `parentFrameId` matches our parent frame
728-
3. If found, attach to that target with `Target.attachToTarget(targetId, flatten=true)`
729-
4. The response includes a `sessionId`
730-
5. Fetch `Page.getFrameTree(sessionId)` for that target
731-
6. The root frame of this tree is our iframe's frame
738+
1. Attach via `Target.attachToTarget(flatten=true)`.
739+
2. Fetch `Page.getFrameTree(sessionId)` and read the root `frame.id`.
740+
3. Call `DOM.getFrameOwner(frameId=root_id)` on the **main** connection.
741+
4. Compare the returned `backendNodeId` with our iframe element's `backendNodeId`.
742+
5. The child whose root owner matches is selected as the correct OOPIF target.
732743

733-
**3b. Fallback: Scan all targets**:
744+
**3b. Fallback: Scan all targets (root owner + child search)**:
734745

735-
1. Iterate all iframe/page targets
736-
2. Attach to each and fetch its frame tree
737-
3. Look for a child frame whose `parentId` matches our parent frame
738-
4. **OR** check if the root frame's owner (via `DOM.getFrameOwner`) matches our iframe's `backendNodeId`
746+
If no suitable direct child is found (or when `parentFrameId` information is incomplete), Pydoll falls back to scanning **all** iframe/page targets:
747+
748+
1. Iterate all iframe/page targets.
749+
2. Attach to each and fetch its frame tree.
750+
3. First, try to match the **root frame owner** via `DOM.getFrameOwner(root_frame_id)` against our iframe's `backendNodeId`.
751+
4. If that does not match, look for a **child frame** whose `parentId` equals our `parent_frame_id` (this covers cases where the OOPIF is nested under an intermediate frame).
739752

740753
**Code**:
741754

@@ -746,40 +759,61 @@ async def _resolve_oopif_by_parent(
746759
parent_frame_id: str,
747760
backend_node_id: Optional[int],
748761
) -> tuple[Optional[ConnectionHandler], Optional[str], Optional[str], Optional[str]]:
749-
"""Resolve an OOPIF using the parent frame id."""
762+
"""Resolve an OOPIF using the given parent frame id."""
750763
browser_handler = ConnectionHandler(
751764
connection_port=self._connection_handler._connection_port
752765
)
753766
targets_response: GetTargetsResponse = await browser_handler.execute_command(
754767
TargetCommands.get_targets()
755768
)
756769
target_infos = targets_response.get('result', {}).get('targetInfos', [])
757-
758-
# Strategy 3a: Direct children
770+
771+
# Strategy 3a: Direct children (fast path)
759772
direct_children = [
760773
target_info
761774
for target_info in target_infos
762775
if target_info.get('type') in {'iframe', 'page'}
763776
and target_info.get('parentFrameId') == parent_frame_id
764777
]
765-
if direct_children:
778+
779+
is_single_child = len(direct_children) == 1
780+
for child_target in direct_children:
766781
attach_response: AttachToTargetResponse = await browser_handler.execute_command(
767782
TargetCommands.attach_to_target(
768-
target_id=direct_children[0]['targetId'], flatten=True
783+
target_id=child_target['targetId'], flatten=True
769784
)
770785
)
771786
attached_session_id = attach_response.get('result', {}).get('sessionId')
772-
if attached_session_id:
773-
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
774-
root_frame = (frame_tree or {}).get('frame', {})
787+
if not attached_session_id:
788+
continue
789+
790+
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
791+
root_frame = (frame_tree or {}).get('frame', {})
792+
root_frame_id = root_frame.get('id', '')
793+
794+
# Same-origin / simple case: single child and no backend_node_id
795+
if is_single_child and root_frame_id and backend_node_id is None:
775796
return (
776797
browser_handler,
777798
attached_session_id,
778-
root_frame.get('id'),
799+
root_frame_id,
779800
root_frame.get('url'),
780801
)
781-
782-
# Strategy 3b: Scan all targets
802+
803+
# OOPIF case: confirm ownership via DOM.getFrameOwner
804+
if root_frame_id and backend_node_id is not None:
805+
owner_backend_id = await self._owner_backend_for(
806+
self._connection_handler, None, root_frame_id
807+
)
808+
if owner_backend_id == backend_node_id:
809+
return (
810+
browser_handler,
811+
attached_session_id,
812+
root_frame_id,
813+
root_frame.get('url'),
814+
)
815+
816+
# Strategy 3b: Scan all targets (root owner + child search)
783817
for target_info in target_infos:
784818
if target_info.get('type') not in {'iframe', 'page'}:
785819
continue
@@ -791,24 +825,29 @@ async def _resolve_oopif_by_parent(
791825
attached_session_id = attach_response.get('result', {}).get('sessionId')
792826
if not attached_session_id:
793827
continue
794-
828+
795829
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
796-
797-
# Check for child with matching parentId
830+
root_frame = (frame_tree or {}).get('frame', {})
831+
root_frame_id = root_frame.get('id', '')
832+
833+
# Try matching root owner by backend_node_id
834+
if root_frame_id and backend_node_id is not None:
835+
owner_backend_id = await self._owner_backend_for(
836+
self._connection_handler, None, root_frame_id
837+
)
838+
if owner_backend_id == backend_node_id:
839+
return (
840+
browser_handler,
841+
attached_session_id,
842+
root_frame_id,
843+
root_frame.get('url'),
844+
)
845+
846+
# Fallback: match a child frame whose parentId equals parent_frame_id
798847
child_frame_id = WebElement._find_child_by_parent(frame_tree, parent_frame_id)
799848
if child_frame_id:
800849
return browser_handler, attached_session_id, child_frame_id, None
801-
802-
# Check if root frame's owner matches our backendNodeId
803-
root_frame_id = (frame_tree or {}).get('frame', {}).get('id', '')
804-
if not root_frame_id or backend_node_id is None:
805-
continue
806-
owner_backend_id = await self._owner_backend_for(
807-
self._connection_handler, None, root_frame_id
808-
)
809-
if owner_backend_id == backend_node_id:
810-
return browser_handler, attached_session_id, root_frame_id, None
811-
850+
812851
return None, None, None, None
813852
```
814853

docs/pt/deep-dive/fundamentals/iframes-and-contexts.md

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -717,25 +717,38 @@ async def _find_frame_by_owner(
717717

718718
#### **Passo 3: Resolver OOPIF pelo Frame Pai**
719719

720-
**Objetivo**: Para Iframes Fora de Processo, encontrar o alvo (target), anexar a ele, e obter o `frameId` da árvore de frames do alvo.
720+
**Objetivo**: Para Iframes Fora de Processo, encontrar o alvo correto, anexar a ele e obter o `frameId` da árvore de frames do alvo (e o `sessionId` de roteamento quando necessário).
721+
722+
**Quando esse passo roda**:
723+
724+
- Iframes de **mesma origem** / in-process que já têm um `frameId` e **não** têm `backendNodeId` pulam esse passo (são tratados diretamente).
725+
- Iframes **cross-origin / OOPIF** (com `backendNodeId`) ou iframes cujo `frameId` não pôde ser resolvido no Passo 2 usam esse passo.
721726

722727
**Estratégia**:
723728

724-
**3a. Busca por alvo filho direto**:
729+
**3a. Busca por alvo filho direto (caminho rápido)**:
730+
731+
1. Chamar `Target.getTargets()` para listar todos os alvos de depuração.
732+
2. Filtrar alvos onde `type` é `"iframe"` ou `"page"` e `parentFrameId` bate com nosso frame pai.
733+
3. Se houver **apenas um** filho direto **e não houver `backendNodeId`**, anexar diretamente a esse alvo com `Target.attachToTarget(targetId, flatten=true)`.
734+
4. Buscar `Page.getFrameTree(sessionId)` para aquele alvo; o frame raiz dessa árvore é o frame do nosso iframe.
735+
736+
Quando existem **múltiplos** filhos diretos ou temos um `backendNodeId` (caso típico de OOPIF), o Pydoll itera sobre cada alvo filho:
725737

726-
1. Chamar `Target.getTargets()` para listar todos os alvos de depuração
727-
2. Filtrar alvos onde `type` é `"iframe"` ou `"page"` e `parentFrameId` bate com nosso frame pai
728-
3. Se encontrado, anexar àquele alvo com `Target.attachToTarget(targetId, flatten=true)`
729-
4. A resposta inclui um `sessionId`
730-
5. Buscar `Page.getFrameTree(sessionId)` para aquele alvo
731-
6. O frame raiz desta árvore é o frame do nosso iframe
738+
1. Anexa com `Target.attachToTarget(flatten=true)`.
739+
2. Busca `Page.getFrameTree(sessionId)` e lê o `frame.id` raiz.
740+
3. Chama `DOM.getFrameOwner(frameId=root_id)` na conexão principal.
741+
4. Compara o `backendNodeId` retornado com o `backendNodeId` do elemento `<iframe>` original.
742+
5. O filho cujo dono raiz coincide é selecionado como o alvo OOPIF correto.
732743

733-
**3b. Fallback: Escanear todos os alvos**:
744+
**3b. Fallback: Escanear todos os alvos (dono raiz + busca por filho)**:
734745

735-
1. Iterar todos os alvos iframe/page
736-
2. Anexar a cada um e buscar sua árvore de frames
737-
3. Procurar por um frame filho cujo `parentId` bate com nosso frame pai
738-
4. **OU** checar se o dono do frame raiz (via `DOM.getFrameOwner`) bate com o `backendNodeId` do nosso iframe
746+
Se nenhum filho direto adequado for encontrado (ou se `parentFrameId` estiver incompleto), o Pydoll recorre a escanear **todos** os alvos iframe/page:
747+
748+
1. Iterar todos os alvos iframe/page.
749+
2. Anexar a cada um e buscar sua árvore de frames.
750+
3. Primeiro, tentar casar o **dono do frame raiz** via `DOM.getFrameOwner(root_frame_id)` com o `backendNodeId` do iframe.
751+
4. Se isso não bater, procurar um **frame filho** cujo `parentId` seja o `parent_frame_id` (isso cobre casos em que o OOPIF está aninhado sob um frame intermediário).
739752

740753
**Código**:
741754

@@ -754,32 +767,53 @@ async def _resolve_oopif_by_parent(
754767
TargetCommands.get_targets()
755768
)
756769
target_infos = targets_response.get('result', {}).get('targetInfos', [])
757-
758-
# Estratégia 3a: Filhos diretos
770+
771+
# Estratégia 3a: Filhos diretos (caminho rápido)
759772
direct_children = [
760773
target_info
761774
for target_info in target_infos
762775
if target_info.get('type') in {'iframe', 'page'}
763776
and target_info.get('parentFrameId') == parent_frame_id
764777
]
765-
if direct_children:
778+
779+
is_single_child = len(direct_children) == 1
780+
for child_target in direct_children:
766781
attach_response: AttachToTargetResponse = await browser_handler.execute_command(
767782
TargetCommands.attach_to_target(
768-
target_id=direct_children[0]['targetId'], flatten=True
783+
target_id=child_target['targetId'], flatten=True
769784
)
770785
)
771786
attached_session_id = attach_response.get('result', {}).get('sessionId')
772-
if attached_session_id:
773-
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
774-
root_frame = (frame_tree or {}).get('frame', {})
787+
if not attached_session_id:
788+
continue
789+
790+
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
791+
root_frame = (frame_tree or {}).get('frame', {})
792+
root_frame_id = root_frame.get('id', '')
793+
794+
# Caso simples / mesma origem: filho único e sem backend_node_id
795+
if is_single_child and root_frame_id and backend_node_id is None:
775796
return (
776797
browser_handler,
777798
attached_session_id,
778-
root_frame.get('id'),
799+
root_frame_id,
779800
root_frame.get('url'),
780801
)
781-
782-
# Estratégia 3b: Escanear todos os alvos
802+
803+
# Caso OOPIF: confirmar propriedade via DOM.getFrameOwner
804+
if root_frame_id and backend_node_id is not None:
805+
owner_backend_id = await self._owner_backend_for(
806+
self._connection_handler, None, root_frame_id
807+
)
808+
if owner_backend_id == backend_node_id:
809+
return (
810+
browser_handler,
811+
attached_session_id,
812+
root_frame_id,
813+
root_frame.get('url'),
814+
)
815+
816+
# Estratégia 3b: Escanear todos os alvos (dono raiz + busca por filho)
783817
for target_info in target_infos:
784818
if target_info.get('type') not in {'iframe', 'page'}:
785819
continue
@@ -791,24 +825,29 @@ async def _resolve_oopif_by_parent(
791825
attached_session_id = attach_response.get('result', {}).get('sessionId')
792826
if not attached_session_id:
793827
continue
794-
828+
795829
frame_tree = await self._get_frame_tree_for(browser_handler, attached_session_id)
796-
797-
# Checa por filho com parentId correspondente
830+
root_frame = (frame_tree or {}).get('frame', {})
831+
root_frame_id = root_frame.get('id', '')
832+
833+
# Primeiro tenta casar o dono do frame raiz via backend_node_id
834+
if root_frame_id and backend_node_id is not None:
835+
owner_backend_id = await self._owner_backend_for(
836+
self._connection_handler, None, root_frame_id
837+
)
838+
if owner_backend_id == backend_node_id:
839+
return (
840+
browser_handler,
841+
attached_session_id,
842+
root_frame_id,
843+
root_frame.get('url'),
844+
)
845+
846+
# Fallback: procurar frame filho cujo parentId seja parent_frame_id
798847
child_frame_id = WebElement._find_child_by_parent(frame_tree, parent_frame_id)
799848
if child_frame_id:
800849
return browser_handler, attached_session_id, child_frame_id, None
801-
802-
# Checa se o dono do frame raiz bate com nosso backendNodeId
803-
root_frame_id = (frame_tree or {}).get('frame', {}).get('id', '')
804-
if not root_frame_id or backend_node_id is None:
805-
continue
806-
owner_backend_id = await self._owner_backend_for(
807-
self._connection_handler, None, root_frame_id
808-
)
809-
if owner_backend_id == backend_node_id:
810-
return browser_handler, attached_session_id, root_frame_id, None
811-
850+
812851
return None, None, None, None
813852
```
814853

0 commit comments

Comments
 (0)