source: src/mlx/gui/cef.py@ 804:afbbd0132506

cef
Last change on this file since 804:afbbd0132506 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
Line 
1from common import *
2
3from mava_simbrief import MavaSimbriefIntegrator
4
5from mlx.util import secondaryInstallation
6
7from cefpython3 import cefpython
8from selenium import webdriver
9from selenium.webdriver.chrome.options import Options
10
11import platform
12import json
13import time
14import os
15import re
16import threading
17import tempfile
18import traceback
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
31# Indicate the users of the fast timeout handler. If it reaches zero, the
32# timeout will be stopped
33_fastTimeoutUsers = 0
34
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
81#------------------------------------------------------------------------------
82
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
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
114 self._driver = None
115
116 self._simBriefBrowser = None
117
118 self._toQuit = False
119
120 @property
121 def programDirectory(self):
122 """Get the program directory."""
123 return self._programDirectory
124
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
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
143 driver = self._driver = webdriver.Chrome(chrome_options = options)
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
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
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
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
194 def _callSimBrief(self, plan, driver,
195 getCredentials, updateProgress, htmlFilePath):
196 """Perform the SimBrief call."""
197 self._simBriefBrowser.LoadUrl(self.simBriefInitURL)
198
199 integrator = MavaSimbriefIntegrator(plan = plan, driver = driver)
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)
209
210 if link is not None:
211 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
212 SIMBRIEF_RESULT_NONE, None)
213
214 try:
215 flight_info = integrator.get_results(link,
216 html_file_path =
217 htmlFilePath)
218
219 updateProgress(SIMBRIEF_PROGRESS_DONE,
220 SIMBRIEF_RESULT_OK, flight_info)
221 except Exception, e:
222 print "Failed to retrieve the briefing:", e
223 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
224 SIMBRIEF_RESULT_ERROR_OTHER, None)
225
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):
234 """Initialize the Chrome Embedded Framework."""
235 global _toQuit, _seleniumHandler
236 _toQuit = False
237
238 gobject.threads_init()
239
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
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
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)
282
283 gobject.timeout_add(10, _handleTimeout)
284
285 print "Initialized, executing callback..."
286 initializedCallback()
287
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
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."""
378 _seleniumHandler.initializeSimBrief()
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
389def finalize():
390 """Finalize the Chrome Embedded Framework."""
391 global _toQuit, _seleniumHandler
392 toQuit = True
393 cefpython.Shutdown()
394 _seleniumHandler.quit()
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
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
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.