Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
"""Small client for the Marionette protocol shipped with current Firefox."""
|
|
|
|
import json
|
|
import socket
|
|
import time
|
|
|
|
|
|
class Client:
|
|
def __init__(self, port=2856):
|
|
deadline = time.monotonic() + 30
|
|
while True:
|
|
try:
|
|
self.socket = socket.create_connection(("127.0.0.1", port), timeout=20)
|
|
break
|
|
except ConnectionRefusedError:
|
|
if time.monotonic() >= deadline:
|
|
raise
|
|
time.sleep(0.2)
|
|
self.sequence = 0
|
|
self.quit_requested = False
|
|
self.receive()
|
|
self.call("WebDriver:NewSession", {"capabilities": {}})
|
|
self.call("Marionette:SetContext", {"value": "chrome"})
|
|
|
|
def receive(self):
|
|
length = bytearray()
|
|
while not length.endswith(b":"):
|
|
chunk = self.socket.recv(1)
|
|
if not chunk:
|
|
raise ConnectionError("Marionette disconnected")
|
|
length += chunk
|
|
remaining = int(length[:-1])
|
|
payload = bytearray()
|
|
while len(payload) < remaining:
|
|
chunk = self.socket.recv(remaining - len(payload))
|
|
if not chunk:
|
|
raise ConnectionError("Marionette disconnected")
|
|
payload += chunk
|
|
return json.loads(payload)
|
|
|
|
def call(self, command, arguments=None):
|
|
self.sequence += 1
|
|
payload = json.dumps([0, self.sequence, command, arguments or {}]).encode()
|
|
self.socket.sendall(str(len(payload)).encode() + b":" + payload)
|
|
response = self.receive()
|
|
if response[2]:
|
|
raise RuntimeError(response[2])
|
|
if command == "Marionette:Quit":
|
|
self.quit_requested = True
|
|
return response[3]
|
|
|
|
def execute(self, script):
|
|
result = self.call(
|
|
"WebDriver:ExecuteScript",
|
|
{"script": script, "args": [], "newSandbox": True, "sandbox": "system"},
|
|
)
|
|
return result.get("value", result)
|
|
|
|
def close(self):
|
|
try:
|
|
if not self.quit_requested:
|
|
self.call("WebDriver:DeleteSession")
|
|
finally:
|
|
self.socket.close()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self.close()
|