Example #1
0
class Browser(QWidget):
    """LilyPond documentation browser widget."""
    def __init__(self, dockwidget):
        super(Browser, self).__init__(dockwidget)

        layout = QVBoxLayout(spacing=0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.toolbar = tb = QToolBar()
        self.webview = QWebView(contextMenuPolicy=Qt.CustomContextMenu)
        self.chooser = QComboBox(sizeAdjustPolicy=QComboBox.AdjustToContents)
        self.search = SearchEntry(maximumWidth=200)

        layout.addWidget(self.toolbar)
        layout.addWidget(self.webview)

        ac = dockwidget.actionCollection
        ac.help_back.triggered.connect(self.webview.back)
        ac.help_forward.triggered.connect(self.webview.forward)
        ac.help_home.triggered.connect(self.showHomePage)
        ac.help_print.triggered.connect(self.slotPrint)

        self.webview.page().setNetworkAccessManager(
            lilydoc.network.accessmanager())
        self.webview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.webview.page().linkClicked.connect(self.openUrl)
        self.webview.page().setForwardUnsupportedContent(True)
        self.webview.page().unsupportedContent.connect(self.slotUnsupported)
        self.webview.urlChanged.connect(self.slotUrlChanged)
        self.webview.customContextMenuRequested.connect(
            self.slotShowContextMenu)

        tb.addAction(ac.help_back)
        tb.addAction(ac.help_forward)
        tb.addSeparator()
        tb.addAction(ac.help_home)
        tb.addAction(ac.help_print)
        tb.addSeparator()
        tb.addWidget(self.chooser)
        tb.addWidget(self.search)

        self.chooser.activated[int].connect(self.showHomePage)
        self.search.textEdited.connect(self.slotSearchChanged)
        self.search.returnPressed.connect(self.slotSearchReturnPressed)
        dockwidget.mainwindow().iconSizeChanged.connect(
            self.updateToolBarSettings)
        dockwidget.mainwindow().toolButtonStyleChanged.connect(
            self.updateToolBarSettings)

        app.settingsChanged.connect(self.readSettings)
        self.readSettings()
        self.loadDocumentation()
        self.showInitialPage()
        app.settingsChanged.connect(self.loadDocumentation)
        app.translateUI(self)

    def readSettings(self):
        s = QSettings()
        s.beginGroup("documentation")
        ws = self.webview.page().settings()
        family = s.value("fontfamily", self.font().family(), type(""))
        size = s.value("fontsize", 16, int)
        ws.setFontFamily(QWebSettings.StandardFont, family)
        ws.setFontSize(QWebSettings.DefaultFontSize, size)
        fixed = textformats.formatData('editor').font
        ws.setFontFamily(QWebSettings.FixedFont, fixed.family())
        ws.setFontSize(QWebSettings.DefaultFixedFontSize,
                       fixed.pointSizeF() * 96 / 72)

    def keyPressEvent(self, ev):
        if ev.text() == "/":
            self.search.setFocus()
        else:
            super(Browser, self).keyPressEvent(ev)

    def translateUI(self):
        try:
            self.search.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass  # not in Qt 4.6

    def showInitialPage(self):
        """Shows the preferred start page.
        
        If a local documentation instance already has a suitable version,
        just loads it. Otherwise connects to the allLoaded signal, that is
        emitted when all the documentation instances have loaded their version
        information and then shows the start page (if another page wasn't yet
        loaded).
        
        """
        if self.webview.url().isEmpty():
            docs = lilydoc.manager.docs()
            version = lilypondinfo.preferred().version()
            index = -1
            if version:
                for num, doc in enumerate(docs):
                    if doc.version() is not None and doc.version() >= version:
                        index = num  # a suitable documentation is found
                        break
            if index == -1:
                # nothing found (or LilyPond version not available),
                # wait for loading or show the most recent version
                if not lilydoc.manager.loaded():
                    lilydoc.manager.allLoaded.connect(self.showInitialPage)
                    return
                index = len(docs) - 1
            self.chooser.setCurrentIndex(index)
            self.showHomePage()

    def loadDocumentation(self):
        """Puts the available documentation instances in the combobox."""
        i = self.chooser.currentIndex()
        self.chooser.clear()
        for doc in lilydoc.manager.docs():
            v = doc.versionString()
            if doc.isLocal():
                t = _("(local)")
            else:
                t = _("({hostname})").format(hostname=doc.url().host())
            self.chooser.addItem("{0} {1}".format(v or _("<unknown>"), t))
        self.chooser.setCurrentIndex(i)
        if not lilydoc.manager.loaded():
            lilydoc.manager.allLoaded.connect(self.loadDocumentation, -1)
            return

    def updateToolBarSettings(self):
        mainwin = self.parentWidget().mainwindow()
        self.toolbar.setIconSize(mainwin.iconSize())
        self.toolbar.setToolButtonStyle(mainwin.toolButtonStyle())

    def showManual(self):
        """Invoked when the user presses F1."""
        self.slotHomeFrescobaldi()  # TEMP

    def slotUrlChanged(self):
        ac = self.parentWidget().actionCollection
        ac.help_back.setEnabled(self.webview.history().canGoBack())
        ac.help_forward.setEnabled(self.webview.history().canGoForward())

    def openUrl(self, url):
        if url.path().endswith(('.ily', '.lyi', '.ly')):
            self.sourceViewer().showReply(lilydoc.network.get(url))
        else:
            self.webview.load(url)

    def slotUnsupported(self, reply):
        helpers.openUrl(reply.url())

    def slotSearchChanged(self):
        text = self.search.text()
        if not text.startswith(':'):
            self.webview.page().findText(text,
                                         QWebPage.FindWrapsAroundDocument)

    def slotSearchReturnPressed(self):
        text = self.search.text()
        if not text.startswith(':'):
            self.slotSearchChanged()
        else:
            pass  # TODO: implement full doc search

    def sourceViewer(self):
        try:
            return self._sourceviewer
        except AttributeError:
            from . import sourceviewer
            self._sourceviewer = sourceviewer.SourceViewer(self)
            return self._sourceviewer

    def showHomePage(self):
        """Shows the homepage of the LilyPond documentation."""
        i = self.chooser.currentIndex()
        if i < 0:
            i = 0
        doc = lilydoc.manager.docs()[i]

        url = doc.home()
        if doc.isLocal():
            path = url.toLocalFile()
            langs = lilydoc.network.langs()
            if langs:
                for lang in langs:
                    if os.path.exists(path + '.' + lang + '.html'):
                        path += '.' + lang
                        break
            url = QUrl.fromLocalFile(path + '.html')
        self.webview.load(url)

    def slotPrint(self):
        printer = QPrinter()
        dlg = QPrintDialog(printer, self)
        dlg.setWindowTitle(app.caption(_("Print")))
        if dlg.exec_():
            self.webview.print_(printer)

    def slotShowContextMenu(self, pos):
        hit = self.webview.page().currentFrame().hitTestContent(pos)
        menu = QMenu()
        if hit.linkUrl().isValid():
            a = self.webview.pageAction(QWebPage.CopyLinkToClipboard)
            a.setIcon(icons.get("edit-copy"))
            a.setText(_("Copy &Link"))
            menu.addAction(a)
            menu.addSeparator()
            a = menu.addAction(icons.get("window-new"),
                               _("Open Link in &New Window"))
            a.triggered.connect(
                (lambda url: lambda: self.slotNewWindow(url))(hit.linkUrl()))
        else:
            if hit.isContentSelected():
                a = self.webview.pageAction(QWebPage.Copy)
                a.setIcon(icons.get("edit-copy"))
                a.setText(_("&Copy"))
                menu.addAction(a)
                menu.addSeparator()
            a = menu.addAction(icons.get("window-new"),
                               _("Open Document in &New Window"))
            a.triggered.connect((lambda url: lambda: self.slotNewWindow(url))(
                self.webview.url()))
        if menu.actions():
            menu.exec_(self.webview.mapToGlobal(pos))

    def slotNewWindow(self, url):
        helpers.openUrl(url)
class MainWindow(QMainWindow):
    subWindows = []
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setWindowTitle("Seabiscuit2")

        self.menuBar = QMenuBar()
        self.setMenuBar(self.menuBar)

        self.fileMenu = QMenu("&File")
        self.menuBar.addMenu(self.fileMenu)

        self.newWindowAction = QAction("&New Window", self)
        self.newWindowAction.setShortcut("Ctrl+N")
        self.newWindowAction.triggered.connect(self.newWindow)
        self.fileMenu.addAction(self.newWindowAction)

        self.editMenu = QMenu("&Edit")
        self.menuBar.addMenu(self.editMenu)

        self.toolsMenu = QMenu("&Tools")
        self.menuBar.addMenu(self.toolsMenu)

        self.historyAction = QAction("View &History", self)
        self.historyAction.setShortcut("Ctrl+H")
        self.historyAction.triggered.connect(self.loadHistory)
        self.toolsMenu.addAction(self.historyAction)

        self.clearHistoryAction = QAction("&Clear History", self)
        self.clearHistoryAction.setShortcut("Ctrl+Shift+Del")
        self.clearHistoryAction.triggered.connect(self.clearHistory)
        self.toolsMenu.addAction(self.clearHistoryAction)

        self.helpMenu = QMenu("&Help")
        self.menuBar.addMenu(self.helpMenu)

        self.readmeAction = QAction("View &README", self)
        self.readmeAction.setShortcut("F1")
        self.readmeAction.triggered.connect(self.loadReadme)
        self.helpMenu.addAction(self.readmeAction)

        self.aboutAction = QAction("&About Seabiscuit2", self)
        self.aboutAction.setShortcut("F2")
        self.aboutAction.triggered.connect(lambda: self.render("Seabiscuit version " + version + " (running on " + platform + ")"))
        self.helpMenu.addAction(self.aboutAction)

        self.toolBar = QToolBar()
        self.toolBar.setMovable(False)
        self.toolBar.setContextMenuPolicy(Qt.CustomContextMenu)
        self.addToolBar(self.toolBar)

        self.wv = QWebView()

        self.backAction = self.wv.pageAction(QWebPage.Back)
        self.backAction.setEnabled(True)
        self.backAction.setShortcut("Alt+Left")
        self.backAction.triggered.connect(lambda: self.mdiArea.currentSubWindow().widget().back())
        self.toolBar.addAction(self.backAction)

        self.forwardAction = self.wv.pageAction(QWebPage.Forward)
        self.forwardAction.setEnabled(True)
        self.forwardAction.setShortcut("Alt+Right")
        self.forwardAction.triggered.connect(lambda: self.mdiArea.currentSubWindow().widget().forward())
        self.toolBar.addAction(self.forwardAction)

        self.reloadAction = self.wv.pageAction(QWebPage.Reload)
        self.reloadAction.setShortcuts(["F5", "Ctrl+R"])
        self.reloadAction.triggered.connect(lambda: self.mdiArea.currentSubWindow().widget().reload())
        self.toolBar.addAction(self.reloadAction)

        self.locationBar = QLineEdit()
        self.locationBar.returnPressed.connect(self.loadCommand)
        self.toolBar.addWidget(self.locationBar)

        self.focusLocationBarAction = QAction(self)
        self.focusLocationBarAction.setShortcuts(["Ctrl+L", "Alt+D"])
        self.focusLocationBarAction.triggered.connect(self.locationBar.setFocus)
        self.focusLocationBarAction.triggered.connect(self.locationBar.selectAll)
        self.addAction(self.focusLocationBarAction)

        self.mdiArea = QMdiArea()
        self.setCentralWidget(self.mdiArea)

    def newWindow(self):
        s = SWebView(self)
        self.subWindows.append(s)
        self.mdiArea.addSubWindow(s)
        s.activateWindow()
        s.show()
        return s

    def loadHistory(self):
        self.newWindow()
        self.updateWeb(os.path.join(seabiscuit_home, "history.html"))

    def loadReadme(self):
        self.newWindow()
        self.updateWeb("file://" + os.path.join(sys.path[0], "README.html"))

    def closeEvent(self, ev):
        confirm = yes_no_question("Query", "Are you sure you want to quit?")
        if confirm:
            ev.accept()
            sys.exit()
        else:
            ev.ignore()

    def render(self, text):
        QMessageBox.question(None, "Seabiscuit2 says...", text.replace("\n", "<br>"))

    def updateWeb(self, url=None):
        if url != None:
            self.mdiArea.currentSubWindow().widget().load(QUrl.fromUserInput(url))

    def clearHistory(self):
        confirm = yes_no_question("Query", "Are you sure you want to clear the browser history?")
        if confirm:
            historyfile = open(os.path.join(seabiscuit_home, "history.html"), "w")
            historyfile.write("<html><head><script type='text/javascript'>function breakPage(url) {top.location = url}</script></head><body style=\"color: black; background: white;\"><h2>Browsing History</h2>\n")
            historyfile.close()

    def loadCommand(self):
        command = unicode(self.locationBar.text())
        if command.lower().startswith("browser:"):
            command = command.replace("browser:", "")
            webbrowser.open(command)
            self.render("The page has been loaded in your default Web browser.")
        elif command.lower().startswith("download:"):
            command = command.replace("download:", "")
            location = ""
            while not os.path.exists(location):
                location = unicode(QInputDialog.getText("Query", "Enter a valid folder to save your file under."))
                if not os.path.exists(location):
                    render("Invalid path!")
            os.chdir(location)
            os.system("wget '" + command + "'")
            os.chdir(path[0])
        else:
            self.updateWeb(command)
Example #3
0
class BrowserTab(QObject):
    """
    An object for controlling a single browser tab (QWebView).

    It is created by splash.pool.Pool. Pool attaches to tab's deferred
    and waits until either a callback or an errback is called, then destroys
    a BrowserTab.

    XXX: currently cookies are not shared between "browser tabs".
    """

    def __init__(self, network_manager, splash_proxy_factory, verbosity,
                 render_options):
        """ Create a new browser tab. """
        QObject.__init__(self)
        self.deferred = defer.Deferred()
        self.network_manager = network_manager
        self.verbosity = verbosity
        self._uid = render_options.get_uid()
        self._closing = False
        self._active_timers = set()
        self._timers_to_cancel_on_redirect = weakref.WeakKeyDictionary()  # timer: callback
        self._timers_to_cancel_on_error = weakref.WeakKeyDictionary()  # timer: callback
        self._js_console = None
        self._history = []
        self._autoload_scripts = []

        self._init_webpage(verbosity, network_manager, splash_proxy_factory,
                           render_options)
        self._setup_logging(verbosity)
        self.http_client = _SplashHttpClient(self.web_page)

    def _init_webpage(self, verbosity, network_manager, splash_proxy_factory, render_options):
        """ Create and initialize QWebPage and QWebView """
        self.web_page = SplashQWebPage(verbosity)
        self.web_page.setNetworkAccessManager(network_manager)
        self.web_page.splash_proxy_factory = splash_proxy_factory
        self.web_page.render_options = render_options

        self._set_default_webpage_options(self.web_page)
        self._setup_webpage_events()

        self.web_view = QWebView()
        self.web_view.setPage(self.web_page)
        self.web_view.setAttribute(Qt.WA_DeleteOnClose, True)

    def _set_default_webpage_options(self, web_page):
        """
        Set QWebPage options.
        TODO: allow to customize them.
        """
        settings = web_page.settings()
        settings.setAttribute(QWebSettings.JavascriptEnabled, True)
        settings.setAttribute(QWebSettings.PluginsEnabled, False)
        settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
        settings.setAttribute(QWebSettings.LocalStorageEnabled, True)
        settings.setAttribute(QWebSettings.LocalContentCanAccessRemoteUrls, True)
        web_page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
        web_page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)

    def _setup_logging(self, verbosity):
        """ Setup logging of various events """
        self.logger = _BrowserTabLogger(
            uid=self._uid,
            web_page=self.web_page,
            verbosity=verbosity,
        )
        self.logger.enable()

    def _setup_webpage_events(self):
        self._load_finished = WrappedSignal(self.web_page.mainFrame().loadFinished)
        self.web_page.mainFrame().loadFinished.connect(self._on_load_finished)
        self.web_page.mainFrame().urlChanged.connect(self._on_url_changed)
        self.web_page.mainFrame().javaScriptWindowObjectCleared.connect(self._on_javascript_window_object_cleared)

    def return_result(self, result):
        """ Return a result to the Pool. """
        if self._result_already_returned():
            self.logger.log("error: result is already returned", min_level=1)

        self.deferred.callback(result)
        # self.deferred = None

    def return_error(self, error=None):
        """ Return an error to the Pool. """
        if self._result_already_returned():
            self.logger.log("error: result is already returned", min_level=1)
        self.deferred.errback(error)
        # self.deferred = None

    def _result_already_returned(self):
        """ Return True if an error or a result is already returned to Pool """
        return self.deferred.called

    def set_custom_headers(self, headers):
        """
        Set custom HTTP headers to be sent with each request. Passed headers
        are merged with QWebKit default headers, overwriting QWebKit values
        in case of conflicts.
        """
        self.web_page.custom_headers = headers

    def set_images_enabled(self, enabled):
        self.web_page.settings().setAttribute(QWebSettings.AutoLoadImages, enabled)

    def set_viewport(self, size):
        """
        Set viewport size.
        If size is "full" viewport size is detected automatically.
        If can also be "<width>x<height>".
        """
        if size == 'full':
            size = self.web_page.mainFrame().contentsSize()
            if size.isEmpty():
                self.logger.log("contentsSize method doesn't work %s", min_level=1)
                size = defaults.VIEWPORT_FALLBACK

        if not isinstance(size, QSize):
            w, h = map(int, size.split('x'))
            size = QSize(w, h)

        self.web_page.setViewportSize(size)
        w, h = int(size.width()), int(size.height())
        self.logger.log("viewport size is set to %sx%s" % (w, h), min_level=2)
        return w, h

    def lock_navigation(self):
        self.web_page.navigation_locked = True

    def unlock_navigation(self):
        self.web_page.navigation_locked = False

    def set_content(self, data, callback, errback, mime_type=None, baseurl=None):
        """
        Set page contents to ``data``, then wait until page loads.
        Invoke a callback if load was successful or errback if it wasn't.
        """
        if mime_type is None:
            mime_type = "text/html; charset=utf-8"
        if baseurl is None:
            baseurl = ''
        if isinstance(data, unicode):
            data = data.encode('utf8')
        callback_id = self._load_finished.connect(
            self._on_content_ready,
            callback=callback,
            errback=errback,
        )
        self.logger.log("callback %s is connected to loadFinished" % callback_id, min_level=3)
        self.web_page.mainFrame().setContent(data, mime_type, QUrl(baseurl))

    def set_user_agent(self, value):
        """ Set User-Agent header for future requests """
        self.http_client.set_user_agent(value)

    def get_cookies(self):
        """ Return a list of all cookies in the current cookiejar """
        return cookies2har(self.web_page.cookiejar.allCookies())

    def init_cookies(self, cookies):
        """ Replace all current cookies with ``cookies`` """
        self.web_page.cookiejar.init(cookies)

    def clear_cookies(self):
        """ Remove all cookies. Return a number of cookies deleted. """
        return self.web_page.cookiejar.clear()

    def delete_cookies(self, name=None, url=None):
        """
        Delete cookies with name == ``name``.

        If ``url`` is not None then only those cookies are deleted wihch
        are to be added when a request is sent to ``url``.

        Return a number of cookies deleted.
        """
        return self.web_page.cookiejar.delete(name, url)

    def add_cookie(self, cookie):
        return self.web_page.cookiejar.add(cookie)

    @property
    def url(self):
        """ Current URL """
        return unicode(self.web_page.mainFrame().url().toString())

    def go(self, url, callback, errback, baseurl=None, http_method='GET',
           body=None, headers=None):
        """
        Go to an URL. This is similar to entering an URL in
        address tab and pressing Enter.
        """
        self.store_har_timing("_onStarted")

        if baseurl:
            # If baseurl is used, we download the page manually,
            # then set its contents to the QWebPage and let it
            # download related resources and render the result.
            cb = functools.partial(
                self._on_baseurl_request_finished,
                callback=callback,
                errback=errback,
                baseurl=baseurl,
                url=url,
            )
            self.http_client.request(url,
                callback=cb,
                method=http_method,
                body=body,
                headers=headers,
                follow_redirects=True,
            )
        else:
            # if not self._goto_callbacks.isempty():
            #     self.logger.log("Only a single concurrent 'go' request is supported. "
            #                     "Previous go requests will be cancelled.", min_level=1)
            #     # When a new URL is loaded to mainFrame an errback will
            #     # be called, so we're not cancelling this callback manually.

            callback_id = self._load_finished.connect(
                self._on_content_ready,
                callback=callback,
                errback=errback,
            )
            self.logger.log("callback %s is connected to loadFinished" % callback_id, min_level=3)
            self._load_url_to_mainframe(url, http_method, body, headers=headers)

    def stop_loading(self):
        """
        Stop loading of the current page and all pending page
        refresh/redirect requests.
        """
        self.logger.log("stop_loading", min_level=2)
        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()

    def close(self):
        """ Destroy this tab """
        self._closing = True
        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()
        self.web_view.close()
        self.web_page.deleteLater()
        self.web_view.deleteLater()
        self._cancel_all_timers()

    @skip_if_closing
    def _on_load_finished(self, ok):
        if self.web_page.maybe_redirect(ok):
            self.logger.log("Redirect or other non-fatal error detected", min_level=2)
            return

        if self.web_page.is_ok(ok):  # or maybe_redirect:
            self.logger.log("loadFinished: ok", min_level=2)
        else:
            self._cancel_timers(self._timers_to_cancel_on_error)

            if self.web_page.error_loading(ok):
                self.logger.log("loadFinished: %s" % (str(self.web_page.error_info)), min_level=1)
            else:
                self.logger.log("loadFinished: unknown error", min_level=1)

    def _on_baseurl_request_finished(self, callback, errback, baseurl, url):
        """
        This method is called when ``baseurl`` is used and a
        reply for the first request is received.
        """
        self.logger.log("baseurl_request_finished", min_level=2)
        reply = self.sender()
        mime_type = reply.header(QNetworkRequest.ContentTypeHeader).toString()
        data = reply.readAll()
        self.set_content(
            data=data,
            callback=callback,
            errback=errback,
            mime_type=mime_type,
            baseurl=baseurl,
        )
        if reply.error():
            self.logger.log("Error loading %s: %s" % (url, reply.errorString()), min_level=1)

    def _load_url_to_mainframe(self, url, http_method, body=None, headers=None):
        request = self.http_client.request_obj(url, headers=headers)
        meth = OPERATION_QT_CONSTANTS[http_method]
        if body is None:  # PyQT doesn't support body=None
            self.web_page.mainFrame().load(request, meth)
        else:
            self.web_page.mainFrame().load(request, meth, body)

    @skip_if_closing
    def _on_content_ready(self, ok, callback, errback, callback_id):
        """
        This method is called when a QWebPage finishes loading its contents.
        """
        if self.web_page.maybe_redirect(ok):
            # XXX: It assumes loadFinished will be called again because
            # redirect happens. If redirect is detected improperly,
            # loadFinished won't be called again, and Splash will return
            # the result only after a timeout.
            return

        self.logger.log("loadFinished: disconnecting callback %s" % callback_id, min_level=3)
        self._load_finished.disconnect(callback_id)

        if self.web_page.is_ok(ok):
            callback()
        elif self.web_page.error_loading(ok):
            # XXX: maybe return a meaningful error page instead of generic
            # error message?
            errback()
            # errback(RenderError())
        else:
            errback()
            # errback(RenderError())

    def wait(self, time_ms, callback, onredirect=None, onerror=None):
        """
        Wait for time_ms, then run callback.

        If onredirect is True then the timer is cancelled if redirect happens.
        If onredirect is callable then in case of redirect the timer is
        cancelled and this callable is called.

        If onerror is True then the timer is cancelled if a render error
        happens. If onerror is callable then in case of a render error the
        timer is cancelled and this callable is called.
        """

        timer = QTimer()
        timer.setSingleShot(True)
        timer_callback = functools.partial(self._on_wait_timeout,
            timer=timer,
            callback=callback,
        )
        timer.timeout.connect(timer_callback)

        self.logger.log("waiting %sms; timer %s" % (time_ms, id(timer)), min_level=2)

        timer.start(time_ms)
        self._active_timers.add(timer)
        if onredirect:
            self._timers_to_cancel_on_redirect[timer] = onredirect
        if onerror:
            self._timers_to_cancel_on_error[timer] = onerror

    def _on_wait_timeout(self, timer, callback):
        self.logger.log("wait timeout for %s" % id(timer), min_level=2)
        if timer in self._active_timers:
            self._active_timers.remove(timer)
        self._timers_to_cancel_on_redirect.pop(timer, None)
        self._timers_to_cancel_on_error.pop(timer, None)
        callback()

    def _cancel_timer(self, timer, errback=None):
        self.logger.log("cancelling timer %s" % id(timer), min_level=2)
        if timer in self._active_timers:
            self._active_timers.remove(timer)
        timer.stop()
        try:
            if callable(errback):
                self.logger.log("calling timer errback", min_level=2)
                errback()
        finally:
            timer.deleteLater()

    def _cancel_timers(self, timers):
        for timer, oncancel in list(timers.items()):
            self._cancel_timer(timer, oncancel)
            timers.pop(timer, None)

    def _cancel_all_timers(self):
        self.logger.log("cancelling %d remaining timers" % len(self._active_timers), min_level=2)
        for timer in list(self._active_timers):
            self._cancel_timer(timer)

    def _on_url_changed(self, url):
        # log history
        url = unicode(url.toString())
        cause_ev = self.web_page.har_log._prev_entry(url, -1)
        if cause_ev:
            self._history.append(without_private(cause_ev.data))

        self._cancel_timers(self._timers_to_cancel_on_redirect)

    def run_js_file(self, filename):
        """
        Load JS library from file ``filename`` to the current frame.
        """
        with open(filename, 'rb') as f:
            script = f.read().decode('utf-8')
            return self.runjs(script)

    def run_js_files(self, folder):
        """
        Load all JS libraries from ``folder`` folder to the current frame.
        """
        for jsfile in os.listdir(folder):
            if jsfile.endswith('.js'):
                filename = os.path.join(folder, jsfile)
                self.run_js_file(filename)

    def autoload(self, js_source):
        """ Execute JS code before each page load """
        self._autoload_scripts.append(js_source)

    def no_autoload(self):
        """ Remove all scripts scheduled for auto-loading """
        self._autoload_scripts = []

    def _on_javascript_window_object_cleared(self):
        for script in self._autoload_scripts:
            self.web_page.mainFrame().evaluateJavaScript(script)

    def http_get(self, url, callback, headers=None, follow_redirects=True):
        """ Send a GET request; call a callback with the reply as an argument. """
        self.http_client.get(url,
            callback=callback,
            headers=headers,
            follow_redirects=follow_redirects
        )

    def runjs(self, js_source):
        """
        Run JS code in page context and return the result.
        Only string results are supported.
        """
        frame = self.web_page.mainFrame()
        res = frame.evaluateJavaScript(js_source)
        return qt2py(res)

    def store_har_timing(self, name):
        self.web_page.har_log.store_timing(name)

    def _jsconsole_enable(self):
        # TODO: add public interface or make console available by default
        if self._js_console is not None:
            return
        self._js_console = _JavascriptConsole()
        frame = self.web_page.mainFrame()
        frame.addToJavaScriptWindowObject('console', self._js_console)

    def _jsconsole_messages(self):
        # TODO: add public interface or make console available by default
        if self._js_console is None:
            return []
        return self._js_console.messages[:]

    def html(self):
        """ Return HTML of the current main frame """
        self.logger.log("getting HTML", min_level=2)
        frame = self.web_page.mainFrame()
        result = bytes(frame.toHtml().toUtf8())
        self.store_har_timing("_onHtmlRendered")
        return result

    def png(self, width=None, height=None, b64=False):
        """ Return screenshot in PNG format """
        self.logger.log("getting PNG", min_level=2)

        image = QImage(self.web_page.viewportSize(), QImage.Format_ARGB32)
        painter = QPainter(image)
        self.web_page.mainFrame().render(painter)
        painter.end()
        self.store_har_timing("_onScreenshotPrepared")

        if width:
            image = image.scaledToWidth(width, Qt.SmoothTransformation)
        if height:
            image = image.copy(0, 0, width, height)
        b = QBuffer()
        image.save(b, "png")
        result = bytes(b.data())
        if b64:
            result = base64.b64encode(result)
        self.store_har_timing("_onPngRendered")
        return result

    def iframes_info(self, children=True, html=True):
        """ Return information about all iframes """
        self.logger.log("getting iframes", min_level=3)
        frame = self.web_page.mainFrame()
        result = self._frame_to_dict(frame, children, html)
        self.store_har_timing("_onIframesRendered")
        return result

    def har(self):
        """ Return HAR information """
        self.logger.log("getting HAR", min_level=3)
        return self.web_page.har_log.todict()

    def history(self):
        """ Return history of 'main' HTTP requests """
        self.logger.log("getting history", min_level=3)
        return copy.deepcopy(self._history)

    def last_http_status(self):
        """
        Return HTTP status code of the currently loaded webpage
        or None if it is not available.
        """
        if not self._history:
            return
        try:
            return self._history[-1]["response"]["status"]
        except KeyError:
            return

    def _frame_to_dict(self, frame, children=True, html=True):
        g = frame.geometry()
        res = {
            "url": unicode(frame.url().toString()),
            "requestedUrl": unicode(frame.requestedUrl().toString()),
            "geometry": (g.x(), g.y(), g.width(), g.height()),
            "title": unicode(frame.title())
        }
        if html:
            res["html"] = unicode(frame.toHtml())

        if children:
            res["childFrames"] = [
                self._frame_to_dict(f, True, html)
                for f in frame.childFrames()
            ]
            res["frameName"] = unicode(frame.frameName())

        return res
