├── .gitignore
├── chrome_control
├── base.py
├── Inspector.py
├── Schema.py
├── Tethering.py
├── DeviceOrientation.py
├── Storage.py
├── IO.py
├── Memory.py
├── __init__.py
├── Console.py
├── Rendering.py
├── DOMStorage.py
├── Database.py
├── SystemInfo.py
├── CacheStorage.py
├── Log.py
├── ApplicationCache.py
├── Profiler.py
├── Tracing.py
├── chrome.py
├── Security.py
├── HeapProfiler.py
├── ServiceWorker.py
├── DOMDebugger.py
├── Target.py
├── IndexedDB.py
├── Animation.py
├── LayerTree.py
├── Accessibility.py
├── Emulation.py
├── Input.py
├── Debugger.py
├── Page.py
├── Runtime.py
├── Network.py
├── CSS.py
└── DOM.py
├── setup.py
├── test.py
└── gen.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | *.pyc
3 |
4 | venv/
5 |
--------------------------------------------------------------------------------
/chrome_control/base.py:
--------------------------------------------------------------------------------
1 | class ChromeCommand: pass
2 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 | setup(
4 | name='chrome_control',
5 | version='0.1',
6 | packages=['chrome_control'],
7 | )
8 |
--------------------------------------------------------------------------------
/chrome_control/Inspector.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class enable(ChromeCommand):
8 | """Enables inspector domain notifications."""
9 |
10 | def __init__(self): pass
11 |
12 | class disable(ChromeCommand):
13 | """Disables inspector domain notifications."""
14 |
15 | def __init__(self): pass
16 |
17 |
--------------------------------------------------------------------------------
/chrome_control/Schema.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class Domain:
8 | """Description of the protocol domain."""
9 | def __init__(self, name: str, version: str):
10 | # Domain name.
11 | self.name = name
12 | # Domain version.
13 | self.version = version
14 |
15 | class getDomains(ChromeCommand):
16 | """Returns supported domains."""
17 |
18 | def __init__(self): pass
19 |
20 |
--------------------------------------------------------------------------------
/chrome_control/Tethering.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class bind(ChromeCommand):
8 | """Request browser port binding."""
9 |
10 | def __init__(self, port: int):
11 | # Port number to bind.
12 | self.port = port
13 |
14 |
15 |
16 | class unbind(ChromeCommand):
17 | """Request browser port unbinding."""
18 |
19 | def __init__(self, port: int):
20 | # Port number to unbind.
21 | self.port = port
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from chrome_control import Chrome, Page, Runtime
4 |
5 | c = Chrome()
6 | c.do(Page.navigate("http://adhocteam.us/our-team"))
7 |
8 | # if we don't wait for the page to load, then we can run the script too
9 | # early and get an empty array.
10 | #
11 | # TODO a great next step would be to figure out how to receive pageLoad
12 | # event from Page and only run the command at that time
13 | time.sleep(2)
14 |
15 | cmd = '[].map.call(document.querySelectorAll("h3.centered"), n => n.textContent)'
16 | c.do(Runtime.evaluate(cmd, returnByValue=True))
17 |
--------------------------------------------------------------------------------
/chrome_control/DeviceOrientation.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class setDeviceOrientationOverride(ChromeCommand):
8 | """Overrides the Device Orientation."""
9 |
10 | def __init__(self, alpha: float, beta: float, gamma: float):
11 | # Mock alpha
12 | self.alpha = alpha
13 | # Mock beta
14 | self.beta = beta
15 | # Mock gamma
16 | self.gamma = gamma
17 |
18 |
19 |
20 | class clearDeviceOrientationOverride(ChromeCommand):
21 | """Clears the overridden Device Orientation."""
22 |
23 | def __init__(self): pass
24 |
25 |
--------------------------------------------------------------------------------
/chrome_control/Storage.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | StorageType = Enum("StorageType", "appcache cookies file_systems indexeddb local_storage shader_cache websql service_workers cache_storage all")
8 | StorageType.__doc__ = """Enum of possible storage types."""
9 |
10 | class clearDataForOrigin(ChromeCommand):
11 | """Clears storage for origin."""
12 |
13 | def __init__(self, origin: str, storageTypes: str):
14 | # Security origin.
15 | self.origin = origin
16 | # Comma separated origin names.
17 | self.storageTypes = storageTypes
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/chrome_control/IO.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | StreamHandle = str
8 |
9 | class read(ChromeCommand):
10 | """Read a chunk of the stream"""
11 |
12 | def __init__(self, handle: "StreamHandle", offset: int=None, size: int=None):
13 | # Handle of the stream to read.
14 | self.handle = handle
15 | # Seek to the specified offset before reading (if not specificed, proceed with offset following the last read).
16 | self.offset = offset
17 | # Maximum number of bytes to read (left upon the agent discretion if not specified).
18 | self.size = size
19 |
20 |
21 |
22 | class close(ChromeCommand):
23 | """Close the stream, discard any temporary backing storage."""
24 |
25 | def __init__(self, handle: "StreamHandle"):
26 | # Handle of the stream to close.
27 | self.handle = handle
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/chrome_control/Memory.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | PressureLevel = Enum("PressureLevel", "moderate critical")
8 | PressureLevel.__doc__ = """Memory pressure level."""
9 |
10 | class getDOMCounters(ChromeCommand):
11 | def __init__(self): pass
12 |
13 | class setPressureNotificationsSuppressed(ChromeCommand):
14 | """Enable/disable suppressing memory pressure notifications in all processes."""
15 |
16 | def __init__(self, suppressed: bool):
17 | # If true, memory pressure notifications will be suppressed.
18 | self.suppressed = suppressed
19 |
20 |
21 |
22 | class simulatePressureNotification(ChromeCommand):
23 | """Simulate a memory pressure notification in all processes."""
24 |
25 | def __init__(self, level: "PressureLevel"):
26 | # Memory pressure level of the notification.
27 | self.level = level
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/chrome_control/__init__.py:
--------------------------------------------------------------------------------
1 | from .chrome import Chrome
2 | from . import Accessibility
3 | from . import Animation
4 | from . import ApplicationCache
5 | from . import CSS
6 | from . import CacheStorage
7 | from . import Console
8 | from . import DOM
9 | from . import DOMDebugger
10 | from . import DOMStorage
11 | from . import Database
12 | from . import Debugger
13 | from . import DeviceOrientation
14 | from . import Emulation
15 | from . import HeapProfiler
16 | from . import IO
17 | from . import IndexedDB
18 | from . import Input
19 | from . import Inspector
20 | from . import LayerTree
21 | from . import Log
22 | from . import Memory
23 | from . import Network
24 | from . import Page
25 | from . import Profiler
26 | from . import Rendering
27 | from . import Runtime
28 | from . import Schema
29 | from . import Security
30 | from . import ServiceWorker
31 | from . import Storage
32 | from . import SystemInfo
33 | from . import Target
34 | from . import Tethering
35 |
--------------------------------------------------------------------------------
/chrome_control/Console.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class ConsoleMessage:
8 | """Console message."""
9 | def __init__(self, source: str, level: str, text: str, url: str=None, line: int=None, column: int=None):
10 | # Message source.
11 | self.source = source
12 | # Message severity.
13 | self.level = level
14 | # Message text.
15 | self.text = text
16 | # URL of the message origin.
17 | self.url = url
18 | # Line number in the resource that generated this message (1-based).
19 | self.line = line
20 | # Column number in the resource that generated this message (1-based).
21 | self.column = column
22 |
23 | class enable(ChromeCommand):
24 | """Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification."""
25 |
26 | def __init__(self): pass
27 |
28 | class disable(ChromeCommand):
29 | """Disables console domain, prevents further console messages from being reported to the client."""
30 |
31 | def __init__(self): pass
32 |
33 | class clearMessages(ChromeCommand):
34 | """Does nothing."""
35 |
36 | def __init__(self): pass
37 |
38 |
--------------------------------------------------------------------------------
/chrome_control/Rendering.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class setShowPaintRects(ChromeCommand):
8 | """Requests that backend shows paint rectangles"""
9 |
10 | def __init__(self, result: bool):
11 | # True for showing paint rectangles
12 | self.result = result
13 |
14 |
15 |
16 | class setShowDebugBorders(ChromeCommand):
17 | """Requests that backend shows debug borders on layers"""
18 |
19 | def __init__(self, show: bool):
20 | # True for showing debug borders
21 | self.show = show
22 |
23 |
24 |
25 | class setShowFPSCounter(ChromeCommand):
26 | """Requests that backend shows the FPS counter"""
27 |
28 | def __init__(self, show: bool):
29 | # True for showing the FPS counter
30 | self.show = show
31 |
32 |
33 |
34 | class setShowScrollBottleneckRects(ChromeCommand):
35 | """Requests that backend shows scroll bottleneck rects"""
36 |
37 | def __init__(self, show: bool):
38 | # True for showing scroll bottleneck rects
39 | self.show = show
40 |
41 |
42 |
43 | class setShowViewportSizeOnResize(ChromeCommand):
44 | """Paints viewport size upon main frame resize."""
45 |
46 | def __init__(self, show: bool):
47 | # Whether to paint size or not.
48 | self.show = show
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/chrome_control/DOMStorage.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class StorageId:
8 | """DOM Storage identifier."""
9 | def __init__(self, securityOrigin: str, isLocalStorage: bool):
10 | # Security origin for the storage.
11 | self.securityOrigin = securityOrigin
12 | # Whether the storage is local storage (not session storage).
13 | self.isLocalStorage = isLocalStorage
14 |
15 | # DOM Storage item.
16 | Item = List[str]
17 | class enable(ChromeCommand):
18 | """Enables storage tracking, storage events will now be delivered to the client."""
19 |
20 | def __init__(self): pass
21 |
22 | class disable(ChromeCommand):
23 | """Disables storage tracking, prevents storage events from being sent to the client."""
24 |
25 | def __init__(self): pass
26 |
27 | class getDOMStorageItems(ChromeCommand):
28 | def __init__(self, storageId: "StorageId"):
29 | self.storageId = storageId
30 |
31 |
32 |
33 | class setDOMStorageItem(ChromeCommand):
34 | def __init__(self, storageId: "StorageId", key: str, value: str):
35 | self.storageId = storageId
36 | self.key = key
37 | self.value = value
38 |
39 |
40 |
41 | class removeDOMStorageItem(ChromeCommand):
42 | def __init__(self, storageId: "StorageId", key: str):
43 | self.storageId = storageId
44 | self.key = key
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/chrome_control/Database.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | # Unique identifier of Database object.
8 | DatabaseId = str
9 |
10 | class Database:
11 | """Database object."""
12 | def __init__(self, id: "DatabaseId", domain: str, name: str, version: str):
13 | # Database ID.
14 | self.id = id
15 | # Database domain.
16 | self.domain = domain
17 | # Database name.
18 | self.name = name
19 | # Database version.
20 | self.version = version
21 |
22 | class Error:
23 | """Database error."""
24 | def __init__(self, message: str, code: int):
25 | # Error message.
26 | self.message = message
27 | # Error code.
28 | self.code = code
29 |
30 | class enable(ChromeCommand):
31 | """Enables database tracking, database events will now be delivered to the client."""
32 |
33 | def __init__(self): pass
34 |
35 | class disable(ChromeCommand):
36 | """Disables database tracking, prevents database events from being sent to the client."""
37 |
38 | def __init__(self): pass
39 |
40 | class getDatabaseTableNames(ChromeCommand):
41 | def __init__(self, databaseId: "DatabaseId"):
42 | self.databaseId = databaseId
43 |
44 |
45 |
46 | class executeSQL(ChromeCommand):
47 | def __init__(self, databaseId: "DatabaseId", query: str):
48 | self.databaseId = databaseId
49 | self.query = query
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/chrome_control/SystemInfo.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class GPUDevice:
8 | """Describes a single graphics processor (GPU)."""
9 | def __init__(self, vendorId: float, deviceId: float, vendorString: str, deviceString: str):
10 | # PCI ID of the GPU vendor, if available; 0 otherwise.
11 | self.vendorId = vendorId
12 | # PCI ID of the GPU device, if available; 0 otherwise.
13 | self.deviceId = deviceId
14 | # String description of the GPU vendor, if the PCI ID is not available.
15 | self.vendorString = vendorString
16 | # String description of the GPU device, if the PCI ID is not available.
17 | self.deviceString = deviceString
18 |
19 | class GPUInfo:
20 | """Provides information about the GPU(s) on the system."""
21 | def __init__(self, devices: List, driverBugWorkarounds: List, auxAttributes: dict=None, featureStatus: dict=None):
22 | # The graphics devices on the system. Element 0 is the primary GPU.
23 | self.devices = devices
24 | # An optional array of GPU driver bug workarounds.
25 | self.driverBugWorkarounds = driverBugWorkarounds
26 | # An optional dictionary of additional GPU related attributes.
27 | self.auxAttributes = auxAttributes
28 | # An optional dictionary of graphics features and their status.
29 | self.featureStatus = featureStatus
30 |
31 | class getInfo(ChromeCommand):
32 | """Returns information about the system."""
33 |
34 | def __init__(self): pass
35 |
36 |
--------------------------------------------------------------------------------
/chrome_control/CacheStorage.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | # Unique identifier of the Cache object.
8 | CacheId = str
9 |
10 | class DataEntry:
11 | """Data entry."""
12 | def __init__(self, request: str, response: str):
13 | # Request url spec.
14 | self.request = request
15 | # Response stataus text.
16 | self.response = response
17 |
18 | class Cache:
19 | """Cache identifier."""
20 | def __init__(self, cacheId: "CacheId", securityOrigin: str, cacheName: str):
21 | # An opaque unique id of the cache.
22 | self.cacheId = cacheId
23 | # Security origin of the cache.
24 | self.securityOrigin = securityOrigin
25 | # The name of the cache.
26 | self.cacheName = cacheName
27 |
28 | class requestCacheNames(ChromeCommand):
29 | """Requests cache names."""
30 |
31 | def __init__(self, securityOrigin: str):
32 | # Security origin.
33 | self.securityOrigin = securityOrigin
34 |
35 |
36 |
37 | class requestEntries(ChromeCommand):
38 | """Requests data from cache."""
39 |
40 | def __init__(self, cacheId: "CacheId", skipCount: int, pageSize: int):
41 | # ID of cache to get entries from.
42 | self.cacheId = cacheId
43 | # Number of records to skip.
44 | self.skipCount = skipCount
45 | # Number of records to fetch.
46 | self.pageSize = pageSize
47 |
48 |
49 |
50 | class deleteCache(ChromeCommand):
51 | """Deletes a cache."""
52 |
53 | def __init__(self, cacheId: "CacheId"):
54 | # Id of cache for deletion.
55 | self.cacheId = cacheId
56 |
57 |
58 |
59 | class deleteEntry(ChromeCommand):
60 | """Deletes a cache entry."""
61 |
62 | def __init__(self, cacheId: "CacheId", request: str):
63 | # Id of cache where the entry will be deleted.
64 | self.cacheId = cacheId
65 | # URL spec of the request.
66 | self.request = request
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/chrome_control/Log.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Runtime
7 | from . import Network
8 |
9 | class LogEntry:
10 | """Log entry."""
11 | def __init__(self, source: str, level: str, text: str, timestamp: "Runtime.Timestamp", url: str=None, lineNumber: int=None, stackTrace: "Runtime.StackTrace"=None, networkRequestId: "Network.RequestId"=None, workerId: str=None):
12 | # Log entry source.
13 | self.source = source
14 | # Log entry severity.
15 | self.level = level
16 | # Logged text.
17 | self.text = text
18 | # Timestamp when this entry was added.
19 | self.timestamp = timestamp
20 | # URL of the resource if known.
21 | self.url = url
22 | # Line number in the resource.
23 | self.lineNumber = lineNumber
24 | # JavaScript stack trace.
25 | self.stackTrace = stackTrace
26 | # Identifier of the network request associated with this entry.
27 | self.networkRequestId = networkRequestId
28 | # Identifier of the worker associated with this entry.
29 | self.workerId = workerId
30 |
31 | class ViolationSetting:
32 | """Violation configuration setting."""
33 | def __init__(self, name: str, threshold: float):
34 | # Violation type.
35 | self.name = name
36 | # Time threshold to trigger upon.
37 | self.threshold = threshold
38 |
39 | class enable(ChromeCommand):
40 | """Enables log domain, sends the entries collected so far to the client by means of the entryAdded notification."""
41 |
42 | def __init__(self): pass
43 |
44 | class disable(ChromeCommand):
45 | """Disables log domain, prevents further log entries from being reported to the client."""
46 |
47 | def __init__(self): pass
48 |
49 | class clear(ChromeCommand):
50 | """Clears the log."""
51 |
52 | def __init__(self): pass
53 |
54 | class startViolationsReport(ChromeCommand):
55 | """start violation reporting."""
56 |
57 | def __init__(self, config: List):
58 | # Configuration for violations.
59 | self.config = config
60 |
61 |
62 |
63 | class stopViolationsReport(ChromeCommand):
64 | """Stop violation reporting."""
65 |
66 | def __init__(self): pass
67 |
68 |
--------------------------------------------------------------------------------
/chrome_control/ApplicationCache.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Page
7 |
8 | class ApplicationCacheResource:
9 | """Detailed application cache resource information."""
10 | def __init__(self, url: str, size: int, type: str):
11 | # Resource url.
12 | self.url = url
13 | # Resource size.
14 | self.size = size
15 | # Resource type.
16 | self.type = type
17 |
18 | class ApplicationCache:
19 | """Detailed application cache information."""
20 | def __init__(self, manifestURL: str, size: float, creationTime: float, updateTime: float, resources: List):
21 | # Manifest URL.
22 | self.manifestURL = manifestURL
23 | # Application cache size.
24 | self.size = size
25 | # Application cache creation time.
26 | self.creationTime = creationTime
27 | # Application cache update time.
28 | self.updateTime = updateTime
29 | # Application cache resources.
30 | self.resources = resources
31 |
32 | class FrameWithManifest:
33 | """Frame identifier - manifest URL pair."""
34 | def __init__(self, frameId: "Page.FrameId", manifestURL: str, status: int):
35 | # Frame identifier.
36 | self.frameId = frameId
37 | # Manifest URL.
38 | self.manifestURL = manifestURL
39 | # Application cache status.
40 | self.status = status
41 |
42 | class getFramesWithManifests(ChromeCommand):
43 | """Returns array of frame identifiers with manifest urls for each frame containing a document associated with some application cache."""
44 |
45 | def __init__(self): pass
46 |
47 | class enable(ChromeCommand):
48 | """Enables application cache domain notifications."""
49 |
50 | def __init__(self): pass
51 |
52 | class getManifestForFrame(ChromeCommand):
53 | """Returns manifest URL for document in the given frame."""
54 |
55 | def __init__(self, frameId: "Page.FrameId"):
56 | # Identifier of the frame containing document whose manifest is retrieved.
57 | self.frameId = frameId
58 |
59 |
60 |
61 | class getApplicationCacheForFrame(ChromeCommand):
62 | """Returns relevant application cache data for the document in given frame."""
63 |
64 | def __init__(self, frameId: "Page.FrameId"):
65 | # Identifier of the frame containing document whose application cache is retrieved.
66 | self.frameId = frameId
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/chrome_control/Profiler.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Runtime
7 |
8 | class ProfileNode:
9 | """Profile node. Holds callsite information, execution statistics and child nodes."""
10 | def __init__(self, id: int, callFrame: "Runtime.CallFrame", hitCount: int=None, children: List=None, deoptReason: str=None, positionTicks: List=None):
11 | # Unique id of the node.
12 | self.id = id
13 | # Function location.
14 | self.callFrame = callFrame
15 | # Number of samples where this node was on top of the call stack.
16 | self.hitCount = hitCount
17 | # Child node ids.
18 | self.children = children
19 | # The reason of being not optimized. The function may be deoptimized or marked as don't optimize.
20 | self.deoptReason = deoptReason
21 | # An array of source position ticks.
22 | self.positionTicks = positionTicks
23 |
24 | class Profile:
25 | """Profile."""
26 | def __init__(self, nodes: List, startTime: float, endTime: float, samples: List=None, timeDeltas: List=None):
27 | # The list of profile nodes. First item is the root node.
28 | self.nodes = nodes
29 | # Profiling start timestamp in microseconds.
30 | self.startTime = startTime
31 | # Profiling end timestamp in microseconds.
32 | self.endTime = endTime
33 | # Ids of samples top nodes.
34 | self.samples = samples
35 | # Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime.
36 | self.timeDeltas = timeDeltas
37 |
38 | class PositionTickInfo:
39 | """Specifies a number of samples attributed to a certain source position."""
40 | def __init__(self, line: int, ticks: int):
41 | # Source line number (1-based).
42 | self.line = line
43 | # Number of samples attributed to the source line.
44 | self.ticks = ticks
45 |
46 | class enable(ChromeCommand):
47 | def __init__(self): pass
48 |
49 | class disable(ChromeCommand):
50 | def __init__(self): pass
51 |
52 | class setSamplingInterval(ChromeCommand):
53 | """Changes CPU profiler sampling interval. Must be called before CPU profiles recording started."""
54 |
55 | def __init__(self, interval: int):
56 | # New sampling interval in microseconds.
57 | self.interval = interval
58 |
59 |
60 |
61 | class start(ChromeCommand):
62 | def __init__(self): pass
63 |
64 | class stop(ChromeCommand):
65 | def __init__(self): pass
66 |
67 |
--------------------------------------------------------------------------------
/chrome_control/Tracing.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | class MemoryDumpConfig: pass
8 |
9 | class TraceConfig:
10 | def __init__(self, recordMode: str=None, enableSampling: bool=None, enableSystrace: bool=None, enableArgumentFilter: bool=None, includedCategories: List=None, excludedCategories: List=None, syntheticDelays: List=None, memoryDumpConfig: "MemoryDumpConfig"=None):
11 | # Controls how the trace buffer stores data.
12 | self.recordMode = recordMode
13 | # Turns on JavaScript stack sampling.
14 | self.enableSampling = enableSampling
15 | # Turns on system tracing.
16 | self.enableSystrace = enableSystrace
17 | # Turns on argument filter.
18 | self.enableArgumentFilter = enableArgumentFilter
19 | # Included category filters.
20 | self.includedCategories = includedCategories
21 | # Excluded category filters.
22 | self.excludedCategories = excludedCategories
23 | # Configuration to synthesize the delays in tracing.
24 | self.syntheticDelays = syntheticDelays
25 | # Configuration for memory dump triggers. Used only when "memory-infra" category is enabled.
26 | self.memoryDumpConfig = memoryDumpConfig
27 |
28 | class start(ChromeCommand):
29 | """Start trace events collection."""
30 |
31 | def __init__(self, categories: str=None, options: str=None, bufferUsageReportingInterval: float=None, transferMode: str=None, traceConfig: "TraceConfig"=None):
32 | # Category/tag filter
33 | self.categories = categories
34 | # Tracing options
35 | self.options = options
36 | # If set, the agent will issue bufferUsage events at this interval, specified in milliseconds
37 | self.bufferUsageReportingInterval = bufferUsageReportingInterval
38 | # Whether to report trace events as series of dataCollected events or to save trace to a stream (defaults to ReportEvents).
39 | self.transferMode = transferMode
40 | #
41 | self.traceConfig = traceConfig
42 |
43 |
44 |
45 | class end(ChromeCommand):
46 | """Stop trace events collection."""
47 |
48 | def __init__(self): pass
49 |
50 | class getCategories(ChromeCommand):
51 | """Gets supported tracing categories."""
52 |
53 | def __init__(self): pass
54 |
55 | class requestMemoryDump(ChromeCommand):
56 | """Request a global memory dump."""
57 |
58 | def __init__(self): pass
59 |
60 | class recordClockSyncMarker(ChromeCommand):
61 | """Record a clock sync marker in the trace."""
62 |
63 | def __init__(self, syncId: str):
64 | # The ID of this clock sync marker
65 | self.syncId = syncId
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/chrome_control/chrome.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 | import websocket
4 | from chrome_control.base import ChromeCommand
5 |
6 | class ObjectEncoder(json.JSONEncoder):
7 | def default(self, obj):
8 | if isinstance(obj, (str, int, float, list, bool, dict)):
9 | return json.JSONEncoder.default(self, obj)
10 | attrs = [x for x in dir(obj) if not x.startswith('_')]
11 | return {key: getattr(obj, key) for key in attrs if getattr(obj, key) is not None}
12 |
13 | class Chrome:
14 | def __init__(self, debug=1):
15 | self.debug = debug
16 |
17 | # is this documented anywhere? I had to find this from reading node code
18 | # https://github.com/cyrus-and/chrome-remote-interface/blob/2a85a87574053f4ef48d17ac1ac03b7513310336/lib/devtools.js#L65
19 | #
20 | # there appears to be an endpoint that's equivalent to /json/new, at:
21 | # https://chromedevtools.github.io/debugger-protocol-viewer/tot/Target/#method-createTarget
22 | #
23 | # update, no this is not documented anywhere:
24 | # https://github.com/GoogleChrome/devtools-docs/issues/67#issuecomment-37675331
25 | self.tab = requests.get("http://localhost:9222/json/new").json()
26 | tabs = requests.get("http://localhost:9222/json").json()
27 |
28 | # we need to find the index of our new tab in the tablist, which the API
29 | # calls the "id", but also doesn't come back in the /new obj. Search the tablist
30 | # for our tab.
31 | for i, t in enumerate(tabs):
32 | if t["id"] == self.tab["id"]:
33 | self.idx = i
34 | break
35 | else: 1/0
36 |
37 | # * Maybe a tab is the proper unit for the client to deal with? Something like:
38 | # with Tab(url) as tab:
39 | # tab.do(Page.navigate("http://bananas.com"))
40 | # tab.do(Runtime.evaluate("console.log(window.location);", returnByValue=true))
41 | self.ws = websocket.create_connection(self.tab['webSocketDebuggerUrl'])
42 |
43 | def do(self, cmd: ChromeCommand):
44 | # Reverse engineer the command name that was passed in from the object's
45 | # meta information. Converts class like `chrome_control.Page.navigate`
46 | # to `'Page.navigate'`.
47 | method = f'{cmd.__module__.split(".")[-1]}.{cmd.__class__.__name__}'
48 |
49 | msg = {
50 | "id": self.idx,
51 | "method": method,
52 | "params": cmd
53 | }
54 | if self.debug:
55 | print("sent: ", json.dumps(msg, cls=ObjectEncoder))
56 |
57 | self.ws.send(json.dumps(msg, cls=ObjectEncoder))
58 |
59 | o = json.loads(self.ws.recv())
60 | if self.debug:
61 | print("rcvd: ", o)
62 |
63 | return o
64 |
--------------------------------------------------------------------------------
/chrome_control/Security.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | # An internal certificate ID value.
8 | CertificateId = int
9 |
10 | SecurityState = Enum("SecurityState", "unknown neutral insecure warning secure info")
11 | SecurityState.__doc__ = """The security level of a page or resource."""
12 |
13 | class SecurityStateExplanation:
14 | """An explanation of an factor contributing to the security state."""
15 | def __init__(self, securityState: "SecurityState", summary: str, description: str, hasCertificate: bool):
16 | # Security state representing the severity of the factor being explained.
17 | self.securityState = securityState
18 | # Short phrase describing the type of factor.
19 | self.summary = summary
20 | # Full text explanation of the factor.
21 | self.description = description
22 | # True if the page has a certificate.
23 | self.hasCertificate = hasCertificate
24 |
25 | class InsecureContentStatus:
26 | """Information about insecure content on the page."""
27 | def __init__(self, ranMixedContent: bool, displayedMixedContent: bool, ranContentWithCertErrors: bool, displayedContentWithCertErrors: bool, ranInsecureContentStyle: "SecurityState", displayedInsecureContentStyle: "SecurityState"):
28 | # True if the page was loaded over HTTPS and ran mixed (HTTP) content such as scripts.
29 | self.ranMixedContent = ranMixedContent
30 | # True if the page was loaded over HTTPS and displayed mixed (HTTP) content such as images.
31 | self.displayedMixedContent = displayedMixedContent
32 | # True if the page was loaded over HTTPS without certificate errors, and ran content such as scripts that were loaded with certificate errors.
33 | self.ranContentWithCertErrors = ranContentWithCertErrors
34 | # True if the page was loaded over HTTPS without certificate errors, and displayed content such as images that were loaded with certificate errors.
35 | self.displayedContentWithCertErrors = displayedContentWithCertErrors
36 | # Security state representing a page that ran insecure content.
37 | self.ranInsecureContentStyle = ranInsecureContentStyle
38 | # Security state representing a page that displayed insecure content.
39 | self.displayedInsecureContentStyle = displayedInsecureContentStyle
40 |
41 | class enable(ChromeCommand):
42 | """Enables tracking security state changes."""
43 |
44 | def __init__(self): pass
45 |
46 | class disable(ChromeCommand):
47 | """Disables tracking security state changes."""
48 |
49 | def __init__(self): pass
50 |
51 | class showCertificateViewer(ChromeCommand):
52 | """Displays native dialog with the certificate details."""
53 |
54 | def __init__(self): pass
55 |
56 |
--------------------------------------------------------------------------------
/chrome_control/HeapProfiler.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Runtime
7 |
8 | # Heap snapshot object id.
9 | HeapSnapshotObjectId = str
10 |
11 | class SamplingHeapProfileNode:
12 | """Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes."""
13 | def __init__(self, callFrame: "Runtime.CallFrame", selfSize: float, children: List):
14 | # Function location.
15 | self.callFrame = callFrame
16 | # Allocations size in bytes for the node excluding children.
17 | self.selfSize = selfSize
18 | # Child nodes.
19 | self.children = children
20 |
21 | class SamplingHeapProfile:
22 | """Profile."""
23 | def __init__(self, head: "SamplingHeapProfileNode"):
24 | self.head = head
25 |
26 | class enable(ChromeCommand):
27 | def __init__(self): pass
28 |
29 | class disable(ChromeCommand):
30 | def __init__(self): pass
31 |
32 | class startTrackingHeapObjects(ChromeCommand):
33 | def __init__(self, trackAllocations: bool=None):
34 | self.trackAllocations = trackAllocations
35 |
36 |
37 |
38 | class stopTrackingHeapObjects(ChromeCommand):
39 | def __init__(self, reportProgress: bool=None):
40 | # If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped.
41 | self.reportProgress = reportProgress
42 |
43 |
44 |
45 | class takeHeapSnapshot(ChromeCommand):
46 | def __init__(self, reportProgress: bool=None):
47 | # If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken.
48 | self.reportProgress = reportProgress
49 |
50 |
51 |
52 | class collectGarbage(ChromeCommand):
53 | def __init__(self): pass
54 |
55 | class getObjectByHeapObjectId(ChromeCommand):
56 | def __init__(self, objectId: "HeapSnapshotObjectId", objectGroup: str=None):
57 | self.objectId = objectId
58 | # Symbolic group name that can be used to release multiple objects.
59 | self.objectGroup = objectGroup
60 |
61 |
62 |
63 | class addInspectedHeapObject(ChromeCommand):
64 | """Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions)."""
65 |
66 | def __init__(self, heapObjectId: "HeapSnapshotObjectId"):
67 | # Heap snapshot object id to be accessible by means of $x command line API.
68 | self.heapObjectId = heapObjectId
69 |
70 |
71 |
72 | class getHeapObjectId(ChromeCommand):
73 | def __init__(self, objectId: "Runtime.RemoteObjectId"):
74 | # Identifier of the object to get heap object id for.
75 | self.objectId = objectId
76 |
77 |
78 |
79 | class startSampling(ChromeCommand):
80 | def __init__(self, samplingInterval: float=None):
81 | # Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes.
82 | self.samplingInterval = samplingInterval
83 |
84 |
85 |
86 | class stopSampling(ChromeCommand):
87 | def __init__(self): pass
88 |
89 |
--------------------------------------------------------------------------------
/chrome_control/ServiceWorker.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Target
7 |
8 | class ServiceWorkerRegistration:
9 | """ServiceWorker registration."""
10 | def __init__(self, registrationId: str, scopeURL: str, isDeleted: bool):
11 | self.registrationId = registrationId
12 | self.scopeURL = scopeURL
13 | self.isDeleted = isDeleted
14 |
15 | ServiceWorkerVersionRunningStatus = Enum("ServiceWorkerVersionRunningStatus", "stopped starting running stopping")
16 | ServiceWorkerVersionRunningStatus.__doc__ = """"""
17 |
18 | ServiceWorkerVersionStatus = Enum("ServiceWorkerVersionStatus", "new installing installed activating activated redundant")
19 | ServiceWorkerVersionStatus.__doc__ = """"""
20 |
21 | class ServiceWorkerVersion:
22 | """ServiceWorker version."""
23 | def __init__(self, versionId: str, registrationId: str, scriptURL: str, runningStatus: "ServiceWorkerVersionRunningStatus", status: "ServiceWorkerVersionStatus", scriptLastModified: float=None, scriptResponseTime: float=None, controlledClients: List=None, targetId: "Target.TargetID"=None):
24 | self.versionId = versionId
25 | self.registrationId = registrationId
26 | self.scriptURL = scriptURL
27 | self.runningStatus = runningStatus
28 | self.status = status
29 | # The Last-Modified header value of the main script.
30 | self.scriptLastModified = scriptLastModified
31 | # The time at which the response headers of the main script were received from the server. For cached script it is the last time the cache entry was validated.
32 | self.scriptResponseTime = scriptResponseTime
33 | self.controlledClients = controlledClients
34 | self.targetId = targetId
35 |
36 | class ServiceWorkerErrorMessage:
37 | """ServiceWorker error message."""
38 | def __init__(self, errorMessage: str, registrationId: str, versionId: str, sourceURL: str, lineNumber: int, columnNumber: int):
39 | self.errorMessage = errorMessage
40 | self.registrationId = registrationId
41 | self.versionId = versionId
42 | self.sourceURL = sourceURL
43 | self.lineNumber = lineNumber
44 | self.columnNumber = columnNumber
45 |
46 | class enable(ChromeCommand):
47 | def __init__(self): pass
48 |
49 | class disable(ChromeCommand):
50 | def __init__(self): pass
51 |
52 | class unregister(ChromeCommand):
53 | def __init__(self, scopeURL: str):
54 | self.scopeURL = scopeURL
55 |
56 |
57 |
58 | class updateRegistration(ChromeCommand):
59 | def __init__(self, scopeURL: str):
60 | self.scopeURL = scopeURL
61 |
62 |
63 |
64 | class startWorker(ChromeCommand):
65 | def __init__(self, scopeURL: str):
66 | self.scopeURL = scopeURL
67 |
68 |
69 |
70 | class skipWaiting(ChromeCommand):
71 | def __init__(self, scopeURL: str):
72 | self.scopeURL = scopeURL
73 |
74 |
75 |
76 | class stopWorker(ChromeCommand):
77 | def __init__(self, versionId: str):
78 | self.versionId = versionId
79 |
80 |
81 |
82 | class inspectWorker(ChromeCommand):
83 | def __init__(self, versionId: str):
84 | self.versionId = versionId
85 |
86 |
87 |
88 | class setForceUpdateOnPageLoad(ChromeCommand):
89 | def __init__(self, forceUpdateOnPageLoad: bool):
90 | self.forceUpdateOnPageLoad = forceUpdateOnPageLoad
91 |
92 |
93 |
94 | class deliverPushMessage(ChromeCommand):
95 | def __init__(self, origin: str, registrationId: str, data: str):
96 | self.origin = origin
97 | self.registrationId = registrationId
98 | self.data = data
99 |
100 |
101 |
102 | class dispatchSyncEvent(ChromeCommand):
103 | def __init__(self, origin: str, registrationId: str, tag: str, lastChance: bool):
104 | self.origin = origin
105 | self.registrationId = registrationId
106 | self.tag = tag
107 | self.lastChance = lastChance
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/chrome_control/DOMDebugger.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Runtime
7 |
8 | DOMBreakpointType = Enum("DOMBreakpointType", "subtree-modified attribute-modified node-removed")
9 | DOMBreakpointType.__doc__ = """DOM breakpoint type."""
10 |
11 | class EventListener:
12 | """Object event listener."""
13 | def __init__(self, type: str, useCapture: bool, passive: bool, once: bool, scriptId: "Runtime.ScriptId", lineNumber: int, columnNumber: int, handler: "Runtime.RemoteObject"=None, originalHandler: "Runtime.RemoteObject"=None, removeFunction: "Runtime.RemoteObject"=None):
14 | # EventListener's type.
15 | self.type = type
16 | # EventListener's useCapture.
17 | self.useCapture = useCapture
18 | # EventListener's passive flag.
19 | self.passive = passive
20 | # EventListener's once flag.
21 | self.once = once
22 | # Script id of the handler code.
23 | self.scriptId = scriptId
24 | # Line number in the script (0-based).
25 | self.lineNumber = lineNumber
26 | # Column number in the script (0-based).
27 | self.columnNumber = columnNumber
28 | # Event handler function value.
29 | self.handler = handler
30 | # Event original handler function value.
31 | self.originalHandler = originalHandler
32 | # Event listener remove function.
33 | self.removeFunction = removeFunction
34 |
35 | class setDOMBreakpoint(ChromeCommand):
36 | """Sets breakpoint on particular operation with DOM."""
37 |
38 | def __init__(self, nodeId: "DOM.NodeId", type: "DOMBreakpointType"):
39 | # Identifier of the node to set breakpoint on.
40 | self.nodeId = nodeId
41 | # Type of the operation to stop upon.
42 | self.type = type
43 |
44 |
45 |
46 | class removeDOMBreakpoint(ChromeCommand):
47 | """Removes DOM breakpoint that was set using setDOMBreakpoint."""
48 |
49 | def __init__(self, nodeId: "DOM.NodeId", type: "DOMBreakpointType"):
50 | # Identifier of the node to remove breakpoint from.
51 | self.nodeId = nodeId
52 | # Type of the breakpoint to remove.
53 | self.type = type
54 |
55 |
56 |
57 | class setEventListenerBreakpoint(ChromeCommand):
58 | """Sets breakpoint on particular DOM event."""
59 |
60 | def __init__(self, eventName: str, targetName: str=None):
61 | # DOM Event name to stop on (any DOM event will do).
62 | self.eventName = eventName
63 | # EventTarget interface name to stop on. If equal to "*" or not provided, will stop on any EventTarget.
64 | self.targetName = targetName
65 |
66 |
67 |
68 | class removeEventListenerBreakpoint(ChromeCommand):
69 | """Removes breakpoint on particular DOM event."""
70 |
71 | def __init__(self, eventName: str, targetName: str=None):
72 | # Event name.
73 | self.eventName = eventName
74 | # EventTarget interface name.
75 | self.targetName = targetName
76 |
77 |
78 |
79 | class setInstrumentationBreakpoint(ChromeCommand):
80 | """Sets breakpoint on particular native event."""
81 |
82 | def __init__(self, eventName: str):
83 | # Instrumentation name to stop on.
84 | self.eventName = eventName
85 |
86 |
87 |
88 | class removeInstrumentationBreakpoint(ChromeCommand):
89 | """Removes breakpoint on particular native event."""
90 |
91 | def __init__(self, eventName: str):
92 | # Instrumentation name to stop on.
93 | self.eventName = eventName
94 |
95 |
96 |
97 | class setXHRBreakpoint(ChromeCommand):
98 | """Sets breakpoint on XMLHttpRequest."""
99 |
100 | def __init__(self, url: str):
101 | # Resource URL substring. All XHRs having this substring in the URL will get stopped upon.
102 | self.url = url
103 |
104 |
105 |
106 | class removeXHRBreakpoint(ChromeCommand):
107 | """Removes breakpoint from XMLHttpRequest."""
108 |
109 | def __init__(self, url: str):
110 | # Resource URL substring.
111 | self.url = url
112 |
113 |
114 |
115 | class getEventListeners(ChromeCommand):
116 | """Returns event listeners of the given object."""
117 |
118 | def __init__(self, objectId: "Runtime.RemoteObjectId"):
119 | # Identifier of the object to return listeners for.
120 | self.objectId = objectId
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/chrome_control/Target.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 |
7 | TargetID = str
8 |
9 | BrowserContextID = str
10 |
11 | class TargetInfo:
12 | def __init__(self, targetId: "TargetID", type: str, title: str, url: str):
13 | self.targetId = targetId
14 | self.type = type
15 | self.title = title
16 | self.url = url
17 |
18 | class RemoteLocation:
19 | def __init__(self, host: str, port: int):
20 | self.host = host
21 | self.port = port
22 |
23 | class setDiscoverTargets(ChromeCommand):
24 | """Controls whether to discover available targets and notify via targetCreated/targetDestroyed events."""
25 |
26 | def __init__(self, discover: bool):
27 | # Whether to discover available targets.
28 | self.discover = discover
29 |
30 |
31 |
32 | class setAutoAttach(ChromeCommand):
33 | """Controls whether to automatically attach to new targets which are considered to be related to this one. When turned on, attaches to all existing related targets as well. When turned off, automatically detaches from all currently attached targets."""
34 |
35 | def __init__(self, autoAttach: bool, waitForDebuggerOnStart: bool):
36 | # Whether to auto-attach to related targets.
37 | self.autoAttach = autoAttach
38 | # Whether to pause new targets when attaching to them. Use Runtime.runIfWaitingForDebugger to run paused targets.
39 | self.waitForDebuggerOnStart = waitForDebuggerOnStart
40 |
41 |
42 |
43 | class setAttachToFrames(ChromeCommand):
44 | def __init__(self, value: bool):
45 | # Whether to attach to frames.
46 | self.value = value
47 |
48 |
49 |
50 | class setRemoteLocations(ChromeCommand):
51 | """Enables target discovery for the specified locations, when setDiscoverTargets was set to true."""
52 |
53 | def __init__(self, locations: List):
54 | # List of remote locations.
55 | self.locations = locations
56 |
57 |
58 |
59 | class sendMessageToTarget(ChromeCommand):
60 | """Sends protocol message to the target with given id."""
61 |
62 | def __init__(self, targetId: str, message: str):
63 | self.targetId = targetId
64 | self.message = message
65 |
66 |
67 |
68 | class getTargetInfo(ChromeCommand):
69 | """Returns information about a target."""
70 |
71 | def __init__(self, targetId: "TargetID"):
72 | self.targetId = targetId
73 |
74 |
75 |
76 | class activateTarget(ChromeCommand):
77 | """Activates (focuses) the target."""
78 |
79 | def __init__(self, targetId: "TargetID"):
80 | self.targetId = targetId
81 |
82 |
83 |
84 | class closeTarget(ChromeCommand):
85 | """Closes the target. If the target is a page that gets closed too."""
86 |
87 | def __init__(self, targetId: "TargetID"):
88 | self.targetId = targetId
89 |
90 |
91 |
92 | class attachToTarget(ChromeCommand):
93 | """Attaches to the target with given id."""
94 |
95 | def __init__(self, targetId: "TargetID"):
96 | self.targetId = targetId
97 |
98 |
99 |
100 | class detachFromTarget(ChromeCommand):
101 | """Detaches from the target with given id."""
102 |
103 | def __init__(self, targetId: "TargetID"):
104 | self.targetId = targetId
105 |
106 |
107 |
108 | class createBrowserContext(ChromeCommand):
109 | """Creates a new empty BrowserContext. Similar to an incognito profile but you can have more than one."""
110 |
111 | def __init__(self): pass
112 |
113 | class disposeBrowserContext(ChromeCommand):
114 | """Deletes a BrowserContext, will fail of any open page uses it."""
115 |
116 | def __init__(self, browserContextId: "BrowserContextID"):
117 | self.browserContextId = browserContextId
118 |
119 |
120 |
121 | class createTarget(ChromeCommand):
122 | """Creates a new page."""
123 |
124 | def __init__(self, url: str, width: int=None, height: int=None, browserContextId: "BrowserContextID"=None):
125 | # The initial URL the page will be navigated to.
126 | self.url = url
127 | # Frame width in DIP (headless chrome only).
128 | self.width = width
129 | # Frame height in DIP (headless chrome only).
130 | self.height = height
131 | # The browser context to create the page in (headless chrome only).
132 | self.browserContextId = browserContextId
133 |
134 |
135 |
136 | class getTargets(ChromeCommand):
137 | """Retrieves a list of available targets."""
138 |
139 | def __init__(self): pass
140 |
141 |
--------------------------------------------------------------------------------
/chrome_control/IndexedDB.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import Runtime
7 |
8 | class DatabaseWithObjectStores:
9 | """Database with an array of object stores."""
10 | def __init__(self, name: str, version: int, objectStores: List):
11 | # Database name.
12 | self.name = name
13 | # Database version.
14 | self.version = version
15 | # Object stores in this database.
16 | self.objectStores = objectStores
17 |
18 | class ObjectStore:
19 | """Object store."""
20 | def __init__(self, name: str, keyPath: "KeyPath", autoIncrement: bool, indexes: List):
21 | # Object store name.
22 | self.name = name
23 | # Object store key path.
24 | self.keyPath = keyPath
25 | # If true, object store has auto increment flag set.
26 | self.autoIncrement = autoIncrement
27 | # Indexes in this object store.
28 | self.indexes = indexes
29 |
30 | class ObjectStoreIndex:
31 | """Object store index."""
32 | def __init__(self, name: str, keyPath: "KeyPath", unique: bool, multiEntry: bool):
33 | # Index name.
34 | self.name = name
35 | # Index key path.
36 | self.keyPath = keyPath
37 | # If true, index is unique.
38 | self.unique = unique
39 | # If true, index allows multiple entries for a key.
40 | self.multiEntry = multiEntry
41 |
42 | class Key:
43 | """Key."""
44 | def __init__(self, type: str, number: float=None, string: str=None, date: float=None, array: List=None):
45 | # Key type.
46 | self.type = type
47 | # Number value.
48 | self.number = number
49 | # String value.
50 | self.string = string
51 | # Date value.
52 | self.date = date
53 | # Array value.
54 | self.array = array
55 |
56 | class KeyRange:
57 | """Key range."""
58 | def __init__(self, lowerOpen: bool, upperOpen: bool, lower: "Key"=None, upper: "Key"=None):
59 | # If true lower bound is open.
60 | self.lowerOpen = lowerOpen
61 | # If true upper bound is open.
62 | self.upperOpen = upperOpen
63 | # Lower bound.
64 | self.lower = lower
65 | # Upper bound.
66 | self.upper = upper
67 |
68 | class DataEntry:
69 | """Data entry."""
70 | def __init__(self, key: "Runtime.RemoteObject", primaryKey: "Runtime.RemoteObject", value: "Runtime.RemoteObject"):
71 | # Key object.
72 | self.key = key
73 | # Primary key object.
74 | self.primaryKey = primaryKey
75 | # Value object.
76 | self.value = value
77 |
78 | class KeyPath:
79 | """Key path."""
80 | def __init__(self, type: str, string: str=None, array: List=None):
81 | # Key path type.
82 | self.type = type
83 | # String value.
84 | self.string = string
85 | # Array value.
86 | self.array = array
87 |
88 | class enable(ChromeCommand):
89 | """Enables events from backend."""
90 |
91 | def __init__(self): pass
92 |
93 | class disable(ChromeCommand):
94 | """Disables events from backend."""
95 |
96 | def __init__(self): pass
97 |
98 | class requestDatabaseNames(ChromeCommand):
99 | """Requests database names for given security origin."""
100 |
101 | def __init__(self, securityOrigin: str):
102 | # Security origin.
103 | self.securityOrigin = securityOrigin
104 |
105 |
106 |
107 | class requestDatabase(ChromeCommand):
108 | """Requests database with given name in given frame."""
109 |
110 | def __init__(self, securityOrigin: str, databaseName: str):
111 | # Security origin.
112 | self.securityOrigin = securityOrigin
113 | # Database name.
114 | self.databaseName = databaseName
115 |
116 |
117 |
118 | class requestData(ChromeCommand):
119 | """Requests data from object store or index."""
120 |
121 | def __init__(self, securityOrigin: str, databaseName: str, objectStoreName: str, indexName: str, skipCount: int, pageSize: int, keyRange: "KeyRange"=None):
122 | # Security origin.
123 | self.securityOrigin = securityOrigin
124 | # Database name.
125 | self.databaseName = databaseName
126 | # Object store name.
127 | self.objectStoreName = objectStoreName
128 | # Index name, empty string for object store data requests.
129 | self.indexName = indexName
130 | # Number of records to skip.
131 | self.skipCount = skipCount
132 | # Number of records to fetch.
133 | self.pageSize = pageSize
134 | # Key range.
135 | self.keyRange = keyRange
136 |
137 |
138 |
139 | class clearObjectStore(ChromeCommand):
140 | """Clears all entries from an object store."""
141 |
142 | def __init__(self, securityOrigin: str, databaseName: str, objectStoreName: str):
143 | # Security origin.
144 | self.securityOrigin = securityOrigin
145 | # Database name.
146 | self.databaseName = databaseName
147 | # Object store name.
148 | self.objectStoreName = objectStoreName
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/chrome_control/Animation.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import DOM
7 |
8 | class Animation:
9 | """Animation instance."""
10 | def __init__(self, id: str, name: str, pausedState: bool, playState: str, playbackRate: float, startTime: float, currentTime: float, source: "AnimationEffect", type: str, cssId: str=None):
11 | # Animation's id.
12 | self.id = id
13 | # Animation's name.
14 | self.name = name
15 | # Animation's internal paused state.
16 | self.pausedState = pausedState
17 | # Animation's play state.
18 | self.playState = playState
19 | # Animation's playback rate.
20 | self.playbackRate = playbackRate
21 | # Animation's start time.
22 | self.startTime = startTime
23 | # Animation's current time.
24 | self.currentTime = currentTime
25 | # Animation's source animation node.
26 | self.source = source
27 | # Animation type of Animation.
28 | self.type = type
29 | # A unique ID for Animation representing the sources that triggered this CSS animation/transition.
30 | self.cssId = cssId
31 |
32 | class AnimationEffect:
33 | """AnimationEffect instance"""
34 | def __init__(self, delay: float, endDelay: float, iterationStart: float, iterations: float, duration: float, direction: str, fill: str, backendNodeId: "DOM.BackendNodeId", easing: str, keyframesRule: "KeyframesRule"=None):
35 | # AnimationEffect's delay.
36 | self.delay = delay
37 | # AnimationEffect's end delay.
38 | self.endDelay = endDelay
39 | # AnimationEffect's iteration start.
40 | self.iterationStart = iterationStart
41 | # AnimationEffect's iterations.
42 | self.iterations = iterations
43 | # AnimationEffect's iteration duration.
44 | self.duration = duration
45 | # AnimationEffect's playback direction.
46 | self.direction = direction
47 | # AnimationEffect's fill mode.
48 | self.fill = fill
49 | # AnimationEffect's target node.
50 | self.backendNodeId = backendNodeId
51 | # AnimationEffect's timing function.
52 | self.easing = easing
53 | # AnimationEffect's keyframes.
54 | self.keyframesRule = keyframesRule
55 |
56 | class KeyframesRule:
57 | """Keyframes Rule"""
58 | def __init__(self, keyframes: List, name: str=None):
59 | # List of animation keyframes.
60 | self.keyframes = keyframes
61 | # CSS keyframed animation's name.
62 | self.name = name
63 |
64 | class KeyframeStyle:
65 | """Keyframe Style"""
66 | def __init__(self, offset: str, easing: str):
67 | # Keyframe's time offset.
68 | self.offset = offset
69 | # AnimationEffect's timing function.
70 | self.easing = easing
71 |
72 | class enable(ChromeCommand):
73 | """Enables animation domain notifications."""
74 |
75 | def __init__(self): pass
76 |
77 | class disable(ChromeCommand):
78 | """Disables animation domain notifications."""
79 |
80 | def __init__(self): pass
81 |
82 | class getPlaybackRate(ChromeCommand):
83 | """Gets the playback rate of the document timeline."""
84 |
85 | def __init__(self): pass
86 |
87 | class setPlaybackRate(ChromeCommand):
88 | """Sets the playback rate of the document timeline."""
89 |
90 | def __init__(self, playbackRate: float):
91 | # Playback rate for animations on page
92 | self.playbackRate = playbackRate
93 |
94 |
95 |
96 | class getCurrentTime(ChromeCommand):
97 | """Returns the current time of the an animation."""
98 |
99 | def __init__(self, id: str):
100 | # Id of animation.
101 | self.id = id
102 |
103 |
104 |
105 | class setPaused(ChromeCommand):
106 | """Sets the paused state of a set of animations."""
107 |
108 | def __init__(self, animations: List, paused: bool):
109 | # Animations to set the pause state of.
110 | self.animations = animations
111 | # Paused state to set to.
112 | self.paused = paused
113 |
114 |
115 |
116 | class setTiming(ChromeCommand):
117 | """Sets the timing of an animation node."""
118 |
119 | def __init__(self, animationId: str, duration: float, delay: float):
120 | # Animation id.
121 | self.animationId = animationId
122 | # Duration of the animation.
123 | self.duration = duration
124 | # Delay of the animation.
125 | self.delay = delay
126 |
127 |
128 |
129 | class seekAnimations(ChromeCommand):
130 | """Seek a set of animations to a particular time within each animation."""
131 |
132 | def __init__(self, animations: List, currentTime: float):
133 | # List of animation ids to seek.
134 | self.animations = animations
135 | # Set the current time of each animation.
136 | self.currentTime = currentTime
137 |
138 |
139 |
140 | class releaseAnimations(ChromeCommand):
141 | """Releases a set of animations to no longer be manipulated."""
142 |
143 | def __init__(self, animations: List):
144 | # List of animation ids to seek.
145 | self.animations = animations
146 |
147 |
148 |
149 | class resolveAnimation(ChromeCommand):
150 | """Gets the remote object of the Animation."""
151 |
152 | def __init__(self, animationId: str):
153 | # Animation id.
154 | self.animationId = animationId
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/gen.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | # given a json schema enum, return an equivalent python enum
4 | def enum(type_):
5 | if type_["type"] == "string":
6 | return f'''{type_["id"]} = Enum("{type_["id"]}", "{" ".join(type_["enum"])}")
7 | {type_["id"]}.__doc__ = """{type_.get("description", "")}"""
8 |
9 | '''
10 | else:
11 | print(type_)
12 | 1/0
13 |
14 | def typemap(typ):
15 | return {
16 | "string": "str",
17 | "integer": "int",
18 | "number": "float",
19 | "array": "List",
20 | "boolean": "bool",
21 | "any": "Any",
22 | # if we get here, the spec is for a dict
23 | "object": "dict",
24 | }[typ]
25 |
26 | def handle_properties(properties):
27 | dependencies = []
28 | constructor_args = ["self"]
29 | args = []
30 |
31 | for p in properties:
32 | if "$ref" in p:
33 | ref = p["$ref"].split(".")
34 | if len(ref) > 1:
35 | dependencies.append(ref[0])
36 | ptype = f'"{p["$ref"]}"'
37 | else:
38 | # TODO: handle array sub-types here
39 | # TODO: handle enum sub-types above
40 | ptype = typemap(p["type"])
41 |
42 | if p.get("type") == "object" and "properties" in p:
43 | # allow objects to pass as long as the spec means
44 | # dict and not a recursive object
45 | print(p)
46 | 1/0
47 |
48 | if p.get("optional"):
49 | constructor_args.append(f'{p["name"]}: {ptype}=None')
50 | else:
51 | constructor_args.append(f'{p["name"]}: {ptype}')
52 |
53 | if "description" in p:
54 | args.append(f'# {p["description"]}')
55 |
56 | args.append(f'self.{p["name"]} = {p["name"]}')
57 |
58 | return dependencies, constructor_args, args
59 |
60 | def object_(type_):
61 | if "properties" not in type_:
62 | return ([], f'class {type_["id"]}: pass\n\n')
63 |
64 | props = sorted(type_["properties"], key=lambda x: x.get("optional", False))
65 | dependencies, constructor_args, args = handle_properties(props)
66 |
67 | docstring = f'\n """{type_["description"]}"""' if "description" in type_ else ""
68 |
69 | return (dependencies, f'''class {type_["id"]}:{docstring}
70 | def __init__({', '.join(constructor_args)}):
71 | ''' + '\n '.join(args) + "\n\n")
72 |
73 | def array(type_):
74 | # TODO: convert to ta tuple if there is minitems and maxitems?
75 | # ex: {'id': 'Quad', 'type': 'array', 'items': {'type': 'number'}, 'minItems': 8, 'maxItems': 8, 'description': 'An array of quad vertices, x immediately followed by y for each point, points clock-wise.', 'experimental': True}
76 | # should be a Tuple[float, float, float, float, float, float, float, float] ?
77 | # there's only this one case though, so I'm not going to stress it.
78 | out = []
79 | if "type" in type_["items"]:
80 | eltype = typemap(type_["items"]["type"])
81 | else:
82 | eltype = f'"{type_["items"]["$ref"]}"'
83 |
84 | if "description" in type_:
85 | out.append(f'# {type_["description"]}\n')
86 | if "description" in type_["items"]:
87 | out.append(f'# items: {type_["items"]["description"]}')
88 | out.append(f'{type_["id"]} = List[{eltype}]\n')
89 |
90 | return "".join(out)
91 |
92 | def simple(type_):
93 | out = []
94 | ttype = typemap(type_["type"])
95 |
96 | if "description" in type_:
97 | out.append(f'# {type_["description"]}\n')
98 | out.append(f'{type_["id"]} = {ttype}\n\n')
99 |
100 | return "".join(out)
101 |
102 | def command(cmd, domain):
103 | name = cmd["name"]
104 | docstr = f'\n """{cmd["description"]}"""\n' if "description" in cmd else ''
105 |
106 | props = sorted(cmd.get("parameters", []), key=lambda x: x.get("optional", False))
107 | dependencies, constructor_args, args = handle_properties(props)
108 |
109 | if args:
110 | argcode = '\n ' + '\n '.join(args) + "\n\n"
111 | else:
112 | argcode = " pass"
113 |
114 | return f'''class {name}(ChromeCommand):{docstr}
115 | def __init__({", ".join(constructor_args)}):{argcode}
116 |
117 | '''
118 |
119 | if __name__=="__main__":
120 | protocol = json.loads(open("protocol.json", ).read())
121 | for domain in protocol["domains"]:
122 | name = domain["domain"]
123 | types = []
124 | commands = []
125 | dependencies = set()
126 | for type_ in domain.get("types", []):
127 | if "enum" in type_: types.append(enum(type_))
128 | elif type_["type"] in ["string", "integer", "number"]:
129 | types.append(simple(type_))
130 | elif type_["type"] == "object":
131 | deps, typeobj = object_(type_)
132 | dependencies.update(deps)
133 | types.append(typeobj)
134 | # TODO array
135 | elif type_["type"] == "array": types.append(array(type_))
136 | else: 1/0
137 |
138 | for cmd in domain.get("commands", []):
139 | commands.append(command(cmd, domain["domain"]))
140 |
141 | mod = open(f"chrome_control/{name}.py", 'w')
142 | mod.write("""from enum import Enum
143 | from typing import Any, List
144 |
145 | from .base import ChromeCommand
146 |
147 | """)
148 |
149 | for dep in dependencies:
150 | mod.write(f'from . import {dep}\n')
151 |
152 | # TODO: objects have to be defined before being referenced fffuuuuu
153 | # so if class A has a parameter of type B, type B must appear
154 | # in the source file above type A
155 | mod.write('\n')
156 | mod.write(''.join(types))
157 |
158 | mod.write(''.join(commands))
159 |
--------------------------------------------------------------------------------
/chrome_control/LayerTree.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import DOM
7 |
8 | # Unique Layer identifier.
9 | LayerId = str
10 |
11 | # Unique snapshot identifier.
12 | SnapshotId = str
13 |
14 | class ScrollRect:
15 | """Rectangle where scrolling happens on the main thread."""
16 | def __init__(self, rect: "DOM.Rect", type: str):
17 | # Rectangle itself.
18 | self.rect = rect
19 | # Reason for rectangle to force scrolling on the main thread
20 | self.type = type
21 |
22 | class PictureTile:
23 | """Serialized fragment of layer picture along with its offset within the layer."""
24 | def __init__(self, x: float, y: float, picture: str):
25 | # Offset from owning layer left boundary
26 | self.x = x
27 | # Offset from owning layer top boundary
28 | self.y = y
29 | # Base64-encoded snapshot data.
30 | self.picture = picture
31 |
32 | class Layer:
33 | """Information about a compositing layer."""
34 | def __init__(self, layerId: "LayerId", offsetX: float, offsetY: float, width: float, height: float, paintCount: int, drawsContent: bool, parentLayerId: "LayerId"=None, backendNodeId: "DOM.BackendNodeId"=None, transform: List=None, anchorX: float=None, anchorY: float=None, anchorZ: float=None, invisible: bool=None, scrollRects: List=None):
35 | # The unique id for this layer.
36 | self.layerId = layerId
37 | # Offset from parent layer, X coordinate.
38 | self.offsetX = offsetX
39 | # Offset from parent layer, Y coordinate.
40 | self.offsetY = offsetY
41 | # Layer width.
42 | self.width = width
43 | # Layer height.
44 | self.height = height
45 | # Indicates how many time this layer has painted.
46 | self.paintCount = paintCount
47 | # Indicates whether this layer hosts any content, rather than being used for transform/scrolling purposes only.
48 | self.drawsContent = drawsContent
49 | # The id of parent (not present for root).
50 | self.parentLayerId = parentLayerId
51 | # The backend id for the node associated with this layer.
52 | self.backendNodeId = backendNodeId
53 | # Transformation matrix for layer, default is identity matrix
54 | self.transform = transform
55 | # Transform anchor point X, absent if no transform specified
56 | self.anchorX = anchorX
57 | # Transform anchor point Y, absent if no transform specified
58 | self.anchorY = anchorY
59 | # Transform anchor point Z, absent if no transform specified
60 | self.anchorZ = anchorZ
61 | # Set if layer is not visible.
62 | self.invisible = invisible
63 | # Rectangles scrolling on main thread only.
64 | self.scrollRects = scrollRects
65 |
66 | # Array of timings, one per paint step.
67 | # items: A time in seconds since the end of previous step (for the first step, time since painting started)PaintProfile = List[float]
68 | class enable(ChromeCommand):
69 | """Enables compositing tree inspection."""
70 |
71 | def __init__(self): pass
72 |
73 | class disable(ChromeCommand):
74 | """Disables compositing tree inspection."""
75 |
76 | def __init__(self): pass
77 |
78 | class compositingReasons(ChromeCommand):
79 | """Provides the reasons why the given layer was composited."""
80 |
81 | def __init__(self, layerId: "LayerId"):
82 | # The id of the layer for which we want to get the reasons it was composited.
83 | self.layerId = layerId
84 |
85 |
86 |
87 | class makeSnapshot(ChromeCommand):
88 | """Returns the layer snapshot identifier."""
89 |
90 | def __init__(self, layerId: "LayerId"):
91 | # The id of the layer.
92 | self.layerId = layerId
93 |
94 |
95 |
96 | class loadSnapshot(ChromeCommand):
97 | """Returns the snapshot identifier."""
98 |
99 | def __init__(self, tiles: List):
100 | # An array of tiles composing the snapshot.
101 | self.tiles = tiles
102 |
103 |
104 |
105 | class releaseSnapshot(ChromeCommand):
106 | """Releases layer snapshot captured by the back-end."""
107 |
108 | def __init__(self, snapshotId: "SnapshotId"):
109 | # The id of the layer snapshot.
110 | self.snapshotId = snapshotId
111 |
112 |
113 |
114 | class profileSnapshot(ChromeCommand):
115 | def __init__(self, snapshotId: "SnapshotId", minRepeatCount: int=None, minDuration: float=None, clipRect: "DOM.Rect"=None):
116 | # The id of the layer snapshot.
117 | self.snapshotId = snapshotId
118 | # The maximum number of times to replay the snapshot (1, if not specified).
119 | self.minRepeatCount = minRepeatCount
120 | # The minimum duration (in seconds) to replay the snapshot.
121 | self.minDuration = minDuration
122 | # The clip rectangle to apply when replaying the snapshot.
123 | self.clipRect = clipRect
124 |
125 |
126 |
127 | class replaySnapshot(ChromeCommand):
128 | """Replays the layer snapshot and returns the resulting bitmap."""
129 |
130 | def __init__(self, snapshotId: "SnapshotId", fromStep: int=None, toStep: int=None, scale: float=None):
131 | # The id of the layer snapshot.
132 | self.snapshotId = snapshotId
133 | # The first step to replay from (replay from the very start if not specified).
134 | self.fromStep = fromStep
135 | # The last step to replay to (replay till the end if not specified).
136 | self.toStep = toStep
137 | # The scale to apply while replaying (defaults to 1).
138 | self.scale = scale
139 |
140 |
141 |
142 | class snapshotCommandLog(ChromeCommand):
143 | """Replays the layer snapshot and returns canvas log."""
144 |
145 | def __init__(self, snapshotId: "SnapshotId"):
146 | # The id of the layer snapshot.
147 | self.snapshotId = snapshotId
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/chrome_control/Accessibility.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, List
3 |
4 | from .base import ChromeCommand
5 |
6 | from . import DOM
7 |
8 | # Unique accessibility node identifier.
9 | AXNodeId = str
10 |
11 | AXValueType = Enum("AXValueType", "boolean tristate booleanOrUndefined idref idrefList integer node nodeList number string computedString token tokenList domRelation role internalRole valueUndefined")
12 | AXValueType.__doc__ = """Enum of possible property types."""
13 |
14 | AXValueSourceType = Enum("AXValueSourceType", "attribute implicit style contents placeholder relatedElement")
15 | AXValueSourceType.__doc__ = """Enum of possible property sources."""
16 |
17 | AXValueNativeSourceType = Enum("AXValueNativeSourceType", "figcaption label labelfor labelwrapped legend tablecaption title other")
18 | AXValueNativeSourceType.__doc__ = """Enum of possible native property sources (as a subtype of a particular AXValueSourceType)."""
19 |
20 | class AXValueSource:
21 | """A single source for a computed AX property."""
22 | def __init__(self, type: "AXValueSourceType", value: "AXValue"=None, attribute: str=None, attributeValue: "AXValue"=None, superseded: bool=None, nativeSource: "AXValueNativeSourceType"=None, nativeSourceValue: "AXValue"=None, invalid: bool=None, invalidReason: str=None):
23 | # What type of source this is.
24 | self.type = type
25 | # The value of this property source.
26 | self.value = value
27 | # The name of the relevant attribute, if any.
28 | self.attribute = attribute
29 | # The value of the relevant attribute, if any.
30 | self.attributeValue = attributeValue
31 | # Whether this source is superseded by a higher priority source.
32 | self.superseded = superseded
33 | # The native markup source for this value, e.g. a