source: src/mlx/gui/cef.py@ 713:64f54381f834

cef
Last change on this file since 713:64f54381f834 was 713:64f54381f834, checked in by István Váradi <ivaradi@…>, 9 years ago

Added support for a fast timeout handler to speed up operations driver via Selenium (re #279).

File size: 14.7 KB
RevLine 
[648]1from common import *
2
[685]3from mava_simbrief import MavaSimbriefIntegrator
4
[682]5from mlx.util import secondaryInstallation
6
7from cefpython3 import cefpython
8from selenium import webdriver
9from selenium.webdriver.chrome.options import Options
10
[648]11import platform
12import json
[682]13import time
[648]14import os
15import re
[682]16import threading
17import tempfile
18import traceback
[648]19
20#------------------------------------------------------------------------------
21
22## @package mlx.gui.cef
23#
24# Some helper stuff related to the Chrome Embedded Framework
25
26#------------------------------------------------------------------------------
27
28# Indicate if we should quit
29_toQuit = False
30
[713]31# Indicate the users of the fast timeout handler. If it reaches zero, the
32# timeout will be stopped
33_fastTimeoutUsers = 0
34
[682]35# The Selenium thread
36_seleniumHandler = None
37
38#------------------------------------------------------------------------------
39
40def getArgsFilePath():
41 """Get the path of the argument file."""
42 if os.name=="nt":
43 return os.path.join(tempfile.gettempdir(),
44 "mlxcef.args" +
45 (".secondary" if secondaryInstallation else ""))
46 else:
47 import pwd
48 return os.path.join(tempfile.gettempdir(),
49 "mlxcef." + pwd.getpwuid(os.getuid())[0] + ".args" +
50 (".secondary" if secondaryInstallation else ""))
51
52#------------------------------------------------------------------------------
53
54class ArgsFileWaiter(threading.Thread):
55 """A thread to wait for the appearance of the arguments file."""
56 def __init__(self, initializedCallback):
57 """Construct the thread."""
58 threading.Thread.__init__(self)
59 self.daemon = True
60
61 self._initializedCallback = initializedCallback
62
63 def run(self):
64 """Repeatedly check for the existence of the arguments file.
65
66 If it is found, read it, extract the arguments and insert a job into
67 the GUI loop to perform the actual initialization of CEF."""
68 argsFilePath = getArgsFilePath()
69 print "Waiting for the arguments file '%s' to appear" % (argsFilePath,)
70
71 while not os.path.exists(argsFilePath):
72 time.sleep(0.1)
73
74 print "Got arguments, reading them."""
75
76 with open(argsFilePath, "rt") as f:
77 args = f.read().split()
78
79 gobject.idle_add(_initializeCEF, args, self._initializedCallback)
80
[648]81#------------------------------------------------------------------------------
82
[692]83SIMBRIEF_PROGRESS_SEARCHING_BROWSER = MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER
84SIMBRIEF_PROGRESS_LOADING_FORM = MavaSimbriefIntegrator.PROGRESS_LOADING_FORM
85SIMBRIEF_PROGRESS_FILLING_FORM = MavaSimbriefIntegrator.PROGRESS_FILLING_FORM
86SIMBRIEF_PROGRESS_WAITING_LOGIN = MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN
87SIMBRIEF_PROGRESS_LOGGING_IN = MavaSimbriefIntegrator.PROGRESS_LOGGING_IN
88SIMBRIEF_PROGRESS_WAITING_RESULT = MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT
89
90SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = MavaSimbriefIntegrator.PROGRESS_MAX + 1
91SIMBRIEF_PROGRESS_DONE = 1000
92
93SIMBRIEF_RESULT_NONE = MavaSimbriefIntegrator.RESULT_NONE
94SIMBRIEF_RESULT_OK = MavaSimbriefIntegrator.RESULT_OK
95SIMBRIEF_RESULT_ERROR_OTHER = MavaSimbriefIntegrator.RESULT_ERROR_OTHER
96SIMBRIEF_RESULT_ERROR_NO_FORM = MavaSimbriefIntegrator.RESULT_ERROR_NO_FORM
97SIMBRIEF_RESULT_ERROR_NO_POPUP = MavaSimbriefIntegrator.RESULT_ERROR_NO_POPUP
98SIMBRIEF_RESULT_ERROR_LOGIN_FAILED = MavaSimbriefIntegrator.RESULT_ERROR_LOGIN_FAILED
99
100#------------------------------------------------------------------------------
101
[682]102class SeleniumHandler(threading.Thread):
103 """Thread to handle Selenium operations."""
104 def __init__(self, programDirectory):
105 """Construct the thread."""
106 threading.Thread.__init__(self)
107 self.daemon = False
108
109 self._programDirectory = programDirectory
110
111 self._commandsCondition = threading.Condition()
112 self._commands = []
113
[685]114 self._driver = None
115
[695]116 self._simBriefBrowser = None
117
[682]118 self._toQuit = False
119
[685]120 @property
121 def programDirectory(self):
122 """Get the program directory."""
123 return self._programDirectory
124
[695]125 @property
126 def simBriefInitURL(self):
127 """Get the initial URL for the SimBrief browser."""
128 return "file://" + os.path.join(self.programDirectory, "simbrief.html")
129
[682]130 def run(self):
131 """Create the Selenium driver and the perform any operations
132 requested."""
133 scriptName = "mlx_cef_caller"
134 if secondaryInstallation:
135 scriptName += "_secondary"
136 scriptName += ".bat" if os.name=="nt" else ".sh"
137
138 scriptPath = os.path.join(self._programDirectory, scriptName)
139 print "Creating the Selenium driver to call script", scriptPath
140
141 options = Options()
142 options.binary_location = scriptPath
[685]143 driver = self._driver = webdriver.Chrome(chrome_options = options)
[682]144 # try:
145 # except:
146 # traceback.print_exc()
147
148 print "Created Selenium driver."
149 while not self._toQuit:
150 with self._commandsCondition:
151 while not self._commands:
152 self._commandsCondition.wait()
153
154 command = self._commands[0]
155 del self._commands[0]
156
157 command()
158
159 driver.quit()
160
[695]161 def initializeSimBrief(self):
162 """Create and initialize the browser used for Simbrief."""
163 windowInfo = cefpython.WindowInfo()
164 windowInfo.SetAsOffscreen(int(0))
165
166 url = self.simBriefInitURL
167 self._simBriefBrowser = \
168 cefpython.CreateBrowserSync(windowInfo, browserSettings = {},
169 navigateUrl = self.simBriefInitURL)
170 self._simBriefBrowser.SetClientHandler(OffscreenRenderHandler())
171 self._simBriefBrowser.SetFocus(True)
172
[685]173 def callSimBrief(self, plan, getCredentials, updateProgress,
174 htmlFilePath):
175 """Call SimBrief with the given plan."""
176 self._enqueue(lambda:
177 self._callSimBrief(plan, self._driver,
178 getCredentials, updateProgress,
179 htmlFilePath))
180
[682]181 def quit(self):
182 """Instruct the thread to quit and then join it."""
183 self._enqueue(self._quit)
184 self.join()
185
186 def _enqueue(self, command):
187 """Enqueue the given command.
188
189 command should be a function to be executed in the thread."""
190 with self._commandsCondition:
191 self._commands.append(command)
192 self._commandsCondition.notify()
193
[685]194 def _callSimBrief(self, plan, driver,
195 getCredentials, updateProgress, htmlFilePath):
196 """Perform the SimBrief call."""
[696]197 self._simBriefBrowser.LoadUrl(self.simBriefInitURL)
198
[685]199 integrator = MavaSimbriefIntegrator(plan = plan, driver = driver)
[703]200 link = None
201 try:
202 link = integrator.get_xml_link(getCredentials, updateProgress,
203 local_xml_debug = False,
204 local_html_debug = False)
205 except Exception, e:
206 print "Failed to initiate the generation of the briefing:", e
207 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
208 SIMBRIEF_RESULT_ERROR_OTHER, None)
[685]209
[692]210 if link is not None:
211 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
212 SIMBRIEF_RESULT_NONE, None)
[685]213
[692]214 try:
215 flight_info = integrator.get_results(link,
216 html_file_path =
217 htmlFilePath)
[685]218
[692]219 updateProgress(SIMBRIEF_PROGRESS_DONE,
220 SIMBRIEF_RESULT_OK, flight_info)
221 except Exception, e:
[697]222 print "Failed to retrieve the briefing:", e
[692]223 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
224 SIMBRIEF_RESULT_ERROR_OTHER, None)
[685]225
[682]226 def _quit(self):
227 """Set the _toQuit member variable to indicate that the thread should
228 quit."""
229 self._toQuit = True
230
231#------------------------------------------------------------------------------
232
233def initialize(programDirectory, initializedCallback):
[648]234 """Initialize the Chrome Embedded Framework."""
[682]235 global _toQuit, _seleniumHandler
[648]236 _toQuit = False
237
238 gobject.threads_init()
239
[682]240 argsFilePath = getArgsFilePath()
241 try:
242 os.unlink(argsFilePath)
243 except:
244 pass
245
246 _seleniumHandler = SeleniumHandler(programDirectory)
247 _seleniumHandler.start()
248
249 ArgsFileWaiter(initializedCallback).start()
250
251#------------------------------------------------------------------------------
252
253def _initializeCEF(args, initializedCallback):
254 """Perform the actual initialization of CEF using the given arguments."""
255 print "Initializing CEF with args:", args
256
[648]257 settings = {
258 "debug": True, # cefpython debug messages in console and in log_file
259 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
260 "log_file": "", # Set to "" to disable
261 "release_dcheck_enabled": True, # Enable only when debugging
262 # This directories must be set on Linux
263 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
264 "resources_dir_path": cefpython.GetModuleDirectory(),
265 "browser_subprocess_path": "%s/%s" % \
266 (cefpython.GetModuleDirectory(), "subprocess"),
267 }
268
[682]269 switches={}
270 for arg in args:
271 if arg.startswith("--"):
272 if arg != "--enable-logging":
273 assignIndex = arg.find("=")
274 if assignIndex<0:
275 switches[arg[2:]] = ""
276 else:
277 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
278 else:
279 print "Unhandled switch", arg
280
281 cefpython.Initialize(settings, switches)
[648]282
283 gobject.timeout_add(10, _handleTimeout)
284
[682]285 print "Initialized, executing callback..."
286 initializedCallback()
287
[648]288#------------------------------------------------------------------------------
289
290def getContainer():
291 """Get a container object suitable for running a browser instance
292 within."""
293 if os.name=="nt":
294 container = gtk.DrawingArea()
295 container.set_property("can-focus", True)
296 container.connect("size-allocate", _handleSizeAllocate)
297 else:
298 container = gtk.VBox(True, 0)
299
300 container.show()
301
302 return container
303
304#------------------------------------------------------------------------------
305
306def startInContainer(container, url, browserSettings = {}):
307 """Start a browser instance in the given container with the given URL."""
308 if os.name=="nt":
309 windowID = container.get_window().handle
310 else:
311 m = re.search("GtkVBox at 0x(\w+)", str(container))
312 hexID = m.group(1)
313 windowID = int(hexID, 16)
314
315 windowInfo = cefpython.WindowInfo()
316 windowInfo.SetAsChild(windowID)
317
318 return cefpython.CreateBrowserSync(windowInfo,
319 browserSettings = browserSettings,
320 navigateUrl = url)
321
322#------------------------------------------------------------------------------
323
[685]324class OffscreenRenderHandler(object):
325 def GetRootScreenRect(self, browser, rect):
326 #print "GetRootScreenRect"
327 rect += [0, 0, 800, 600]
328 return True
329
330 def GetViewRect(self, browser, rect):
331 #print "GetViewRect"
332 rect += [0, 0, 800, 600]
333 return True
334
335 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
336 #print "GetScreenPoint", viewX, viewY
337 rect += [viewX, viewY]
338 return True
339
340 def GetScreenInfo(self, browser, screenInfo):
341 #print "GetScreenInfo"
342 pass
343
344 def OnPopupShow(self, browser, show):
345 #print "OnPopupShow", show
346 pass
347
348 def OnPopupSize(self, browser, rect):
349 #print "OnPopupSize", rect
350 pass
351
352 def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
353 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
354 pass
355
356 def OnCursorChange(self, browser, cursor):
357 #print "OnCursorChange", cursor
358 pass
359
360 def OnScrollOffsetChanged(self, browser):
361 #print "OnScrollOffsetChange"
362 pass
363
364 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
365 popupFeatures, windowInfo, client, browserSettings,
366 noJavascriptAccess):
367 wInfo = cefpython.WindowInfo()
368 wInfo.SetAsOffscreen(int(0))
369
370 windowInfo.append(wInfo)
371
372 return False
373
374#------------------------------------------------------------------------------
375
376def initializeSimBrief():
377 """Initialize the (hidden) browser window for SimBrief."""
[695]378 _seleniumHandler.initializeSimBrief()
[685]379
380#------------------------------------------------------------------------------
381
382def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
383 """Call SimBrief with the given plan."""
384 _seleniumHandler.callSimBrief(plan, getCredentials,
385 updateProgress, htmlFilePath)
386
387#------------------------------------------------------------------------------
388
[648]389def finalize():
390 """Finalize the Chrome Embedded Framework."""
[682]391 global _toQuit, _seleniumHandler
[648]392 toQuit = True
[702]393 cefpython.Shutdown()
[682]394 _seleniumHandler.quit()
[648]395
396#------------------------------------------------------------------------------
397
398def _handleTimeout():
399 """Handle the timeout by running the CEF message loop."""
400 if _toQuit:
401 return False
402 else:
403 cefpython.MessageLoopWork()
404 return True
405
406#------------------------------------------------------------------------------
407
[713]408def startFastTimeout():
409 """Start the fast timeout handler."""
410 global _fastTimeoutUsers
411
412 if _fastTimeoutUsers==0:
413 _fastTimeoutUsers = 1
414 gobject.timeout_add(1, _handleFastTimeout)
415 else:
416 _fastTimeoutUsers += 1
417
418#------------------------------------------------------------------------------
419
420def stopFastTimeout():
421 """Stop the fast timeout handler."""
422 global _fastTimeoutUsers
423
424 assert _fastTimeoutUsers>0
425 _fastTimeoutUsers -= 1
426
427#------------------------------------------------------------------------------
428
429def _handleFastTimeout():
430 """Handle a (fast) timeout by running the CEF message loop."""
431 if _toQuit or _fastTimeoutUsers==0:
432 return False
433 else:
434 cefpython.MessageLoopWork()
435 return True
436
437#------------------------------------------------------------------------------
438
[648]439def _handleSizeAllocate(widget, sizeAlloc):
440 """Handle the size-allocate event."""
441 cefpython.WindowUtils.OnSize(widget.get_window().handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.