We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] exports do not work with Firefox webdriver
Firefox webdriver does not currently support logs: https://github.com/SeleniumHQ/selenium/issues/2972

It makes it impossible to export figures with Firefox as a webdriver.
Fixing this issue may allow to fix this one: https://github.com/bokeh/bokeh/issues/8176

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bokeh/io/export.py`
Content:
```
1 #-----------------------------------------------------------------------------
2 # Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors.
3 # All rights reserved.
4 #
5 # The full license is in the file LICENSE.txt, distributed with this software.
6 #-----------------------------------------------------------------------------
7 '''
8 
9 '''
10 
11 #-----------------------------------------------------------------------------
12 # Boilerplate
13 #-----------------------------------------------------------------------------
14 from __future__ import absolute_import, division, print_function, unicode_literals
15 
16 import logging
17 log = logging.getLogger(__name__)
18 
19 #-----------------------------------------------------------------------------
20 # Imports
21 #-----------------------------------------------------------------------------
22 
23 # Standard library imports
24 import os
25 import io
26 import warnings
27 from os.path import abspath
28 from tempfile import mkstemp
29 
30 # External imports
31 from six import raise_from, b
32 
33 # Bokeh imports
34 from ..embed import file_html
35 from ..resources import INLINE
36 from ..util.dependencies import import_required
37 from ..util.string import decode_utf8
38 from .util import default_filename
39 
40 #-----------------------------------------------------------------------------
41 # Globals and constants
42 #-----------------------------------------------------------------------------
43 
44 __all__ = (
45     'create_webdriver',
46     'export_png',
47     'export_svgs',
48     'get_layout_html',
49     'get_screenshot_as_png',
50     'get_svgs',
51     'terminate_webdriver',
52     'webdriver_control',
53 )
54 
55 #-----------------------------------------------------------------------------
56 # General API
57 #-----------------------------------------------------------------------------
58 
59 def export_png(obj, filename=None, height=None, width=None, webdriver=None):
60     ''' Export the ``LayoutDOM`` object or document as a PNG.
61 
62     If the filename is not given, it is derived from the script name (e.g.
63     ``/foo/myplot.py`` will create ``/foo/myplot.png``)
64 
65     Args:
66         obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget
67             object or Document to export.
68 
69         filename (str, optional) : filename to save document under (default: None)
70             If None, infer from the filename.
71 
72         height (int) : the desired height of the exported layout obj only if
73             it's a Plot instance. Otherwise the height kwarg is ignored.
74 
75         width (int) : the desired width of the exported layout obj only if
76             it's a Plot instance. Otherwise the width kwarg is ignored.
77 
78         webdriver (selenium.webdriver) : a selenium webdriver instance to use
79             to export the image.
80 
81     Returns:
82         filename (str) : the filename where the static file is saved.
83 
84     If you would like to access an Image object directly, rather than save a
85     file to disk, use the lower-level :func:`~bokeh.io.export.get_screenshot_as_png`
86     function.
87 
88     .. warning::
89         Responsive sizing_modes may generate layouts with unexpected size and
90         aspect ratios. It is recommended to use the default ``fixed`` sizing mode.
91 
92     '''
93 
94     image = get_screenshot_as_png(obj, height=height, width=width, driver=webdriver)
95 
96     if filename is None:
97         filename = default_filename("png")
98 
99     if image.width == 0 or image.height == 0:
100         raise ValueError("unable to save an empty image")
101 
102     image.save(filename)
103 
104     return abspath(filename)
105 
106 def export_svgs(obj, filename=None, height=None, width=None, webdriver=None):
107     ''' Export the SVG-enabled plots within a layout. Each plot will result
108     in a distinct SVG file.
109 
110     If the filename is not given, it is derived from the script name
111     (e.g. ``/foo/myplot.py`` will create ``/foo/myplot.svg``)
112 
113     Args:
114         obj (LayoutDOM object) : a Layout (Row/Column), Plot or Widget object to display
115 
116         filename (str, optional) : filename to save document under (default: None)
117             If None, infer from the filename.
118 
119         height (int) : the desired height of the exported layout obj only if
120             it's a Plot instance. Otherwise the height kwarg is ignored.
121 
122         width (int) : the desired width of the exported layout obj only if
123             it's a Plot instance. Otherwise the width kwarg is ignored.
124 
125         webdriver (selenium.webdriver) : a selenium webdriver instance to use
126             to export the image.
127 
128     Returns:
129         filenames (list(str)) : the list of filenames where the SVGs files are
130         saved.
131 
132     .. warning::
133         Responsive sizing_modes may generate layouts with unexpected size and
134         aspect ratios. It is recommended to use the default ``fixed`` sizing mode.
135 
136     '''
137     svgs = get_svgs(obj, height=height, width=width, driver=webdriver)
138 
139     if len(svgs) == 0:
140         log.warning("No SVG Plots were found.")
141         return
142 
143     if filename is None:
144         filename = default_filename("svg")
145 
146     filenames = []
147 
148     for i, svg in enumerate(svgs):
149         if i == 0:
150             filename = filename
151         else:
152             idx = filename.find(".svg")
153             filename = filename[:idx] + "_{}".format(i) + filename[idx:]
154 
155         with io.open(filename, mode="w", encoding="utf-8") as f:
156             f.write(svg)
157 
158         filenames.append(filename)
159 
160     return filenames
161 
162 #-----------------------------------------------------------------------------
163 # Dev API
164 #-----------------------------------------------------------------------------
165 
166 # this is part of the API for this module
167 from .webdriver import webdriver_control
168 from .webdriver import terminate_webdriver # for back compat
169 
170 def create_webdriver():
171     ''' Create a new webdriver.
172 
173     .. note ::
174         Here for compatibility. Prefer methods on the webdriver_control
175         object.
176 
177     '''
178     return webdriver_control.create()
179 
180 def get_screenshot_as_png(obj, driver=None, **kwargs):
181     ''' Get a screenshot of a ``LayoutDOM`` object.
182 
183     Args:
184         obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget
185             object or Document to export.
186 
187         driver (selenium.webdriver) : a selenium webdriver instance to use
188             to export the image.
189 
190     Returns:
191         cropped_image (PIL.Image.Image) : a pillow image loaded from PNG.
192 
193     .. warning::
194         Responsive sizing_modes may generate layouts with unexpected size and
195         aspect ratios. It is recommended to use the default ``fixed`` sizing mode.
196 
197     '''
198     Image = import_required('PIL.Image',
199                             'To use bokeh.io.export_png you need pillow ' +
200                             '("conda install pillow" or "pip install pillow")')
201 
202     with _tmp_html() as tmp:
203         html = get_layout_html(obj, **kwargs)
204         with io.open(tmp.path, mode="w", encoding="utf-8") as file:
205             file.write(decode_utf8(html))
206 
207         web_driver = driver if driver is not None else webdriver_control.get()
208 
209         web_driver.get("file:///" + tmp.path)
210         web_driver.maximize_window()
211 
212         ## resize for PhantomJS compat
213         web_driver.execute_script("document.body.style.width = '100%';")
214 
215         wait_until_render_complete(web_driver)
216 
217         png = web_driver.get_screenshot_as_png()
218 
219         b_rect = web_driver.execute_script(_BOUNDING_RECT_SCRIPT)
220 
221     image = Image.open(io.BytesIO(png))
222     cropped_image = _crop_image(image, **b_rect)
223 
224     return cropped_image
225 
226 def get_svgs(obj, driver=None, **kwargs):
227     '''
228 
229     '''
230     with _tmp_html() as tmp:
231         html = get_layout_html(obj, **kwargs)
232         with io.open(tmp.path, mode="wb") as file:
233             file.write(b(html))
234 
235         web_driver = driver if driver is not None else webdriver_control.get()
236 
237         web_driver.get("file:///" + tmp.path)
238 
239         wait_until_render_complete(web_driver)
240 
241         svgs = web_driver.execute_script(_SVG_SCRIPT)
242 
243     return svgs
244 
245 def get_layout_html(obj, resources=INLINE, **kwargs):
246     '''
247 
248     '''
249     resize = False
250     if kwargs.get('height') is not None or kwargs.get('width') is not None:
251         # Defer this import, it is expensive
252         from ..models.plots import Plot
253         if not isinstance(obj, Plot):
254             warnings.warn("Export method called with height or width kwargs on a non-Plot layout. The size values will be ignored.")
255         else:
256             resize = True
257             old_height = obj.plot_height
258             old_width = obj.plot_width
259             obj.plot_height = kwargs.get('height', old_height)
260             obj.plot_width = kwargs.get('width', old_width)
261 
262     try:
263         html = file_html(obj, resources, title="", suppress_callback_warning=True, _always_new=True)
264     finally:
265         if resize:
266             obj.plot_height = old_height
267             obj.plot_width = old_width
268 
269     return html
270 
271 def wait_until_render_complete(driver):
272     '''
273 
274     '''
275     from selenium.webdriver.support.ui import WebDriverWait
276     from selenium.common.exceptions import TimeoutException
277 
278     def is_bokeh_loaded(driver):
279         return driver.execute_script('''
280             const b = window.Bokeh;
281             return b && b.documents && b.documents.length > 0;
282         ''')
283 
284     try:
285         WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_loaded)
286     except TimeoutException as e:
287         raise_from(RuntimeError('Bokeh was not loaded in time. Something may have gone wrong.'), e)
288 
289     driver.execute_script(_WAIT_SCRIPT)
290 
291     def is_bokeh_render_complete(driver):
292         return driver.execute_script('return window._bokeh_render_complete;')
293 
294     try:
295         WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_render_complete)
296     except TimeoutException:
297         log.warning("The webdriver raised a TimeoutException while waiting for "
298                     "a 'bokeh:idle' event to signify that the layout has rendered. "
299                     "Something may have gone wrong.")
300     finally:
301         browser_logs = driver.get_log('browser')
302         messages = [ l.get("message") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]
303         if len(messages) > 0:
304             log.warning("There were browser warnings and/or errors that may have affected your export")
305             for message in messages:
306                 log.warning(message)
307 
308 #-----------------------------------------------------------------------------
309 # Private API
310 #-----------------------------------------------------------------------------
311 
312 _BOUNDING_RECT_SCRIPT = """
313 return document.getElementsByClassName('bk-root')[0].children[0].getBoundingClientRect()
314 """
315 
316 _SVG_SCRIPT = """
317 var serialized_svgs = [];
318 var svgs = document.getElementsByClassName('bk-root')[0].getElementsByTagName("svg");
319 for (var i = 0; i < svgs.length; i++) {
320     var source = (new XMLSerializer()).serializeToString(svgs[i]);
321     serialized_svgs.push(source);
322 };
323 return serialized_svgs
324 """
325 
326 _WAIT_SCRIPT = """
327 // add private window prop to check that render is complete
328 window._bokeh_render_complete = false;
329 function done() {
330   window._bokeh_render_complete = true;
331 }
332 
333 var doc = window.Bokeh.documents[0];
334 
335 if (doc.is_idle)
336   done();
337 else
338   doc.idle.connect(done);
339 """
340 
341 def _crop_image(image, left=0, top=0, right=0, bottom=0, **kwargs):
342     ''' Crop the border from the layout
343 
344     '''
345     return image.crop((left, top, right, bottom))
346 
347 class _TempFile(object):
348 
349     _closed = False
350 
351     def __init__(self, prefix="tmp", suffix=""):
352         self.fd, self.path = mkstemp(prefix=prefix, suffix=suffix)
353 
354     def __enter__(self):
355         return self
356 
357     def __exit__(self, exc, value, tb):
358         self.close()
359 
360     def __del__(self):
361         self.close()
362 
363     def close(self):
364         if self._closed:
365             return
366 
367         try:
368             os.close(self.fd)
369         except (OSError, IOError):
370             pass
371         finally:
372             self.fd = None
373 
374         try:
375             os.unlink(self.path)
376         except (OSError, IOError):
377             pass
378         finally:
379             self.path = None
380 
381         self._closed = True
382 
383 def _tmp_html():
384     return _TempFile(prefix="bokeh", suffix=".html")
385 
386 #-----------------------------------------------------------------------------
387 # Code
388 #-----------------------------------------------------------------------------
389
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bokeh/io/export.py b/bokeh/io/export.py
--- a/bokeh/io/export.py
+++ b/bokeh/io/export.py
@@ -274,6 +274,7 @@
     '''
     from selenium.webdriver.support.ui import WebDriverWait
     from selenium.common.exceptions import TimeoutException
+    from selenium.webdriver import Firefox
 
     def is_bokeh_loaded(driver):
         return driver.execute_script('''