Example #4
0
class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.initUI()

    def initUI(self):
        self.centralWidget = QtGui.QWidget(self)

        '''
        Address Bar
        '''
        self.addbar = QtGui.QLineEdit(self)
        self.addbar.setGeometry(200, 16, 840, 35)
        self.addbar.setStyleSheet("font-size:21px")
        self.addbar.setFont(QtGui.QFont("Times New Roman"))
        self.addbar.returnPressed.connect(self.goClicked)
        self.addbar.cursorPositionAt(QtCore.QPoint(0, 0))
        self.addbar.setFocus()
        self.addbar.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, True)

        '''
        Progress Bar
        '''
        self.progressbar = QtGui.QProgressBar(self)
        self.progressbar.setMaximumWidth(100)

        '''
        Web Page Action setting
        '''
        self.web = QWebView(loadProgress=self.progressbar.setValue,
                            loadFinished=self.progressbar.hide, loadStarted=
                            self.progressbar.show, titleChanged=self.
                            setWindowTitle)
        self.web.setGeometry(300, 100, 1099, 768)
        self.web.urlChanged.connect(self.goUrlChanged)
        self.web.page().linkHovered.connect(self.LinkHovered)
        self.web.loadStarted.connect(self.pageLoadStarted)
        self.web.loadFinished.connect(self.pageLoadFinished)

        '''
        Back, Forward, Reload and Stop Button
        '''
        self.toolbar = QtGui.QToolBar(self)
        self.toolbar.addAction(self.web.pageAction(QWebPage.Back))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Forward))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Reload))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Stop))

        '''
        Global Web Page Setting
        '''
        self.setting = QWebSettings.globalSettings()
        self.setting.setAttribute(QWebSettings.PluginsEnabled, True)
        self.setting.setAttribute(QWebSettings.AutoLoadImages, True)
        self.setting.setAttribute(QWebSettings.JavaEnabled, True)
        self.setting.setAttribute(QWebSettings.DnsPrefetchEnabled, True)
        self.setting.setAttribute(QWebSettings.WebGLEnabled, True)
        self.setting.setAttribute(QWebSettings.LocalStorageEnabled, True)
        self.setting.setAttribute(QWebSettings.JavascriptEnabled, True)
        self.setting.setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
        self.setting.setAttribute(QWebSettings.
                                  OfflineWebApplicationCacheEnabled, True)
        self.setting.setAttribute(QWebSettings.ZoomTextOnly, True)
        self.setting.setAttribute(QWebSettings.AcceleratedCompositingEnabled,
                                  True)
        self.setting.setAttribute(QWebSettings.SpatialNavigationEnabled, True)
        self.setting.setAttribute(QWebSettings.LinksIncludedInFocusChain, True)
        self.setting.setAttribute(QWebSettings.LocalContentCanAccessFileUrls,
                                  True)

        '''
        Main Window Setting
        '''
        self.setGeometry(300, 100, 1099, 768)
        self.setWindowIcon(QtGui.QIcon(""))
        self.setStyleSheet("background-color:")
        self.status = self.statusBar()
        self.status.setSizeGripEnabled(False)
        self.status.addPermanentWidget(self.progressbar)
        self.status.hide()
        self.setCentralWidget(self.centralWidget)
        grid = QtGui.QGridLayout()
        grid.addWidget(self.toolbar, 0, 0, 1, 1)
        grid.addWidget(self.addbar, 0, 1, 1, 1)
        grid.addWidget(self.web, 2, 0, 1, 6)
        self.centralWidget.setLayout(grid)

        '''
        Use system Proxy to load page
        '''
        QNetworkProxyFactory.setUseSystemConfiguration(True)

    def goClicked(self):
        '''
        When URL is entered in addressbar and enter is pressed
        '''
        global url
        url = self.addbar.text()
        http = "http://"
        www = "www."
        https = "https://"
        if www in url and http not in url:
            url = http + url
        elif "." not in url:
            url = "http://www.google.com/search?q=" + url
        elif http in url and www not in url:
            url = url[:7] + www + url[7:]
        elif https in url:
            url = url
        elif http and www not in url:
            url = http + www + url

        self.addbar.setText(url)
        self.web.load(QtCore.QUrl(url))
        self.status.show()
        self.web.setFocus(True)

    def goUrlChanged(self):
        '''
        Update URL in Address bar if link is clicked
        '''
        self.addbar.setText(self.web.url().toString())

    def LinkHovered(self, l):
        self.status.showMessage(l)

    def pageLoadStarted(self):
        self.status.showMessage("Loading")
        self.status.show()

    def pageLoadFinished(self):
        self.addbar.setText(self.web.url().toString())
        self.status.hide()
