https://agateau.com/tags/webkit/feedPosts tagged webkit2012-02-03T21:00:00+01:00Aurélien Gâteaupython-feedgenhttps://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experimentsSwimming against the stream or preparing for next stream change? PyQt+WebKit experiments2012-01-20T21:00:00+01:00<h1>Introduction</h1>
<p>(This is part 1 of the <a href="https://agateau.com/article-series/pyqtwebkit-experiments/">PyQt+WebKit experiments series</a>)</p>
<p>It is an interesting time to be a Qt developer: new ways to develop applications are emerging, one can either ignore them or experiment... I like experimentations.</p>
<p>In one corner we have traditional, QWidget-based Qt development.</p>
<p>In another corner we have QML, a declarative language coupled with JavaScript, which brings a whole new way to think about application development. Qt developers are betting a lot on QML, planning for it to be the preferred way to develop new applications with Qt 5.</p>
<p>In yet another corner is the Web, a perfect demonstration of technology hijacking: what was once designed to present structured documents has become one of the most powerful way to create applications.</p>
<p>Of those three technologies, I believe QML is the most innovative and powerful one, and I had high hopes it could secure a significant market share when Nokia and Intel were still planning to deliver it on their upcoming smart-phones and tablets. Now that Nokia switched to WP7 and Intel switched to EFL, I am a lot less confident QML will succeed. Experience shows that when two technologies are in competition, the best one does not automatically wins, parameters like market share and learning curve are as important as, if not more important than, technical excellence.</p>
<p>Not willing to ignore the behemoth that is the Web, I started an experiment: how would it feel to develop an application which would mix Qt and HTML rendering? Thanks to QtWebKit, this kind of integration is easy to achieve. My experimentation subject is based on <a href="http://yokadi.github.io">Yokadi</a>, a command-line based TODO list I work on. I decided to create a graphical frontend for it. The result is this:</p>
<p>
<img src="https://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/qyok1.png" title="QYok due page" height="471" width="630" alt="" class="aligncenter size-full wp-image-1053"/>
<img src="https://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/qyok2.png" title="QYok projects page" height="471" width="630" alt="" class="aligncenter size-full wp-image-1054"/>
</p>
<p>This application, named <a href="http://github.com/agateau/qyok">QYok</a> (yes, I suck at naming applications, suggestions for better names are welcome!), is an interesting mix of technologies. It uses:</p>
<ul>
<li><a href="http://www.riverbankcomputing.com/software/pyqt/">PyQt</a>, for the main window and most of the widgets</li>
<li><a href="http://trac.webkit.org/wiki/QtWebKit">QtWebKit</a>, to display the task lists</li>
<li><a href="http://jquery.com">jQuery</a>, to make it easier to manipulate the HTML and provide nice animations</li>
<li><a href="http://jinja.pocoo.org/">Jinja2</a>, a Python-based template system, using a syntax similar to Django (and thus Grantlee)</li>
<li>and of course, <a href="http://yokadi.github.io">Yokadi</a> itself, to provide access to the TODO database</li>
</ul>
<p>In this series of articles, I am going to describe how one can mix PyQt and WebKit together, based on my learnings from the QYok project. In particular I want to show ways to generate native-looking HTML code, to ensure your application does not look alien on your desktop.</p>
<h1>Getting a Qt application to show HTML code</h1>
<p>This is the first, very easy, step. Here is a complete example:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtCore</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtGui</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtWebKit</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Window</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="n">view</span> <span class="o">=</span> <span class="n">QWebView</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="n">html</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> <html><body></span>
<span class="s"> Hello World!</span>
<span class="s"> </body></html></span>
<span class="s"> """</span>
<span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="n">window</span> <span class="o">=</span> <span class="n">Window</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">()</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>
</p><p>(<a href="https://github.com/agateau/pyqt-webkit-tutorial/blob/master/tut1/hello-world.py">Source code</a>)</p>
<p>What all this does is create a window, create a webview in it and set some HTML. Nothing fancy.</p>
<img src="https://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/hello-world.png" title="hello-world" height="223" width="343" alt="" class="aligncenter size-full wp-image-1051"/>
<h1>HTML is nicer with images</h1>
<p>One of the main point of using HTML is that it makes it reasonably easy to create complex documents which include images. So let's add a folder named "static" to our code folder, with an image in it. Since we are feeding QWebView with HTML, it has no way to know where to look for our images. Luckily, setHtml() accepts a second parameter: the base url of the document. Any relative url contained in our HTML will be resolved using this url as a base.</p>
<p>Here is the modified call to setHtml():</p>
<div class="highlight"><pre><span class="n">pyDir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">))</span>
<span class="n">baseUrl</span> <span class="o">=</span> <span class="n">QUrl</span><span class="o">.</span><span class="n">fromLocalFile</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">pyDir</span><span class="p">,</span> <span class="s">"static/"</span><span class="p">))</span>
<span class="n">html</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> <html><body></span>
<span class="s"> <div>Hello World!</div></span>
<span class="s"> <img src="test.png"/></span>
<span class="s"> </body></html></span>
<span class="s"> """</span>
<span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">,</span> <span class="n">baseUrl</span><span class="p">)</span>
</pre></div>
<p>
</p><p>(<a href="https://github.com/agateau/pyqt-webkit-tutorial/blob/master/tut1/hello-world-img.py">Source code</a>)</p>
<img src="https://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/hello-world-img.png" title="hello-world-img" height="308" width="338" alt="" class="aligncenter size-full wp-image-1052"/>
<p>Be careful: always ensure baseUrl ends up with a trailing slash! If there is no trailing slash, QWebView will resolve "test.png" as "/path/to/tut1/statictest.png" instead of "/path/to/tut1/static/test.png". I learnt that the hard way...</p>
<h1>Calling Qt code from JavaScript</h1>
<p>QWebView.setHtml() makes it easy for Qt code to provide HTML content to WebKit, but it is a one-way only setup: it doesn't provide a way for JavaScript code to call Qt code. The way to do this is by exposing objects created on the Qt side to QWebView (or more precisely, to the main frame of the QWebPage inside QWebView).</p>
<p>QtWebKit takes advantage of Qt introspection features to access object methods and properties. To be exposed to JavaScript, an object must thus inherit from QObject and expose itself through Qt properties, Qt signals and Qt slots. Here is an example. First lets define a class named Foo which implements a compute() slot, performing complicated computations and a quit() slot:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="n">QObject</span><span class="p">):</span>
<span class="nd">@pyqtSlot</span><span class="p">(</span><span class="nb">int</span><span class="p">,</span> <span class="n">result</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">compute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="k">return</span> <span class="n">value</span> <span class="o">*</span> <span class="mi">2</span>
<span class="nd">@pyqtSlot</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">quit</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">QApplication</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
</pre></div>
<p>
</p><p>The important part here are the "@pyqtSlot()" decorators, which take care of turning our methods into slots. As you may have guessed, the decorator expects you to describe the type of the parameters accepted by your method, as well as the type of the returned value, if any.</p>
<p>Now that this done, lets create a Window class which will instantiate our Foo class and pass it to JavaScript:</p>
<div class="highlight"><pre><span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Window</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="n">view</span> <span class="o">=</span> <span class="n">QWebView</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">foo</span> <span class="o">=</span> <span class="n">Foo</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">view</span><span class="o">.</span><span class="n">page</span><span class="p">()</span><span class="o">.</span><span class="n">mainFrame</span><span class="p">()</span><span class="o">.</span><span class="n">addToJavaScriptWindowObject</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">foo</span><span class="p">)</span>
<span class="n">html</span> <span class="o">=</span> <span class="s">"""</span>
<span class="s"> <html></span>
<span class="s"> <head></span>
<span class="s"> <script></span>
<span class="s"> function updateEntry() {</span>
<span class="s"> var element = document.getElementById("entry");</span>
<span class="s"> var result = foo.compute(element.value);</span>
<span class="s"> element.value = result;</span>
<span class="s"> }</span>
<span class="s"> </script></span>
<span class="s"> </head></span>
<span class="s"> <body></span>
<span class="s"> <div></span>
<span class="s"> <input type="text" id="entry" value="1"/></span>
<span class="s"> <input type="button" value="Compute" onclick="updateEntry()"/></span>
<span class="s"> </div></span>
<span class="s"> <div></span>
<span class="s"> <input type="button" value="Quit" onclick="foo.quit()"/></span>
<span class="s"> </div></span>
<span class="s"> </body></span>
<span class="s"> </html></span>
<span class="s"> """</span>
<span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
</pre></div>
<p>
</p><p>(<a href="https://github.com/agateau/pyqt-webkit-tutorial/blob/master/tut1/expose-qtobject.py">Source code</a>)</p>
<img src="https://agateau.com/2012/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/expose-qtobject.png" title="expose-qtobject" height="276" width="440" alt="" class="aligncenter size-full wp-image-1050"/>
<p>Clicking the "Compute" button doubles the value in the input widget, clicking the "Quit" button, quits the application.</p>
<p>In this example, we create self.foo, an instance of the Foo class, then expose it to our QWebView with the line:</p>
<div class="highlight"><pre><span class="n">view</span><span class="o">.</span><span class="n">page</span><span class="p">()</span><span class="o">.</span><span class="n">mainFrame</span><span class="p">()</span><span class="o">.</span><span class="n">addToJavaScriptWindowObject</span><span class="p">(</span><span class="s">"foo"</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">foo</span><span class="p">)</span>
</pre></div>
<p>
</p><p>We then feed our QWebView with some HTML code with view.setHtml(). Note how JavaScript code can now refer to our foo object as if it was a native JavaScript object.</p>
<h1>Conclusion</h1>
<p>That's it for now. Stay tuned for the next article. </p>
<p>PS: All examples are available from <a href="http://github.com/agateau/pyqt-webkit-tutorial">github</a>: <code>git clone git://github.com/agateau/pyqt-webkit-tutorial.git</code> to get it.</p>
2012-01-20T21:00:00+01:00https://agateau.com/2012/pyqtwebkit-experiments-part-2-debuggingPyQt+WebKit experiments part 2: debugging2012-02-03T21:00:00+01:00<p>(This is part 2 of the <a href="https://agateau.com/article-series/pyqtwebkit-experiments/">PyQt+WebKit experiments series</a>)</p>
<p>In <a href="https://agateau.com/2012/01/20/swimming-against-the-stream-or-preparing-for-next-stream-change-pyqtwebkit-experiments/">Part 1</a> I described how to embed WebKit in a PyQt application and how to expose PyQt objects in WebKit and manipulate them with JavaScript.</p>
<p>Even if you are a great JavaScript master, you can't avoid the occasional typo while writing JavaScript code in your application. This can be quite frustrating with QtWebKit because it likes to stay quiet: it won't tell you about any error.</p>
<p>Let's have a look at an example.</p>
<p>First here is loader.py, a simple Python script which loads a block of HTML:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtCore</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtGui</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtWebKit</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Window</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">view</span> <span class="o">=</span> <span class="n">QWebView</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">setMargin</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="n">window</span> <span class="o">=</span> <span class="n">Window</span><span class="p">()</span>
<span class="n">html</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">()</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>
</p><p>And here is "broken.html", our broken HTML code:</p>
<div class="highlight"><pre><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">brokenFunction</span><span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
<span class="nx">result</span> <span class="o">=</span> <span class="nx">arg1</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">result</span> <span class="o">+=</span> <span class="nx">arg2</span><span class="p">;</span>
<span class="nx">resul</span> <span class="o">/=</span> <span class="mi">4</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
Complex computation:
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">brokenFunction</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>
</p><p>Notice the missing 't' in "resul /= 4"?</p>
<p>The last-resort, grandpa-debugged-js-this-way, debugging tool is still there: the mighty alert() function. Just stuff your code with calls to alert() and be happy... Anyone ever wrote code like that?</p>
<div class="highlight"><pre><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">brokenFunction</span><span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
<span class="nx">result</span> <span class="o">=</span> <span class="nx">arg1</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">alert</span><span class="p">(</span><span class="s2">"1"</span><span class="p">);</span>
<span class="nx">result</span> <span class="o">+=</span> <span class="nx">arg2</span><span class="p">;</span>
<span class="nx">alert</span><span class="p">(</span><span class="s2">"2"</span><span class="p">);</span>
<span class="nx">resul</span> <span class="o">/=</span> <span class="mi">4</span><span class="p">;</span>
<span class="nx">alert</span><span class="p">(</span><span class="s2">"3"</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
Complex computation:
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">brokenFunction</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>
</p><p>Easy enough, no? With the great alert() function we can quickly pinpoint the bug in our brokenFunction() is between alert("2") and alert("3").</p>
<h2>Can we do better?</h2>
<p>alert()-style debugging gets old very fast. Clicking that "OK" button is a pain. Fortunately, there is a way to get more useful feedback from our PyQt application.</p>
<p>The job of the QWebView class is to show the content of a QWebPage instance. By default QWebView creates its own instance of QWebPage, but it is possible to replace this instance with our own QWebPage. The QWebPage class has a few virtual methods. Among them, the javaScriptConsoleMessage() method is the one we are looking for: it is called every time console.log() is called from JavaScript.</p>
<p>Here is an implementation of WebPage which uses Python logging module to get JavaScript console messages out:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtWebKit</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">class</span> <span class="nc">WebPage</span><span class="p">(</span><span class="n">QWebPage</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Makes it possible to use a Python logger to print javascript console messages</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">parent</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">WebPage</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="n">parent</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">logger</span><span class="p">:</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span>
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="n">logger</span>
<span class="k">def</span> <span class="nf">javaScriptConsoleMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">lineNumber</span><span class="p">,</span> <span class="n">sourceID</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s">"JsConsole(</span><span class="si">%s</span><span class="s">:</span><span class="si">%d</span><span class="s">): </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">sourceID</span><span class="p">,</span> <span class="n">lineNumber</span><span class="p">,</span> <span class="n">msg</span><span class="p">))</span>
</pre></div>
<p>
</p><p>And here is "loader-log.py", a loader which uses this class:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtCore</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtGui</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtWebKit</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">webpage</span> <span class="kn">import</span> <span class="n">WebPage</span>
<span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Window</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">view</span> <span class="o">=</span> <span class="n">QWebView</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">setPage</span><span class="p">(</span><span class="n">WebPage</span><span class="p">())</span>
<span class="n">layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">setMargin</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="n">window</span> <span class="o">=</span> <span class="n">Window</span><span class="p">()</span>
<span class="n">html</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">()</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>
</p><p>If we load "broken.html" with "loader-log.py" we get the following on stderr:</p>
<p/><pre><code>$ python loader-log.py broken.html
WARNING:root:JsConsole(undefined:0): ReferenceError: Can't find variable: resul
</code></pre><p/>
<p>That should make it easier to find and fix our bug, even if we don't get very useful file names or line numbers.</p>
<p>javaScriptConsoleMessage() receives all console messages. This means our logger will also print out calls to console.log(). Here is "console-log.html":</p>
<div class="highlight"><pre><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">chattyFunction</span><span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
<span class="nx">result</span> <span class="o">=</span> <span class="nx">arg1</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"result"</span> <span class="o">+</span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">result</span> <span class="o">+=</span> <span class="nx">arg2</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"result"</span> <span class="o">+</span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">result</span> <span class="o">/=</span> <span class="mi">4</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"result"</span> <span class="o">+</span> <span class="nx">result</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
Complex computation:
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">chattyFunction</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>
</p><p>When loaded with "loader-log.py", we get this output:</p>
<p/><pre><code>$ python loader-log.py console-log.html
WARNING:root:JsConsole(about:blank:5): result: 4
WARNING:root:JsConsole(about:blank:5): result: 7
WARNING:root:JsConsole(about:blank:5): result: 1.75
</code></pre><p/>
<h2>Not good enough?</h2>
<p>Getting the output of console.log() is nice, but modern browsers have much more efficient tools: if you open "broken.html" with Rekonq and look at the output in the Web Inspector Console, you can not only see console output, but you can also easily inspect your HTML tree and many other things.</p>
<a href="https://agateau.com/2012/pyqtwebkit-experiments-part-2-debugging/rekonq-webinspector.png"><img src="https://agateau.com/2012/pyqtwebkit-experiments-part-2-debugging/thumb_rekonq-webinspector.png" title="Rekonq Web Inspector" height="148" width="300" alt="" class="aligncenter size-medium wp-image-1083"/></a>
<p>Like us, Rekonq uses QtWebKit, so is there a way to get a similar tool?</p>
<p>It is actually possible. Rekonq uses a class named QWebInspector. All that is necessary to get a nice inspector tool for our application is to:</p>
<ol>
<li>Get the QWebView page</li>
<li>set the QWebSettings.DeveloperExtrasEnabled attribute on this page</li>
<li>Instantiate a QWebInspector</li>
<li>Pass the view page to the inspector</li>
</ol>
<p>Here is "loader-webinspector.py", a new HTML loader which can show a web inspector when one presses F12:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtCore</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtGui</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">PyQt4.QtWebKit</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">webpage</span> <span class="kn">import</span> <span class="n">WebPage</span>
<span class="k">class</span> <span class="nc">Window</span><span class="p">(</span><span class="n">QWidget</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Window</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">view</span> <span class="o">=</span> <span class="n">QWebView</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">setupInspector</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">splitter</span> <span class="o">=</span> <span class="n">QSplitter</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">splitter</span><span class="o">.</span><span class="n">setOrientation</span><span class="p">(</span><span class="n">Qt</span><span class="o">.</span><span class="n">Vertical</span><span class="p">)</span>
<span class="n">layout</span> <span class="o">=</span> <span class="n">QVBoxLayout</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">setMargin</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">layout</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">splitter</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">splitter</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">splitter</span><span class="o">.</span><span class="n">addWidget</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">setupInspector</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">page</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">page</span><span class="p">()</span>
<span class="n">page</span><span class="o">.</span><span class="n">settings</span><span class="p">()</span><span class="o">.</span><span class="n">setAttribute</span><span class="p">(</span><span class="n">QWebSettings</span><span class="o">.</span><span class="n">DeveloperExtrasEnabled</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span> <span class="o">=</span> <span class="n">QWebInspector</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span><span class="o">.</span><span class="n">setPage</span><span class="p">(</span><span class="n">page</span><span class="p">)</span>
<span class="n">shortcut</span> <span class="o">=</span> <span class="n">QShortcut</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="n">shortcut</span><span class="o">.</span><span class="n">setKey</span><span class="p">(</span><span class="n">Qt</span><span class="o">.</span><span class="n">Key_F12</span><span class="p">)</span>
<span class="n">shortcut</span><span class="o">.</span><span class="n">activated</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">toggleInspector</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span><span class="o">.</span><span class="n">setVisible</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">toggleInspector</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span><span class="o">.</span><span class="n">setVisible</span><span class="p">(</span><span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">webInspector</span><span class="o">.</span><span class="n">isVisible</span><span class="p">())</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">QApplication</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="n">window</span> <span class="o">=</span> <span class="n">Window</span><span class="p">()</span>
<span class="n">html</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">setHtml</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">exec_</span><span class="p">()</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>
</p><p>The four steps I described are done in the "setupInspector()" method.</p>
<p>And here is "console-webinspector.html":</p>
<div class="highlight"><pre><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">chattyFunction</span><span class="p">(</span><span class="nx">arg1</span><span class="p">,</span> <span class="nx">arg2</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">result</span><span class="p">;</span>
<span class="nx">result</span> <span class="o">=</span> <span class="nx">arg1</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"result: %d"</span><span class="p">,</span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">result</span> <span class="o">+=</span> <span class="nx">arg2</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="s2">"result: %d"</span><span class="p">,</span> <span class="nx">result</span><span class="p">);</span>
<span class="nx">result</span> <span class="o">/=</span> <span class="mi">4</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">"result: %d"</span><span class="p">,</span> <span class="nx">result</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
Complex computation:
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">chattyFunction</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>
</p><p>It is similar to "console-log.html" but takes advantage of two new features which do not work with the previous approach:</p>
<ul>
<li>printf-style formatting: that is, printing the value of <code>result</code> with <code>"result: %d", result</code>, not <code>"result: " + result</code></li>
<li>log categorization: you can use console.warn() and console.error() to get different type of output. These methods worked with the previous approach, but the categorization was lost.</li>
</ul>
<p>Loading "console-webinspector.html" with "loader-webinspector.py" and pressing F12 we get this:</p>
<a href="https://agateau.com/2012/pyqtwebkit-experiments-part-2-debugging/loader-webinspector.png"><img src="https://agateau.com/2012/pyqtwebkit-experiments-part-2-debugging/thumb_loader-webinspector.png" title="Loader with Web Inspector" height="146" width="300" alt="" class="aligncenter size-medium wp-image-1082"/></a>
<h2>Closing words</h2>
<p>These two approaches should help you track down the nastiest bugs in your embedded JavaScript code. The Web Inspector approach is probably the most powerful one, but the Python logging approach can also be useful when tracking down bugs where it is more practical to have one single log output for both the PyQt and the JavaScript sides.</p>
2012-02-03T21:00:00+01:00