@@ -298,12 +299,14 @@
                     "a 'bokeh:idle' event to signify that the layout has rendered. "
                     "Something may have gone wrong.")
     finally:
-        browser_logs = driver.get_log('browser')
-        messages = [ l.get("message") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]
-        if len(messages) > 0:
-            log.warning("There were browser warnings and/or errors that may have affected your export")
-            for message in messages:
-                log.warning(message)
+        # Firefox webdriver does not currently support logs
+        if not isinstance(driver, Firefox):
+            browser_logs = driver.get_log('browser')
+            messages = [ l.get("message") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]
+            if len(messages) > 0:
+                log.warning("There were browser warnings and/or errors that may have affected your export")
+                for message in messages:
+                    log.warning(message)
 
 #-----------------------------------------------------------------------------
 # Private API

{"golden_diff": "diff --git a/bokeh/io/export.py b/bokeh/io/export.py\n--- a/bokeh/io/export.py\n+++ b/bokeh/io/export.py\n@@ -274,6 +274,7 @@\n     '''\n     from selenium.webdriver.support.ui import WebDriverWait\n     from selenium.common.exceptions import TimeoutException\n+    from selenium.webdriver import Firefox\n \n     def is_bokeh_loaded(driver):\n         return driver.execute_script('''\n@@ -298,12 +299,14 @@\n                     \"a 'bokeh:idle' event to signify that the layout has rendered. \"\n                     \"Something may have gone wrong.\")\n     finally:\n-        browser_logs = driver.get_log('browser')\n-        messages = [ l.get(\"message\") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]\n-        if len(messages) > 0:\n-            log.warning(\"There were browser warnings and/or errors that may have affected your export\")\n-            for message in messages:\n-                log.warning(message)\n+        # Firefox webdriver does not currently support logs\n+        if not isinstance(driver, Firefox):\n+            browser_logs = driver.get_log('browser')\n+            messages = [ l.get(\"message\") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]\n+            if len(messages) > 0:\n+                log.warning(\"There were browser warnings and/or errors that may have affected your export\")\n+                for message in messages:\n+                    log.warning(message)\n \n #-----------------------------------------------------------------------------\n # Private API\n", "issue": "[BUG] exports do not work with Firefox webdriver\nFirefox webdriver does not currently support logs: https://github.com/SeleniumHQ/selenium/issues/2972\r\n\r\nIt makes it impossible to export figures with Firefox as a webdriver.\r\nFixing this issue may allow to fix this one: https://github.com/bokeh/bokeh/issues/8176\n", "before_files": [{"content": "#-----------------------------------------------------------------------------\n# Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors.\n# All rights reserved.\n#\n# The full license is in the file LICENSE.txt, distributed with this software.\n#-----------------------------------------------------------------------------\n'''\n\n'''\n\n#-----------------------------------------------------------------------------\n# Boilerplate\n#-----------------------------------------------------------------------------\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nlog = logging.getLogger(__name__)\n\n#-----------------------------------------------------------------------------\n# Imports\n#-----------------------------------------------------------------------------\n\n# Standard library imports\nimport os\nimport io\nimport warnings\nfrom os.path import abspath\nfrom tempfile import mkstemp\n\n# External imports\nfrom six import raise_from, b\n\n# Bokeh imports\nfrom ..embed import file_html\nfrom ..resources import INLINE\nfrom ..util.dependencies import import_required\nfrom ..util.string import decode_utf8\nfrom .util import default_filename\n\n#-----------------------------------------------------------------------------\n# Globals and constants\n#-----------------------------------------------------------------------------\n\n__all__ = (\n    'create_webdriver',\n    'export_png',\n    'export_svgs',\n    'get_layout_html',\n    'get_screenshot_as_png',\n    'get_svgs',\n    'terminate_webdriver',\n    'webdriver_control',\n)\n\n#-----------------------------------------------------------------------------\n# General API\n#-----------------------------------------------------------------------------\n\ndef export_png(obj, filename=None, height=None, width=None, webdriver=None):\n    ''' Export the ``LayoutDOM`` object or document as a PNG.\n\n    If the filename is not given, it is derived from the script name (e.g.\n    ``/foo/myplot.py`` will create ``/foo/myplot.png``)\n\n    Args:\n        obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget\n            object or Document to export.\n\n        filename (str, optional) : filename to save document under (default: None)\n            If None, infer from the filename.\n\n        height (int) : the desired height of the exported layout obj only if\n            it's a Plot instance. Otherwise the height kwarg is ignored.\n\n        width (int) : the desired width of the exported layout obj only if\n            it's a Plot instance. Otherwise the width kwarg is ignored.\n\n        webdriver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        filename (str) : the filename where the static file is saved.\n\n    If you would like to access an Image object directly, rather than save a\n    file to disk, use the lower-level :func:`~bokeh.io.export.get_screenshot_as_png`\n    function.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n\n    image = get_screenshot_as_png(obj, height=height, width=width, driver=webdriver)\n\n    if filename is None:\n        filename = default_filename(\"png\")\n\n    if image.width == 0 or image.height == 0:\n        raise ValueError(\"unable to save an empty image\")\n\n    image.save(filename)\n\n    return abspath(filename)\n\ndef export_svgs(obj, filename=None, height=None, width=None, webdriver=None):\n    ''' Export the SVG-enabled plots within a layout. Each plot will result\n    in a distinct SVG file.\n\n    If the filename is not given, it is derived from the script name\n    (e.g. ``/foo/myplot.py`` will create ``/foo/myplot.svg``)\n\n    Args:\n        obj (LayoutDOM object) : a Layout (Row/Column), Plot or Widget object to display\n\n        filename (str, optional) : filename to save document under (default: None)\n            If None, infer from the filename.\n\n        height (int) : the desired height of the exported layout obj only if\n            it's a Plot instance. Otherwise the height kwarg is ignored.\n\n        width (int) : the desired width of the exported layout obj only if\n            it's a Plot instance. Otherwise the width kwarg is ignored.\n\n        webdriver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        filenames (list(str)) : the list of filenames where the SVGs files are\n        saved.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n    svgs = get_svgs(obj, height=height, width=width, driver=webdriver)\n\n    if len(svgs) == 0:\n        log.warning(\"No SVG Plots were found.\")\n        return\n\n    if filename is None:\n        filename = default_filename(\"svg\")\n\n    filenames = []\n\n    for i, svg in enumerate(svgs):\n        if i == 0:\n            filename = filename\n        else:\n            idx = filename.find(\".svg\")\n            filename = filename[:idx] + \"_{}\".format(i) + filename[idx:]\n\n        with io.open(filename, mode=\"w\", encoding=\"utf-8\") as f:\n            f.write(svg)\n\n        filenames.append(filename)\n\n    return filenames\n\n#-----------------------------------------------------------------------------\n# Dev API\n#-----------------------------------------------------------------------------\n\n# this is part of the API for this module\nfrom .webdriver import webdriver_control\nfrom .webdriver import terminate_webdriver # for back compat\n\ndef create_webdriver():\n    ''' Create a new webdriver.\n\n    .. note ::\n        Here for compatibility. Prefer methods on the webdriver_control\n        object.\n\n    '''\n    return webdriver_control.create()\n\ndef get_screenshot_as_png(obj, driver=None, **kwargs):\n    ''' Get a screenshot of a ``LayoutDOM`` object.\n\n    Args:\n        obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget\n            object or Document to export.\n\n        driver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        cropped_image (PIL.Image.Image) : a pillow image loaded from PNG.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n    Image = import_required('PIL.Image',\n                            'To use bokeh.io.export_png you need pillow ' +\n                            '(\"conda install pillow\" or \"pip install pillow\")')\n\n    with _tmp_html() as tmp:\n        html = get_layout_html(obj, **kwargs)\n        with io.open(tmp.path, mode=\"w\", encoding=\"utf-8\") as file:\n            file.write(decode_utf8(html))\n\n        web_driver = driver if driver is not None else webdriver_control.get()\n\n        web_driver.get(\"file:///\" + tmp.path)\n        web_driver.maximize_window()\n\n        ## resize for PhantomJS compat\n        web_driver.execute_script(\"document.body.style.width = '100%';\")\n\n        wait_until_render_complete(web_driver)\n\n        png = web_driver.get_screenshot_as_png()\n\n        b_rect = web_driver.execute_script(_BOUNDING_RECT_SCRIPT)\n\n    image = Image.open(io.BytesIO(png))\n    cropped_image = _crop_image(image, **b_rect)\n\n    return cropped_image\n\ndef get_svgs(obj, driver=None, **kwargs):\n    '''\n\n    '''\n    with _tmp_html() as tmp:\n        html = get_layout_html(obj, **kwargs)\n        with io.open(tmp.path, mode=\"wb\") as file:\n            file.write(b(html))\n\n        web_driver = driver if driver is not None else webdriver_control.get()\n\n        web_driver.get(\"file:///\" + tmp.path)\n\n        wait_until_render_complete(web_driver)\n\n        svgs = web_driver.execute_script(_SVG_SCRIPT)\n\n    return svgs\n\ndef get_layout_html(obj, resources=INLINE, **kwargs):\n    '''\n\n    '''\n    resize = False\n    if kwargs.get('height') is not None or kwargs.get('width') is not None:\n        # Defer this import, it is expensive\n        from ..models.plots import Plot\n        if not isinstance(obj, Plot):\n            warnings.warn(\"Export method called with height or width kwargs on a non-Plot layout. The size values will be ignored.\")\n        else:\n            resize = True\n            old_height = obj.plot_height\n            old_width = obj.plot_width\n            obj.plot_height = kwargs.get('height', old_height)\n            obj.plot_width = kwargs.get('width', old_width)\n\n    try:\n        html = file_html(obj, resources, title=\"\", suppress_callback_warning=True, _always_new=True)\n    finally:\n        if resize:\n            obj.plot_height = old_height\n            obj.plot_width = old_width\n\n    return html\n\ndef wait_until_render_complete(driver):\n    '''\n\n    '''\n    from selenium.webdriver.support.ui import WebDriverWait\n    from selenium.common.exceptions import TimeoutException\n\n    def is_bokeh_loaded(driver):\n        return driver.execute_script('''\n            const b = window.Bokeh;\n            return b && b.documents && b.documents.length > 0;\n        ''')\n\n    try:\n        WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_loaded)\n    except TimeoutException as e:\n        raise_from(RuntimeError('Bokeh was not loaded in time. Something may have gone wrong.'), e)\n\n    driver.execute_script(_WAIT_SCRIPT)\n\n    def is_bokeh_render_complete(driver):\n        return driver.execute_script('return window._bokeh_render_complete;')\n\n    try:\n        WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_render_complete)\n    except TimeoutException:\n        log.warning(\"The webdriver raised a TimeoutException while waiting for \"\n                    \"a 'bokeh:idle' event to signify that the layout has rendered. \"\n                    \"Something may have gone wrong.\")\n    finally:\n        browser_logs = driver.get_log('browser')\n        messages = [ l.get(\"message\") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]\n        if len(messages) > 0:\n            log.warning(\"There were browser warnings and/or errors that may have affected your export\")\n            for message in messages:\n                log.warning(message)\n\n#-----------------------------------------------------------------------------\n# Private API\n#-----------------------------------------------------------------------------\n\n_BOUNDING_RECT_SCRIPT = \"\"\"\nreturn document.getElementsByClassName('bk-root')[0].children[0].getBoundingClientRect()\n\"\"\"\n\n_SVG_SCRIPT = \"\"\"\nvar serialized_svgs = [];\nvar svgs = document.getElementsByClassName('bk-root')[0].getElementsByTagName(\"svg\");\nfor (var i = 0; i < svgs.length; i++) {\n    var source = (new XMLSerializer()).serializeToString(svgs[i]);\n    serialized_svgs.push(source);\n};\nreturn serialized_svgs\n\"\"\"\n\n_WAIT_SCRIPT = \"\"\"\n// add private window prop to check that render is complete\nwindow._bokeh_render_complete = false;\nfunction done() {\n  window._bokeh_render_complete = true;\n}\n\nvar doc = window.Bokeh.documents[0];\n\nif (doc.is_idle)\n  done();\nelse\n  doc.idle.connect(done);\n\"\"\"\n\ndef _crop_image(image, left=0, top=0, right=0, bottom=0, **kwargs):\n    ''' Crop the border from the layout\n\n    '''\n    return image.crop((left, top, right, bottom))\n\nclass _TempFile(object):\n\n    _closed = False\n\n    def __init__(self, prefix=\"tmp\", suffix=\"\"):\n        self.fd, self.path = mkstemp(prefix=prefix, suffix=suffix)\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc, value, tb):\n        self.close()\n\n    def __del__(self):\n        self.close()\n\n    def close(self):\n        if self._closed:\n            return\n\n        try:\n            os.close(self.fd)\n        except (OSError, IOError):\n            pass\n        finally:\n            self.fd = None\n\n        try:\n            os.unlink(self.path)\n        except (OSError, IOError):\n            pass\n        finally:\n            self.path = None\n\n        self._closed = True\n\ndef _tmp_html():\n    return _TempFile(prefix=\"bokeh\", suffix=\".html\")\n\n#-----------------------------------------------------------------------------\n# Code\n#-----------------------------------------------------------------------------\n", "path": "bokeh/io/export.py"}], "after_files": [{"content": "#-----------------------------------------------------------------------------\n# Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors.\n# All rights reserved.\n#\n# The full license is in the file LICENSE.txt, distributed with this software.\n#-----------------------------------------------------------------------------\n'''\n\n'''\n\n#-----------------------------------------------------------------------------\n# Boilerplate\n#-----------------------------------------------------------------------------\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nlog = logging.getLogger(__name__)\n\n#-----------------------------------------------------------------------------\n# Imports\n#-----------------------------------------------------------------------------\n\n# Standard library imports\nimport os\nimport io\nimport warnings\nfrom os.path import abspath\nfrom tempfile import mkstemp\n\n# External imports\nfrom six import raise_from, b\n\n# Bokeh imports\nfrom ..embed import file_html\nfrom ..resources import INLINE\nfrom ..util.dependencies import import_required\nfrom ..util.string import decode_utf8\nfrom .util import default_filename\n\n#-----------------------------------------------------------------------------\n# Globals and constants\n#-----------------------------------------------------------------------------\n\n__all__ = (\n    'create_webdriver',\n    'export_png',\n    'export_svgs',\n    'get_layout_html',\n    'get_screenshot_as_png',\n    'get_svgs',\n    'terminate_webdriver',\n    'webdriver_control',\n)\n\n#-----------------------------------------------------------------------------\n# General API\n#-----------------------------------------------------------------------------\n\ndef export_png(obj, filename=None, height=None, width=None, webdriver=None):\n    ''' Export the ``LayoutDOM`` object or document as a PNG.\n\n    If the filename is not given, it is derived from the script name (e.g.\n    ``/foo/myplot.py`` will create ``/foo/myplot.png``)\n\n    Args:\n        obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget\n            object or Document to export.\n\n        filename (str, optional) : filename to save document under (default: None)\n            If None, infer from the filename.\n\n        height (int) : the desired height of the exported layout obj only if\n            it's a Plot instance. Otherwise the height kwarg is ignored.\n\n        width (int) : the desired width of the exported layout obj only if\n            it's a Plot instance. Otherwise the width kwarg is ignored.\n\n        webdriver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        filename (str) : the filename where the static file is saved.\n\n    If you would like to access an Image object directly, rather than save a\n    file to disk, use the lower-level :func:`~bokeh.io.export.get_screenshot_as_png`\n    function.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n\n    image = get_screenshot_as_png(obj, height=height, width=width, driver=webdriver)\n\n    if filename is None:\n        filename = default_filename(\"png\")\n\n    if image.width == 0 or image.height == 0:\n        raise ValueError(\"unable to save an empty image\")\n\n    image.save(filename)\n\n    return abspath(filename)\n\ndef export_svgs(obj, filename=None, height=None, width=None, webdriver=None):\n    ''' Export the SVG-enabled plots within a layout. Each plot will result\n    in a distinct SVG file.\n\n    If the filename is not given, it is derived from the script name\n    (e.g. ``/foo/myplot.py`` will create ``/foo/myplot.svg``)\n\n    Args:\n        obj (LayoutDOM object) : a Layout (Row/Column), Plot or Widget object to display\n\n        filename (str, optional) : filename to save document under (default: None)\n            If None, infer from the filename.\n\n        height (int) : the desired height of the exported layout obj only if\n            it's a Plot instance. Otherwise the height kwarg is ignored.\n\n        width (int) : the desired width of the exported layout obj only if\n            it's a Plot instance. Otherwise the width kwarg is ignored.\n\n        webdriver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        filenames (list(str)) : the list of filenames where the SVGs files are\n        saved.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n    svgs = get_svgs(obj, height=height, width=width, driver=webdriver)\n\n    if len(svgs) == 0:\n        log.warning(\"No SVG Plots were found.\")\n        return\n\n    if filename is None:\n        filename = default_filename(\"svg\")\n\n    filenames = []\n\n    for i, svg in enumerate(svgs):\n        if i == 0:\n            filename = filename\n        else:\n            idx = filename.find(\".svg\")\n            filename = filename[:idx] + \"_{}\".format(i) + filename[idx:]\n\n        with io.open(filename, mode=\"w\", encoding=\"utf-8\") as f:\n            f.write(svg)\n\n        filenames.append(filename)\n\n    return filenames\n\n#-----------------------------------------------------------------------------\n# Dev API\n#-----------------------------------------------------------------------------\n\n# this is part of the API for this module\nfrom .webdriver import webdriver_control\nfrom .webdriver import terminate_webdriver # for back compat\n\ndef create_webdriver():\n    ''' Create a new webdriver.\n\n    .. note ::\n        Here for compatibility. Prefer methods on the webdriver_control\n        object.\n\n    '''\n    return webdriver_control.create()\n\ndef get_screenshot_as_png(obj, driver=None, **kwargs):\n    ''' Get a screenshot of a ``LayoutDOM`` object.\n\n    Args:\n        obj (LayoutDOM or Document) : a Layout (Row/Column), Plot or Widget\n            object or Document to export.\n\n        driver (selenium.webdriver) : a selenium webdriver instance to use\n            to export the image.\n\n    Returns:\n        cropped_image (PIL.Image.Image) : a pillow image loaded from PNG.\n\n    .. warning::\n        Responsive sizing_modes may generate layouts with unexpected size and\n        aspect ratios. It is recommended to use the default ``fixed`` sizing mode.\n\n    '''\n    Image = import_required('PIL.Image',\n                            'To use bokeh.io.export_png you need pillow ' +\n                            '(\"conda install pillow\" or \"pip install pillow\")')\n\n    with _tmp_html() as tmp:\n        html = get_layout_html(obj, **kwargs)\n        with io.open(tmp.path, mode=\"w\", encoding=\"utf-8\") as file:\n            file.write(decode_utf8(html))\n\n        web_driver = driver if driver is not None else webdriver_control.get()\n\n        web_driver.get(\"file:///\" + tmp.path)\n        web_driver.maximize_window()\n\n        ## resize for PhantomJS compat\n        web_driver.execute_script(\"document.body.style.width = '100%';\")\n\n        wait_until_render_complete(web_driver)\n\n        png = web_driver.get_screenshot_as_png()\n\n        b_rect = web_driver.execute_script(_BOUNDING_RECT_SCRIPT)\n\n    image = Image.open(io.BytesIO(png))\n    cropped_image = _crop_image(image, **b_rect)\n\n    return cropped_image\n\ndef get_svgs(obj, driver=None, **kwargs):\n    '''\n\n    '''\n    with _tmp_html() as tmp:\n        html = get_layout_html(obj, **kwargs)\n        with io.open(tmp.path, mode=\"wb\") as file:\n            file.write(b(html))\n\n        web_driver = driver if driver is not None else webdriver_control.get()\n\n        web_driver.get(\"file:///\" + tmp.path)\n\n        wait_until_render_complete(web_driver)\n\n        svgs = web_driver.execute_script(_SVG_SCRIPT)\n\n    return svgs\n\ndef get_layout_html(obj, resources=INLINE, **kwargs):\n    '''\n\n    '''\n    resize = False\n    if kwargs.get('height') is not None or kwargs.get('width') is not None:\n        # Defer this import, it is expensive\n        from ..models.plots import Plot\n        if not isinstance(obj, Plot):\n            warnings.warn(\"Export method called with height or width kwargs on a non-Plot layout. The size values will be ignored.\")\n        else:\n            resize = True\n            old_height = obj.plot_height\n            old_width = obj.plot_width\n            obj.plot_height = kwargs.get('height', old_height)\n            obj.plot_width = kwargs.get('width', old_width)\n\n    try:\n        html = file_html(obj, resources, title=\"\", suppress_callback_warning=True, _always_new=True)\n    finally:\n        if resize:\n            obj.plot_height = old_height\n            obj.plot_width = old_width\n\n    return html\n\ndef wait_until_render_complete(driver):\n    '''\n\n    '''\n    from selenium.webdriver.support.ui import WebDriverWait\n    from selenium.common.exceptions import TimeoutException\n    from selenium.webdriver import Firefox\n\n    def is_bokeh_loaded(driver):\n        return driver.execute_script('''\n            const b = window.Bokeh;\n            return b && b.documents && b.documents.length > 0;\n        ''')\n\n    try:\n        WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_loaded)\n    except TimeoutException as e:\n        raise_from(RuntimeError('Bokeh was not loaded in time. Something may have gone wrong.'), e)\n\n    driver.execute_script(_WAIT_SCRIPT)\n\n    def is_bokeh_render_complete(driver):\n        return driver.execute_script('return window._bokeh_render_complete;')\n\n    try:\n        WebDriverWait(driver, 5, poll_frequency=0.1).until(is_bokeh_render_complete)\n    except TimeoutException:\n        log.warning(\"The webdriver raised a TimeoutException while waiting for \"\n                    \"a 'bokeh:idle' event to signify that the layout has rendered. \"\n                    \"Something may have gone wrong.\")\n    finally:\n        # Firefox webdriver does not currently support logs\n        if not isinstance(driver, Firefox):\n            browser_logs = driver.get_log('browser')\n            messages = [ l.get(\"message\") for l in browser_logs if l.get('level') in ['WARNING', 'ERROR', 'SEVERE'] ]\n            if len(messages) > 0:\n                log.warning(\"There were browser warnings and/or errors that may have affected your export\")\n                for message in messages:\n                    log.warning(message)\n\n#-----------------------------------------------------------------------------\n# Private API\n#-----------------------------------------------------------------------------\n\n_BOUNDING_RECT_SCRIPT = \"\"\"\nreturn document.getElementsByClassName('bk-root')[0].children[0].getBoundingClientRect()\n\"\"\"\n\n_SVG_SCRIPT = \"\"\"\nvar serialized_svgs = [];\nvar svgs = document.getElementsByClassName('bk-root')[0].getElementsByTagName(\"svg\");\nfor (var i = 0; i < svgs.length; i++) {\n    var source = (new XMLSerializer()).serializeToString(svgs[i]);\n    serialized_svgs.push(source);\n};\nreturn serialized_svgs\n\"\"\"\n\n_WAIT_SCRIPT = \"\"\"\n// add private window prop to check that render is complete\nwindow._bokeh_render_complete = false;\nfunction done() {\n  window._bokeh_render_complete = true;\n}\n\nvar doc = window.Bokeh.documents[0];\n\nif (doc.is_idle)\n  done();\nelse\n  doc.idle.connect(done);\n\"\"\"\n\ndef _crop_image(image, left=0, top=0, right=0, bottom=0, **kwargs):\n    ''' Crop the border from the layout\n\n    '''\n    return image.crop((left, top, right, bottom))\n\nclass _TempFile(object):\n\n    _closed = False\n\n    def __init__(self, prefix=\"tmp\", suffix=\"\"):\n        self.fd, self.path = mkstemp(prefix=prefix, suffix=suffix)\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc, value, tb):\n        self.close()\n\n    def __del__(self):\n        self.close()\n\n    def close(self):\n        if self._closed:\n            return\n\n        try:\n            os.close(self.fd)\n        except (OSError, IOError):\n            pass\n        finally:\n            self.fd = None\n\n        try:\n            os.unlink(self.path)\n        except (OSError, IOError):\n            pass\n        finally:\n            self.path = None\n\n        self._closed = True\n\ndef _tmp_html():\n    return _TempFile(prefix=\"bokeh\", suffix=\".html\")\n\n#-----------------------------------------------------------------------------\n# Code\n#-----------------------------------------------------------------------------\n", "path": "bokeh/io/export.py"}]}