Example #5
0
class WebpageRender(object):
    """
    WebpageRender object renders a webpage: it downloads the page using
    network_manager and renders it using QWebView according to options
    passed to :meth:`WebpageRender.doRequest`.

    This class is not used directly; its subclasses are used.
    Subclasses choose how to return the result (as html, json, png).
    """

    def __init__(self, network_manager, splash_proxy_factory, splash_request, verbosity):
        self.network_manager = network_manager
        self.web_view = QWebView()
        self.web_page = SplashQWebPage(verbosity)
        self.web_page.setNetworkAccessManager(self.network_manager)
        self.web_view.setPage(self.web_page)
        self.web_view.setAttribute(Qt.WA_DeleteOnClose, True)

        settings = self.web_page.settings()
        settings.setAttribute(QWebSettings.JavascriptEnabled, True)
        settings.setAttribute(QWebSettings.PluginsEnabled, False)
        settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
        settings.setAttribute(QWebSettings.LocalStorageEnabled, True)
        settings.setAttribute(QWebSettings.LocalContentCanAccessRemoteUrls, True)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)

        self.splash_request = splash_request
        self.web_page.splash_request = splash_request
        self.web_page.splash_proxy_factory = splash_proxy_factory
        self.verbosity = verbosity

        self.deferred = defer.Deferred()
        self._finished_timer = None
        self._closing = False

    # ======= General request/response handling:

    def start(self, url, baseurl=None, wait=None, viewport=None,
                  js_source=None, js_profile=None, images=None, console=False,
                  headers=None, http_method='GET', body=None):

        self.web_page.har_log.store_timing("_onStarted")

        self.url = url
        self.history = []
        self.web_page.settings().setAttribute(QWebSettings.AutoLoadImages, images)
        self.wait_time = defaults.WAIT_TIME if wait is None else wait

        self.js_source = js_source
        self.js_profile = js_profile
        self.console = console
        self.viewport = defaults.VIEWPORT if viewport is None else viewport

        # setup logging
        if self.verbosity >= 4:
            self.web_page.loadStarted.connect(self._loadStarted)
            self.web_page.mainFrame().loadFinished.connect(self._frameLoadFinished)
            self.web_page.mainFrame().loadStarted.connect(self._frameLoadStarted)
            self.web_page.mainFrame().contentsSizeChanged.connect(self._contentsSizeChanged)

        if self.verbosity >= 3:
            self.web_page.mainFrame().javaScriptWindowObjectCleared.connect(self._javaScriptWindowObjectCleared)
            self.web_page.mainFrame().initialLayoutCompleted.connect(self._initialLayoutCompleted)

        self.web_page.mainFrame().urlChanged.connect(self._urlChanged)

        # do the request
        request = QNetworkRequest()
        request.setUrl(QUrl(url.decode('utf8')))
        self._setHeaders(request, headers)

        if getattr(self.splash_request, 'inspect_me', False):
            # Set http method and request body from the request
            http_method = self.splash_request.method
            body = self.splash_request.content.getvalue()

        if self.viewport != 'full':
            # viewport='full' can't be set if content is not loaded yet,
            # but in other cases it is better to set it earlier.
            self._setViewportSize(self.viewport)

        if baseurl:
            # If baseurl is used, we download the page manually,
            # then set its contents to the QWebPage and let it
            # download related resources and render the result.
            if http_method != 'GET':
                raise NotImplementedError()

            self._baseUrl = QUrl(baseurl.decode('utf8'))
            request.setOriginatingObject(self.web_page.mainFrame())
            self._reply = self.network_manager.get(request)
            self._reply.finished.connect(self._requestFinished)
        else:
            self.web_page.loadFinished.connect(self._loadFinished)
            meth = OPERATION_QT_CONSTANTS[http_method]
            if body is None:  # PyQT doesn't support body=None
                self.web_page.mainFrame().load(request, meth)
            else:
                self.web_page.mainFrame().load(request, meth, body)

    def render(self):
        """
        This method is called to get the result after the requested page is
        downloaded and rendered. Subcalles should implement it to customize
        which data to return.
        """
        raise NotImplementedError()

    def close(self):
        """
        This method is called by a Pool after the rendering is done and
        the WebpageRender object is no longer needed.
        """
        self._closing = True
        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()
        self.web_view.close()
        self.web_page.deleteLater()
        self.web_view.deleteLater()

    def _setHeaders(self, request, headers):
        """ Set HTTP headers for the ``request``. """
        if isinstance(headers, dict):
            headers = headers.items()

        for name, value in headers or []:
            request.setRawHeader(name, value)
            if name.lower() == 'user-agent':
                self.web_page.custom_user_agent = value

    def _requestFinished(self):
        """
        This method is called when ``baseurl`` is used and a
        reply for the first request is received.
        """
        self.log("_requestFinished %s" % id(self.splash_request))
        self.web_page.loadFinished.connect(self._loadFinished)
        mimeType = self._reply.header(QNetworkRequest.ContentTypeHeader).toString()
        data = self._reply.readAll()
        self.web_page.mainFrame().setContent(data, mimeType, self._baseUrl)
        if self._reply.error():
            self.log("Error loading %s: %s" % (self.url, self._reply.errorString()), min_level=1)
        self._reply.close()
        self._reply.deleteLater()

    def _loadFinished(self, ok):
        """
        This method is called when a QWebPage finished loading its contents.
        """
        if self._closing:
            self.log("loadFinished is ignored because WebpageRender is closing", min_level=3)
            return

        if self.deferred.called:
            # sometimes this callback is called multiple times
            self.log("loadFinished called multiple times", min_level=1)
            return

        page_ok = ok and self.web_page.errorInfo is None
        maybe_redirect = not ok and self.web_page.errorInfo is None
        error_loading = ok and self.web_page.errorInfo is not None

        if maybe_redirect:
            self.log("Redirect or other non-fatal error detected %s" % id(self.splash_request))
            # XXX: It assumes loadFinished will be called again because
            # redirect happens. If redirect is detected improperly,
            # loadFinished won't be called again, and Splash will return
            # the result only after a timeout.
            #
            # FIXME: This can happen if server returned incorrect
            # Content-Type header; there is no an additional loadFinished
            # signal in this case.
            return

        if page_ok:  # or maybe_redirect:
            if self.wait_time == 0:
                self.log("loadFinished %s; not waiting" % (id(self.splash_request)))
                self._loadFinishedOK()
            else:
                time_ms = int(self.wait_time * 1000)
                self.log("loadFinished %s; waiting %sms" % (id(self.splash_request), time_ms))
                if self._finished_timer is not None:
                    raise Exception("timer is not None!")

                self._finished_timer = QTimer()
                self._finished_timer.setSingleShot(True)
                self._finished_timer.timeout.connect(self._loadFinishedOK)
                self._finished_timer.start(time_ms)
        elif error_loading:
            self.log("loadFinished %s: %s" % (id(self.splash_request), str(self.web_page.errorInfo)), min_level=1)
            # XXX: maybe return a meaningful error page instead of generic
            # error message?
            self.deferred.errback(RenderError())
        else:
            self.log("loadFinished %s: unknown error" % id(self.splash_request), min_level=1)
            self.deferred.errback(RenderError())

    def _loadFinishedOK(self):
        self._finished_timer = None
        self.log("_loadFinishedOK %s" % id(self.splash_request))

        if self._closing:
            self.log("loadFinishedOK is ignored because WebpageRender is closing", min_level=3)
            return

        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()

        self.web_page.har_log.store_timing("_onPrepareStart")
        try:
            self._prepareRender()
            self.deferred.callback(self.render())
        except:
            self.deferred.errback()

    def _frameLoadFinished(self, ok):
        self.log("mainFrame().LoadFinished %s %s" % (id(self.splash_request), ok), min_level=4)

    def _loadStarted(self):
        self.log("loadStarted %s" % id(self.splash_request), min_level=4)

    def _urlChanged(self, url):
        cause_ev = self.web_page.har_log._prev_entry(unicode(url.toString()), -1)
        if cause_ev:
            self.history.append(without_private(cause_ev.data))

        msg = "mainFrame().urlChanged %s: %s" % (id(self.splash_request), qurl2ascii(url))
        self.log(msg, min_level=3)

        if self._finished_timer is not None:
            self.log("Cancelling wait timer %s" % (id(self.splash_request)))
            self._finished_timer.stop()
            self._finished_timer = None

    def _frameLoadStarted(self):
        self.log("mainFrame().loadStarted %s" % id(self.splash_request), min_level=4)

    def _initialLayoutCompleted(self):
        self.log("mainFrame().initialLayoutCompleted %s" % id(self.splash_request), min_level=3)

    def _javaScriptWindowObjectCleared(self):
        self.log("mainFrame().javaScriptWindowObjectCleared %s" % id(self.splash_request), min_level=3)

    def _contentsSizeChanged(self):
        self.log("mainFrame().contentsSizeChanged %s" % id(self.splash_request), min_level=4)

    def _repaintRequested(self):
        self.log("mainFrame().repaintRequested %s" % id(self.splash_request), min_level=4)

    # ======= Rendering methods that subclasses can use:

    def _getHtml(self):
        self.log("getting HTML %s" % id(self.splash_request))
        frame = self.web_page.mainFrame()
        result = bytes(frame.toHtml().toUtf8())
        self.web_page.har_log.store_timing("_onHtmlRendered")
        return result

    def _getPng(self, width=None, height=None, b64=False):
        self.log("getting PNG %s" % id(self.splash_request))

        image = QImage(self.web_page.viewportSize(), QImage.Format_ARGB32)
        painter = QPainter(image)
        self.web_page.mainFrame().render(painter)
        painter.end()
        self.web_page.har_log.store_timing("_onScreenshotPrepared")

        if width:
            image = image.scaledToWidth(width, Qt.SmoothTransformation)
        if height:
            image = image.copy(0, 0, width, height)
        b = QBuffer()
        image.save(b, "png")
        result = bytes(b.data())
        if b64:
            result = base64.b64encode(result)
        self.web_page.har_log.store_timing("_onPngRendered")
        return result

    def _getIframes(self, children=True, html=True):
        self.log("getting iframes %s" % id(self.splash_request), min_level=3)
        frame = self.web_page.mainFrame()
        result = self._frameToDict(frame, children, html)
        self.web_page.har_log.store_timing("_onIframesRendered")
        return result

    def _getHistory(self):
        self.log("getting history %s" % id(self.splash_request), min_level=3)

        hist = copy.deepcopy(self.history)
        for entry in hist:
            if entry is not None:
                del entry['request']['queryString']
        return hist

    def _getHAR(self):
        self.log("getting HAR %s" % id(self.splash_request), min_level=3)
        return self.web_page.har_log.todict()

    # ======= Other helper methods:

    def _setViewportSize(self, size):
        if not isinstance(size, QSize):
            w, h = map(int, size.split('x'))
            size = QSize(w, h)
        self.web_page.setViewportSize(size)
        w, h = int(size.width()), int(size.height())
        self.log("viewport size for %s is set to %sx%s" % (id(self.splash_request), w, h))

    def _setFullViewport(self):
        size = self.web_page.mainFrame().contentsSize()
        if size.isEmpty():
            self.log("contentsSize method doesn't work %s" % id(self.splash_request), min_level=1)
            self._setViewportSize(defaults.VIEWPORT_FALLBACK)
        else:
            self._setViewportSize(size)
        self.web_page.har_log.store_timing("_onFullViewportSet")

    def _loadJsLibs(self, frame, js_profile):
        if js_profile:
            for jsfile in os.listdir(js_profile):
                if jsfile.endswith('.js'):
                    with open(os.path.join(js_profile, jsfile)) as f:
                        frame.evaluateJavaScript(f.read().decode('utf-8'))

    def _runJS(self, js_source, js_profile):
        js_output = None
        js_console_output = None
        if js_source:
            frame = self.web_page.mainFrame()
            if self.console:
                js_console = JavascriptConsole()
                frame.addToJavaScriptWindowObject('console', js_console)
            if js_profile:
                self._loadJsLibs(frame, js_profile)
            ret = frame.evaluateJavaScript(js_source)
            js_output = bytes(ret.toString().toUtf8())
            if self.console:
                js_console_output = [bytes(s.toUtf8()) for s in js_console.messages]

        self.web_page.har_log.store_timing('_onCustomJsExecuted')
        return js_output, js_console_output

    def _frameToDict(self, frame, children=True, html=True):
        g = frame.geometry()
        res = {
            "url": unicode(frame.url().toString()),
            "requestedUrl": unicode(frame.requestedUrl().toString()),
            "geometry": (g.x(), g.y(), g.width(), g.height()),
            "title": unicode(frame.title())
        }
        if html:
            res["html"] = unicode(frame.toHtml())

        if children:
            res["childFrames"] = [self._frameToDict(f, True, html) for f in frame.childFrames()]
            res["frameName"] = unicode(frame.frameName())

        return res

    def _prepareRender(self):
        if self.viewport == 'full':
            self._setFullViewport()
        self.js_output, self.js_console_output = self._runJS(self.js_source, self.js_profile)

    def log(self, text, min_level=2):
        if self.verbosity >= min_level:
            if isinstance(text, unicode):
                text = text.encode('unicode-escape').decode('ascii')
            log.msg(text, system='render')
