{"version":3,"file":"dom/service.mjs","sources":["webpack://@agent-infra/browser-use/./src/dom/service.ts"],"sourcesContent":["/**\n * The following code is modified based on\n * https://github.com/nanobrowser/nanobrowser/blob/master/chrome-extension/src/background/dom/service.ts\n *\n * Apache-2.0 License\n * Copyright (c) 2024 alexchenzl\n * https://github.com/nanobrowser/nanobrowser/blob/master/LICENSE\n */\nimport { createLogger } from '../utils';\nimport type { BuildDomTreeArgs, RawDomTreeNode } from './raw_types';\nimport {\n  type DOMState,\n  type DOMBaseNode,\n  DOMElementNode,\n  DOMTextNode,\n} from './views';\nimport { Page } from 'puppeteer-core';\n\nconst logger = createLogger('DOMService');\n\nexport interface ReadabilityResult {\n  title: string;\n  content: string;\n  textContent: string;\n  length: number;\n  excerpt: string;\n  byline: string;\n  dir: string;\n  siteName: string;\n  lang: string;\n  publishedTime: string;\n}\n\ndeclare global {\n  interface Window {\n    buildDomTree: (args: BuildDomTreeArgs) => RawDomTreeNode | null;\n    turn2Markdown: (selector?: string) => string;\n    parserReadability: () => ReadabilityResult | null;\n  }\n}\n\n/**\n * Get the scroll information for the current page.\n * @returns A tuple containing the number of pixels above and below the current scroll position.\n */\nexport async function getScrollInfo(page: Page): Promise<[number, number]> {\n  const result = await page.evaluate(() => {\n    const scroll_y = window.scrollY;\n    const viewport_height = window.innerHeight;\n    const total_height = document.documentElement.scrollHeight;\n    return {\n      pixels_above: scroll_y,\n      pixels_below: total_height - (scroll_y + viewport_height),\n    };\n  });\n\n  if (!result) {\n    throw new Error('Failed to get scroll information');\n  }\n  return [result.pixels_above, result.pixels_below];\n}\n\n/**\n * Get the markdown content for the current page.\n * @param tabId - The ID of the tab to get the markdown content for.\n * @param selector - The selector to get the markdown content for. If not provided, the body of the entire page will be converted to markdown.\n * @returns The markdown content for the selected element on the current page.\n */\nexport async function getMarkdownContent(\n  page: Page,\n  selector?: string,\n): Promise<string> {\n  const result = await page.evaluate((sel) => {\n    return window.turn2Markdown(sel);\n  }, selector || '');\n\n  if (!result) {\n    throw new Error('Failed to get markdown content');\n  }\n  return result as string;\n}\n\n/**\n * Get the readability content for the current page.\n * @param tabId - The ID of the tab to get the readability content for.\n * @returns The readability content for the current page.\n */\nexport async function getReadabilityContent(\n  page: Page,\n): Promise<ReadabilityResult> {\n  const result = await page.evaluate(() => {\n    return window.parserReadability();\n  });\n  if (!result) {\n    throw new Error('Failed to get readability content');\n  }\n  return result as ReadabilityResult;\n}\n\n/**\n/**\n * Get the clickable elements for the current page.\n * @param tabId - The ID of the tab to get the clickable elements for.\n * @param highlightElements - Whether to highlight the clickable elements.\n * @param focusElement - The element to focus on.\n * @param viewportExpansion - The viewport expansion to use.\n * @returns A DOMState object containing the clickable elements for the current page.\n */\nexport async function getClickableElements(\n  page: Page,\n  highlightElements = true,\n  focusElement = -1,\n  viewportExpansion = 0,\n): Promise<DOMState | null> {\n  try {\n    const elementTree = await _buildDomTree(\n      page,\n      highlightElements,\n      focusElement,\n      viewportExpansion,\n    );\n    const selectorMap = createSelectorMap(elementTree);\n    return { elementTree, selectorMap };\n  } catch (error) {\n    logger.error('Failed to build DOM tree:', error);\n    return null;\n  }\n}\n\nexport function createSelectorMap(\n  elementTree: DOMElementNode,\n): Map<number, DOMElementNode> {\n  const selectorMap = new Map<number, DOMElementNode>();\n\n  function processNode(node: DOMBaseNode): void {\n    if (node instanceof DOMElementNode) {\n      if (node.highlightIndex != null) {\n        // console.log('createSelectorMap node.highlightIndex:', node.highlightIndex);\n        selectorMap.set(node.highlightIndex, node);\n      }\n      node.children.forEach(processNode);\n    }\n  }\n\n  processNode(elementTree);\n  return selectorMap;\n}\n\nasync function _buildDomTree(\n  page: Page,\n  highlightElements = true,\n  focusElement = -1,\n  viewportExpansion = 0,\n): Promise<DOMElementNode> {\n  const rawDomTree = await page.evaluate(\n    (args) => {\n      // Access buildDomTree from the window context of the target page\n      return window.buildDomTree(args);\n    },\n    {\n      doHighlightElements: highlightElements,\n      focusHighlightIndex: focusElement,\n      viewportExpansion,\n    },\n  );\n\n  if (rawDomTree !== null) {\n    const elementTree = parseNode(rawDomTree as RawDomTreeNode);\n    if (elementTree !== null && elementTree instanceof DOMElementNode) {\n      return elementTree;\n    }\n  }\n  throw new Error('Failed to build DOM tree: Invalid or empty tree structure');\n}\n\nexport function parseNode(\n  nodeData: RawDomTreeNode,\n  parent: DOMElementNode | null = null,\n): DOMBaseNode | null {\n  if (!nodeData) return null;\n\n  if ('type' in nodeData) {\n    // && nodeData.type === 'TEXT_NODE'\n    return new DOMTextNode(nodeData.text, nodeData.isVisible, parent);\n  }\n\n  const tagName = nodeData.tagName;\n\n  // Parse coordinates if they exist\n  const viewportCoordinates = nodeData.viewportCoordinates;\n  const pageCoordinates = nodeData.pageCoordinates;\n  const viewportInfo = nodeData.viewportInfo;\n\n  // Element node (possible other kinds of nodes, but we don't care about them for now)\n  const elementNode = new DOMElementNode({\n    tagName: tagName,\n    xpath: nodeData.xpath,\n    cssSelector: nodeData.cssSelector,\n    attributes: nodeData.attributes ?? {},\n    children: [],\n    isVisible: nodeData.isVisible ?? false,\n    isInteractive: nodeData.isInteractive ?? false,\n    isTopElement: nodeData.isTopElement ?? false,\n    highlightIndex: nodeData.highlightIndex,\n    viewportCoordinates: viewportCoordinates ?? undefined,\n    pageCoordinates: pageCoordinates ?? undefined,\n    viewportInfo: viewportInfo ?? undefined,\n    shadowRoot: nodeData.shadowRoot ?? false,\n    parent,\n  });\n\n  const children: DOMBaseNode[] = [];\n  for (const child of nodeData.children || []) {\n    if (child !== null) {\n      const childNode = parseNode(child, elementNode);\n      if (childNode !== null) {\n        children.push(childNode);\n      }\n    }\n  }\n\n  elementNode.children = children;\n  return elementNode;\n}\n\nexport async function removeHighlights(page: Page): Promise<void> {\n  try {\n    await page.evaluate(() => {\n      // Remove the highlight container and all its contents\n      const container = document.getElementById(\n        'playwright-highlight-container',\n      );\n      if (container) {\n        container.remove();\n      }\n\n      // Remove highlight attributes from elements\n      const highlightedElements = document.querySelectorAll(\n        '[browser-user-highlight-id^=\"playwright-highlight-\"]',\n      );\n      for (const el of Array.from(highlightedElements)) {\n        el.removeAttribute('browser-user-highlight-id');\n      }\n    });\n  } catch (error) {\n    logger.error('Failed to remove highlights:', error);\n  }\n}\n"],"names":["logger","createLogger","getScrollInfo","page","result","scroll_y","window","viewport_height","total_height","document","Error","getMarkdownContent","selector","sel","getReadabilityContent","getClickableElements","highlightElements","focusElement","viewportExpansion","elementTree","_buildDomTree","selectorMap","createSelectorMap","error","Map","processNode","node","DOMElementNode","rawDomTree","args","parseNode","nodeData","parent","DOMTextNode","tagName","viewportCoordinates","pageCoordinates","viewportInfo","elementNode","undefined","children","child","childNode","removeHighlights","container","highlightedElements","el","Array"],"mappings":"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"}