Example #6
0
class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.initUI()

    def initUI(self):
        self.centralWidget = QtGui.QWidget(self)
        '''
        Address Bar
        '''
        self.addbar = QtGui.QLineEdit(self)
        self.addbar.setGeometry(200, 16, 840, 35)
        self.addbar.setStyleSheet("font-size:21px")
        self.addbar.setFont(QtGui.QFont("Times New Roman"))
        self.addbar.returnPressed.connect(self.goClicked)
        self.addbar.cursorPositionAt(QtCore.QPoint(0, 0))
        self.addbar.setFocus()
        self.addbar.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, True)
        '''
        Progress Bar
        '''
        self.progressbar = QtGui.QProgressBar(self)
        self.progressbar.setMaximumWidth(100)
        '''
        Web Page Action setting
        '''
        self.web = QWebView(loadProgress=self.progressbar.setValue,
                            loadFinished=self.progressbar.hide,
                            loadStarted=self.progressbar.show,
                            titleChanged=self.setWindowTitle)
        self.web.setGeometry(300, 100, 1099, 768)
        self.web.urlChanged.connect(self.goUrlChanged)
        self.web.page().linkHovered.connect(self.LinkHovered)
        self.web.loadStarted.connect(self.pageLoadStarted)
        self.web.loadFinished.connect(self.pageLoadFinished)
        '''
        Back, Forward, Reload and Stop Button
        '''
        self.toolbar = QtGui.QToolBar(self)
        self.toolbar.addAction(self.web.pageAction(QWebPage.Back))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Forward))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Reload))
        self.toolbar.addAction(self.web.pageAction(QWebPage.Stop))
        '''
        Global Web Page Setting
        '''
        self.setting = QWebSettings.globalSettings()
        self.setting.setAttribute(QWebSettings.PluginsEnabled, True)
        self.setting.setAttribute(QWebSettings.AutoLoadImages, True)
        self.setting.setAttribute(QWebSettings.JavaEnabled, True)
        self.setting.setAttribute(QWebSettings.DnsPrefetchEnabled, True)
        self.setting.setAttribute(QWebSettings.WebGLEnabled, True)
        self.setting.setAttribute(QWebSettings.LocalStorageEnabled, True)
        self.setting.setAttribute(QWebSettings.JavascriptEnabled, True)
        self.setting.setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
        self.setting.setAttribute(
            QWebSettings.OfflineWebApplicationCacheEnabled, True)
        self.setting.setAttribute(QWebSettings.ZoomTextOnly, True)
        self.setting.setAttribute(QWebSettings.AcceleratedCompositingEnabled,
                                  True)
        self.setting.setAttribute(QWebSettings.SpatialNavigationEnabled, True)
        self.setting.setAttribute(QWebSettings.LinksIncludedInFocusChain, True)
        self.setting.setAttribute(QWebSettings.LocalContentCanAccessFileUrls,
                                  True)
        '''
        Main Window Setting
        '''
        self.setGeometry(300, 100, 1099, 768)
        self.setWindowIcon(QtGui.QIcon(""))
        self.setStyleSheet("background-color:")
        self.status = self.statusBar()
        self.status.setSizeGripEnabled(False)
        self.status.addPermanentWidget(self.progressbar)
        self.status.hide()
        self.setCentralWidget(self.centralWidget)
        grid = QtGui.QGridLayout()
        grid.addWidget(self.toolbar, 0, 0, 1, 1)
        grid.addWidget(self.addbar, 0, 1, 1, 1)
        grid.addWidget(self.web, 2, 0, 1, 6)
        self.centralWidget.setLayout(grid)
        '''
        Use system Proxy to load page
        '''
        QNetworkProxyFactory.setUseSystemConfiguration(True)

    def goClicked(self):
        '''
        When URL is entered in addressbar and enter is pressed
        '''
        global url
        url = self.addbar.text()
        http = "http://"
        www = "www."
        https = "https://"
        if www in url and http not in url:
            url = http + url
        elif "." not in url:
            url = "http://www.google.com/search?q=" + url
        elif http in url and www not in url:
            url = url[:7] + www + url[7:]
        elif https in url:
            url = url
        elif http and www not in url:
            url = http + www + url

        self.addbar.setText(url)
        self.web.load(QtCore.QUrl(url))
        self.status.show()
        self.web.setFocus(True)

    def goUrlChanged(self):
        '''
        Update URL in Address bar if link is clicked
        '''
        self.addbar.setText(self.web.url().toString())

    def LinkHovered(self, l):
        self.status.showMessage(l)

    def pageLoadStarted(self):
        self.status.showMessage("Loading")
        self.status.show()

    def pageLoadFinished(self):
        self.addbar.setText(self.web.url().toString())
        self.status.hide()
Example #7
0
class Browser(QWidget):
    """LilyPond documentation browser widget."""
    def __init__(self, dockwidget):
        super(Browser, self).__init__(dockwidget)
        
        layout = QVBoxLayout(spacing=0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        
        self.toolbar = tb = QToolBar()
        self.webview = QWebView(contextMenuPolicy=Qt.CustomContextMenu)
        self.chooser = QComboBox(sizeAdjustPolicy=QComboBox.AdjustToContents)
        self.search = SearchEntry(maximumWidth=200)
        
        layout.addWidget(self.toolbar)
        layout.addWidget(self.webview)
        
        ac = dockwidget.actionCollection
        ac.help_back.triggered.connect(self.webview.back)
        ac.help_forward.triggered.connect(self.webview.forward)
        ac.help_home.triggered.connect(self.showHomePage)
        ac.help_print.triggered.connect(self.slotPrint)
        
        self.webview.page().setNetworkAccessManager(lilydoc.network.accessmanager())
        self.webview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.webview.page().linkClicked.connect(self.openUrl)
        self.webview.page().setForwardUnsupportedContent(True)
        self.webview.page().unsupportedContent.connect(self.slotUnsupported)
        self.webview.urlChanged.connect(self.slotUrlChanged)
        self.webview.customContextMenuRequested.connect(self.slotShowContextMenu)
        
        tb.addAction(ac.help_back)
        tb.addAction(ac.help_forward)
        tb.addSeparator()
        tb.addAction(ac.help_home)
        tb.addAction(ac.help_print)
        tb.addSeparator()
        tb.addWidget(self.chooser)
        tb.addWidget(self.search)
        
        self.chooser.activated[int].connect(self.showHomePage)
        self.search.textEdited.connect(self.slotSearchChanged)
        self.search.returnPressed.connect(self.slotSearchReturnPressed)
        dockwidget.mainwindow().iconSizeChanged.connect(self.updateToolBarSettings)
        dockwidget.mainwindow().toolButtonStyleChanged.connect(self.updateToolBarSettings)
        
        app.settingsChanged.connect(self.readSettings)
        self.readSettings()
        self.loadDocumentation()
        self.showInitialPage()
        app.settingsChanged.connect(self.loadDocumentation)
        app.translateUI(self)
    
    def readSettings(self):
        s = QSettings()
        s.beginGroup("documentation")
        ws = self.webview.page().settings()
        family = s.value("fontfamily", self.font().family(), type(""))
        size = s.value("fontsize", 16, int)
        ws.setFontFamily(QWebSettings.StandardFont, family)
        ws.setFontSize(QWebSettings.DefaultFontSize, size)
        fixed = textformats.formatData('editor').font
        ws.setFontFamily(QWebSettings.FixedFont, fixed.family())
        ws.setFontSize(QWebSettings.DefaultFixedFontSize, fixed.pointSizeF() * 96 / 72)
        
    def keyPressEvent(self, ev):
        if ev.text() == "/":
            self.search.setFocus()
        else:
            super(Browser, self).keyPressEvent(ev)
        
    def translateUI(self):
        try:
            self.search.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass # not in Qt 4.6
    
    def showInitialPage(self):
        """Shows the preferred start page.
        
        If a local documentation instance already has a suitable version,
        just loads it. Otherwise connects to the allLoaded signal, that is
        emitted when all the documentation instances have loaded their version
        information and then shows the start page (if another page wasn't yet
        loaded).
        
        """
        if self.webview.url().isEmpty():
            docs = lilydoc.manager.docs()
            version = lilypondinfo.preferred().version()
            index = -1
            if version:
                for num, doc in enumerate(docs):
                    if doc.version() is not None and doc.version() >= version:
                        index = num # a suitable documentation is found
                        break
            if index == -1:
                # nothing found (or LilyPond version not available),
                # wait for loading or show the most recent version
                if not lilydoc.manager.loaded():
                    lilydoc.manager.allLoaded.connect(self.showInitialPage)
                    return
                index = len(docs) - 1
            self.chooser.setCurrentIndex(index)
            self.showHomePage()
    
    def loadDocumentation(self):
        """Puts the available documentation instances in the combobox."""
        i = self.chooser.currentIndex()
        self.chooser.clear()
        for doc in lilydoc.manager.docs():
            v = doc.versionString()
            if doc.isLocal():
                t = _("(local)")
            else:
                t = _("({hostname})").format(hostname=doc.url().host())
            self.chooser.addItem("{0} {1}".format(v or _("<unknown>"), t))
        self.chooser.setCurrentIndex(i)
        if not lilydoc.manager.loaded():
            lilydoc.manager.allLoaded.connect(self.loadDocumentation, -1)
            return
        
    def updateToolBarSettings(self):
        mainwin = self.parentWidget().mainwindow()
        self.toolbar.setIconSize(mainwin.iconSize())
        self.toolbar.setToolButtonStyle(mainwin.toolButtonStyle())
        
    def showManual(self):
        """Invoked when the user presses F1."""
        self.slotHomeFrescobaldi() # TEMP
        
    def slotUrlChanged(self):
        ac = self.parentWidget().actionCollection
        ac.help_back.setEnabled(self.webview.history().canGoBack())
        ac.help_forward.setEnabled(self.webview.history().canGoForward())
    
    def openUrl(self, url):
        if url.path().endswith(('.ily', '.lyi', '.ly')):
            self.sourceViewer().showReply(lilydoc.network.get(url))
        else:
            self.webview.load(url)
    
    def slotUnsupported(self, reply):
        helpers.openUrl(reply.url())
    
    def slotSearchChanged(self):
        text = self.search.text()
        if not text.startswith(':'):
            self.webview.page().findText(text, QWebPage.FindWrapsAroundDocument)
    
    def slotSearchReturnPressed(self):
        text = self.search.text()
        if not text.startswith(':'):
            self.slotSearchChanged()
        else:
            pass # TODO: implement full doc search
    
    def sourceViewer(self):
        try:
            return self._sourceviewer
        except AttributeError:
            from . import sourceviewer
            self._sourceviewer = sourceviewer.SourceViewer(self)
            return self._sourceviewer
    
    def showHomePage(self):
        """Shows the homepage of the LilyPond documentation."""
        i = self.chooser.currentIndex()
        if i < 0:
            i = 0
        doc = lilydoc.manager.docs()[i]
        
        url = doc.home()
        if doc.isLocal():
            path = url.toLocalFile()
            langs = lilydoc.network.langs()
            if langs:
                for lang in langs:
                    if os.path.exists(path + '.' + lang + '.html'):
                        path += '.' + lang
                        break
            url = QUrl.fromLocalFile(path + '.html')
        self.webview.load(url)
    
    def slotPrint(self):
        printer = QPrinter()
        dlg = QPrintDialog(printer, self)
        dlg.setWindowTitle(app.caption(_("Print")))
        if dlg.exec_():
            self.webview.print_(printer)
    
    def slotShowContextMenu(self, pos):
        hit = self.webview.page().currentFrame().hitTestContent(pos)
        menu = QMenu()
        if hit.linkUrl().isValid():
            a = self.webview.pageAction(QWebPage.CopyLinkToClipboard)
            a.setIcon(icons.get("edit-copy"))
            a.setText(_("Copy &Link"))
            menu.addAction(a)
            menu.addSeparator()
            a = menu.addAction(icons.get("window-new"), _("Open Link in &New Window"))
            a.triggered.connect((lambda url: lambda: self.slotNewWindow(url))(hit.linkUrl()))
        else:
            if hit.isContentSelected():
                a = self.webview.pageAction(QWebPage.Copy)
                a.setIcon(icons.get("edit-copy"))
                a.setText(_("&Copy"))
                menu.addAction(a)
                menu.addSeparator()
            a = menu.addAction(icons.get("window-new"), _("Open Document in &New Window"))
            a.triggered.connect((lambda url: lambda: self.slotNewWindow(url))(self.webview.url()))
        if menu.actions():
            menu.exec_(self.webview.mapToGlobal(pos))
    
    def slotNewWindow(self, url):
        helpers.openUrl(url)
Example #8
0
class BrowserTab(object):
    """
    An object for controlling a single browser tab (QWebView).

    It is created by splash.pool.Pool. Pool attaches to tab's deferred
    and waits until either a callback or an errback is called, then destroys
    a BrowserTab.
    """
    def __init__(self, network_manager, splash_proxy_factory, verbosity,
                 render_options):
        """ Create a new browser tab. """
        self.deferred = defer.Deferred()
        self.network_manager = network_manager
        self.verbosity = verbosity
        self._uid = render_options.get_uid()
        self._closing = False
        self._default_headers = None
        self._active_timers = set()
        self._timers_to_cancel_on_redirect = weakref.WeakKeyDictionary(
        )  # timer: callback
        self._timers_to_cancel_on_error = weakref.WeakKeyDictionary(
        )  # timer: callback
        self._js_console = None
        self._history = []

        self._init_webpage(verbosity, network_manager, splash_proxy_factory,
                           render_options)
        self._setup_logging(verbosity)

    def _init_webpage(self, verbosity, network_manager, splash_proxy_factory,
                      render_options):
        """ Create and initialize QWebPage and QWebView """
        self.web_page = SplashQWebPage(verbosity)
        self.web_page.setNetworkAccessManager(network_manager)
        self.web_page.splash_proxy_factory = splash_proxy_factory
        self.web_page.render_options = render_options

        self._set_default_webpage_options(self.web_page)
        self._setup_webpage_events()

        self.web_view = QWebView()
        self.web_view.setPage(self.web_page)
        self.web_view.setAttribute(Qt.WA_DeleteOnClose, True)

    def _set_default_webpage_options(self, web_page):
        """
        Set QWebPage options.
        TODO: allow to customize them.
        """
        settings = web_page.settings()
        settings.setAttribute(QWebSettings.JavascriptEnabled, True)
        settings.setAttribute(QWebSettings.PluginsEnabled, False)
        settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
        settings.setAttribute(QWebSettings.LocalStorageEnabled, True)
        settings.setAttribute(QWebSettings.LocalContentCanAccessRemoteUrls,
                              True)
        web_page.mainFrame().setScrollBarPolicy(Qt.Vertical,
                                                Qt.ScrollBarAlwaysOff)
        web_page.mainFrame().setScrollBarPolicy(Qt.Horizontal,
                                                Qt.ScrollBarAlwaysOff)

    def _setup_logging(self, verbosity):
        """ Setup logging of various events """
        self.logger = _BrowserTabLogger(
            uid=self._uid,
            web_page=self.web_page,
            verbosity=verbosity,
        )
        self.logger.enable()

    def _setup_webpage_events(self):
        self._load_finished = WrappedSignal(
            self.web_page.mainFrame().loadFinished)
        self.web_page.mainFrame().loadFinished.connect(self._on_load_finished)
        self.web_page.mainFrame().urlChanged.connect(self._on_url_changed)

    def return_result(self, result):
        """ Return a result to the Pool. """
        if self.result_already_returned():
            self.logger.log("error: result is already returned", min_level=1)

        self.deferred.callback(result)
        # self.deferred = None

    def return_error(self, error=None):
        """ Return an error to the Pool. """
        if self.result_already_returned():
            self.logger.log("error: result is already returned", min_level=1)
        self.deferred.errback(error)
        # self.deferred = None

    def result_already_returned(self):
        """ Return True if an error or a result is already returned to Pool """
        return self.deferred.called

    def set_default_headers(self, headers):
        """ Set default HTTP headers """
        self._default_headers = headers

    def set_images_enabled(self, enabled):
        self.web_page.settings().setAttribute(QWebSettings.AutoLoadImages,
                                              enabled)

    def set_viewport(self, size):
        """
        Set viewport size.
        If size is "full" viewport size is detected automatically.
        If can also be "<width>x<height>".
        """
        if size == 'full':
            size = self.web_page.mainFrame().contentsSize()
            if size.isEmpty():
                self.logger.log("contentsSize method doesn't work %s",
                                min_level=1)
                size = defaults.VIEWPORT_FALLBACK

        if not isinstance(size, QSize):
            w, h = map(int, size.split('x'))
            size = QSize(w, h)

        self.web_page.setViewportSize(size)
        w, h = int(size.width()), int(size.height())
        self.logger.log("viewport size is set to %sx%s" % (w, h), min_level=2)
        return w, h

    @property
    def url(self):
        """ Current URL """
        return unicode(self.web_page.mainFrame().url().toString())

    def go(self,
           url,
           callback,
           errback,
           baseurl=None,
           http_method='GET',
           body=None):
        """
        Go to an URL. This is similar to entering an URL in
        address tab and pressing Enter.
        """
        self.store_har_timing("_onStarted")

        if baseurl:
            # If baseurl is used, we download the page manually,
            # then set its contents to the QWebPage and let it
            # download related resources and render the result.
            if http_method != 'GET':
                raise NotImplementedError()

            request = self._create_request(url)
            request.setOriginatingObject(self.web_page.mainFrame())

            # TODO / FIXME: add support for multiple replies
            # or discard/cancel previous replies
            self._reply = self.network_manager.get(request)

            cb = functools.partial(
                self._on_baseurl_request_finished,
                callback=callback,
                errback=errback,
                baseurl=baseurl,
                url=url,
            )
            self._reply.finished.connect(cb)
        else:
            # if not self._goto_callbacks.isempty():
            #     self.logger.log("Only a single concurrent 'go' request is supported. "
            #                     "Previous go requests will be cancelled.", min_level=1)
            #     # When a new URL is loaded to mainFrame an errback will
            #     # be called, so we're not cancelling this callback manually.

            callback_id = self._load_finished.connect(
                self._on_goto_load_finished,
                callback=callback,
                errback=errback,
            )
            self.logger.log("callback %s is connected to loadFinished" %
                            callback_id,
                            min_level=3)
            self._load_url_to_mainframe(url, http_method, body)

    def stop_loading(self):
        """
        Stop loading of the current page and all pending page
        refresh/redirect requests.
        """
        self.logger.log("stop_loading", min_level=2)
        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()

    def _close(self):
        """ Destroy this tab """
        self._closing = True
        self.web_view.pageAction(QWebPage.StopScheduledPageRefresh)
        self.web_view.stop()
        self.web_view.close()
        self.web_page.deleteLater()
        self.web_view.deleteLater()
        self._cancel_all_timers()

    @skip_if_closing
    def _on_load_finished(self, ok):
        if self.web_page.maybe_redirect(ok):
            self.logger.log("Redirect or other non-fatal error detected",
                            min_level=2)
            return

        if self.web_page.is_ok(ok):  # or maybe_redirect:
            self.logger.log("loadFinished: ok", min_level=2)
        else:
            self._cancel_timers(self._timers_to_cancel_on_error)

            if self.web_page.error_loading(ok):
                self.logger.log("loadFinished: %s" %
                                (str(self.web_page.error_info)),
                                min_level=1)
            else:
                self.logger.log("loadFinished: unknown error", min_level=1)

    def _on_baseurl_request_finished(self, callback, errback, baseurl, url):
        """
        This method is called when ``baseurl`` is used and a
        reply for the first request is received.
        """
        self.logger.log("baseurl_request_finished", min_level=2)

        callback_id = self._load_finished.connect(
            self._on_goto_load_finished,
            callback=callback,
            errback=errback,
        )
        self.logger.log("callback %s is connected to loadFinished" %
                        callback_id,
                        min_level=3)

        baseurl = QUrl(baseurl)
        mimeType = self._reply.header(
            QNetworkRequest.ContentTypeHeader).toString()
        data = self._reply.readAll()
        self.web_page.mainFrame().setContent(data, mimeType, baseurl)
        if self._reply.error():
            self.logger.log("Error loading %s: %s" %
                            (url, self._reply.errorString()),
                            min_level=1)
        self._reply.close()
        self._reply.deleteLater()

    def _load_url_to_mainframe(self, url, http_method, body=None):
        request = self._create_request(url)
        meth = OPERATION_QT_CONSTANTS[http_method]
        if body is None:  # PyQT doesn't support body=None
            self.web_page.mainFrame().load(request, meth)
        else:
            self.web_page.mainFrame().load(request, meth, body)

    def _create_request(self, url):
        request = QNetworkRequest()
        request.setUrl(QUrl(url))
        self._set_request_headers(request, self._default_headers)
        return request

    @skip_if_closing
    def _on_goto_load_finished(self, ok, callback, errback, callback_id):
        """
        This method is called when a QWebPage finishes loading its contents.
        """
        if self.web_page.maybe_redirect(ok):
            # XXX: It assumes loadFinished will be called again because
            # redirect happens. If redirect is detected improperly,
            # loadFinished won't be called again, and Splash will return
            # the result only after a timeout.
            return

        self.logger.log("loadFinished: disconnecting callback %s" %
                        callback_id,
                        min_level=3)
        self._load_finished.disconnect(callback_id)

        if self.web_page.is_ok(ok):
            callback()
        elif self.web_page.error_loading(ok):
            # XXX: maybe return a meaningful error page instead of generic
            # error message?
            errback()
            # errback(RenderError())
        else:
            errback()
            # errback(RenderError())

    def _set_request_headers(self, request, headers):
        """ Set HTTP headers for the request. """
        if isinstance(headers, dict):
            headers = headers.items()

        for name, value in headers or []:
            request.setRawHeader(name, value)
            if name.lower() == 'user-agent':
                self.web_page.custom_user_agent = value

    def wait(self, time_ms, callback, onredirect=None, onerror=None):
        """
        Wait for time_ms, then run callback.

        If onredirect is True then the timer is cancelled if redirect happens.
        If onredirect is callable then in case of redirect the timer is
        cancelled and this callable is called.

        If onerror is True then the timer is cancelled if a render error
        happens. If onerror is callable then in case of a render error the
        timer is cancelled and this callable is called.
        """

        timer = QTimer()
        timer.setSingleShot(True)
        timer_callback = functools.partial(
            self._on_wait_timeout,
            timer=timer,
            callback=callback,
        )
        timer.timeout.connect(timer_callback)

        self.logger.log("waiting %sms; timer %s" % (time_ms, id(timer)),
                        min_level=2)

        timer.start(time_ms)
        self._active_timers.add(timer)
        if onredirect:
            self._timers_to_cancel_on_redirect[timer] = onredirect
        if onerror:
            self._timers_to_cancel_on_error[timer] = onerror

    def _on_wait_timeout(self, timer, callback):
        self.logger.log("wait timeout for %s" % id(timer), min_level=2)
        if timer in self._active_timers:
            self._active_timers.remove(timer)
        self._timers_to_cancel_on_redirect.pop(timer, None)
        self._timers_to_cancel_on_error.pop(timer, None)
        callback()

    def _cancel_timer(self, timer, errback=None):
        self.logger.log("cancelling timer %s" % id(timer), min_level=2)
        if timer in self._active_timers:
            self._active_timers.remove(timer)
        timer.stop()
        if callable(errback):
            self.logger.log("calling timer errback", min_level=2)
            errback()
        timer.deleteLater()

    def _cancel_timers(self, timers):
        for timer, oncancel in list(timers.items()):
            self._cancel_timer(timer, oncancel)
            timers.pop(timer, None)

    def _cancel_all_timers(self):
        self.logger.log("cancelling %d remaining timers" %
                        len(self._active_timers),
                        min_level=2)
        for timer in list(self._active_timers):
            self._cancel_timer(timer)

    def _on_url_changed(self, url):
        # log history
        url = unicode(url.toString())
        cause_ev = self.web_page.har_log._prev_entry(url, -1)
        if cause_ev:
            self._history.append(without_private(cause_ev.data))

        self._cancel_timers(self._timers_to_cancel_on_redirect)

    def inject_js(self, filename):
        """
        Load JS library from file ``filename`` to the current frame.
        """

        # TODO: shouldn't it keep injected scripts after redirects/reloads?
        with open(filename, 'rb') as f:
            script = f.read().decode('utf-8')
            return self.runjs(script)

    def inject_js_libs(self, folder):
        """
        Load all JS libraries from ``folder`` folder to the current frame.
        """
        # TODO: shouldn't it keep injected scripts after redirects/reloads?
        for jsfile in os.listdir(folder):
            if jsfile.endswith('.js'):
                filename = os.path.join(folder, jsfile)
                self.inject_js(filename)

    def runjs(self, js_source):
        """
        Run JS code in page context and return the result.
        Only string results are supported.
        """
        frame = self.web_page.mainFrame()
        res = frame.evaluateJavaScript(js_source)
        return qt2py(res)

    def store_har_timing(self, name):
        self.web_page.har_log.store_timing(name)

    def _jsconsole_enable(self):
        # TODO: add public interface or make console available by default
        if self._js_console is not None:
            return
        self._js_console = _JavascriptConsole()
        frame = self.web_page.mainFrame()
        frame.addToJavaScriptWindowObject('console', self._js_console)

    def _jsconsole_messages(self):
        # TODO: add public interface or make console available by default
        if self._js_console is None:
            return []
        return self._js_console.messages[:]

    def html(self):
        """ Return HTML of the current main frame """
        self.logger.log("getting HTML", min_level=2)
        frame = self.web_page.mainFrame()
        result = bytes(frame.toHtml().toUtf8())
        self.store_har_timing("_onHtmlRendered")
        return result

    def png(self, width=None, height=None, b64=False):
        """ Return screenshot in PNG format """
        self.logger.log("getting PNG", min_level=2)

        image = QImage(self.web_page.viewportSize(), QImage.Format_ARGB32)
        painter = QPainter(image)
        self.web_page.mainFrame().render(painter)
        painter.end()
        self.store_har_timing("_onScreenshotPrepared")

        if width:
            image = image.scaledToWidth(width, Qt.SmoothTransformation)
        if height:
            image = image.copy(0, 0, width, height)
        b = QBuffer()
        image.save(b, "png")
        result = bytes(b.data())
        if b64:
            result = base64.b64encode(result)
        self.store_har_timing("_onPngRendered")
        return result

    def iframes_info(self, children=True, html=True):
        """ Return information about all iframes """
        self.logger.log("getting iframes", min_level=3)
        frame = self.web_page.mainFrame()
        result = self._frame_to_dict(frame, children, html)
        self.store_har_timing("_onIframesRendered")
        return result

    def har(self):
        """ Return HAR information """
        self.logger.log("getting HAR", min_level=3)
        return self.web_page.har_log.todict()

    def history(self):
        """ Return history of 'main' HTTP requests """
        self.logger.log("getting history", min_level=3)
        return copy.deepcopy(self._history)

    def last_http_status(self):
        """
        Return HTTP status code of the currently loaded webpage
        or None if it is not available.
        """
        if not self._history:
            return
        try:
            return self._history[-1]["response"]["status"]
        except KeyError:
            return

    def _frame_to_dict(self, frame, children=True, html=True):
        g = frame.geometry()
        res = {
            "url": unicode(frame.url().toString()),
            "requestedUrl": unicode(frame.requestedUrl().toString()),
            "geometry": (g.x(), g.y(), g.width(), g.height()),
            "title": unicode(frame.title())
        }
        if html:
            res["html"] = unicode(frame.toHtml())

        if children:
            res["childFrames"] = [
                self._frame_to_dict(f, True, html)
                for f in frame.childFrames()
            ]
            res["frameName"] = unicode(frame.frameName())

        return res