Example #1
0
class WebInspector(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        vbox = QVBoxLayout(self)
        self._webInspector = QWebInspector(self)
        vbox.addWidget(self._webInspector)
        self.btnDock = QPushButton(translations.TR_UNDOCK)
        vbox.addWidget(self.btnDock)

        ExplorerContainer.register_tab(translations.TR_TAB_WEB_INSPECTOR, self)
        IDE.register_service('web_inspector', self)

    def refresh_inspector(self):
        self._webInspector.hide()
        self._webInspector.show()

    def set_inspection_page(self, page):
        self._webInspector.setPage(page)
        self._webInspector.setVisible(True)
Example #2
0
class WebInspector(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        vbox = QVBoxLayout(self)
        self._webInspector = QWebInspector(self)
        vbox.addWidget(self._webInspector)
        self.btnDock = QPushButton(translations.TR_UNDOCK)
        vbox.addWidget(self.btnDock)

        ExplorerContainer.register_tab(translations.TR_TAB_WEB_INSPECTOR, self)

    def refresh_inspector(self):
        self._webInspector.hide()
        self._webInspector.show()

    def set_inspection_page(self, page):
        self._webInspector.setPage(page)
        self._webInspector.setVisible(True)
Example #3
0
class WebInspector(QWidget):
    """WebInspector widget class"""
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        vbox = QVBoxLayout(self)
        self._webInspector = QWebInspector(self)
        vbox.addWidget(self._webInspector)
        self.btnDock = QPushButton(translations.TR_UNDOCK)
        vbox.addWidget(self.btnDock)

        ExplorerContainer.register_tab(translations.TR_TAB_WEB_INSPECTOR, self)
        IDE.register_service('web_inspector', self)

    def refresh_inspector(self):
        """Refresh WebInspector widget by hiding and showing"""
        self._webInspector.hide()
        self._webInspector.show()

    def set_inspection_page(self, page):
        """Method to load an argument page object on the WebInspector"""
        self._webInspector.setPage(page)
        self._webInspector.setVisible(True)
Example #4
0
class WebInspector(QWidget):
    """WebInspector widget class"""

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        vbox = QVBoxLayout(self)
        self._webInspector = QWebInspector(self)
        vbox.addWidget(self._webInspector)
        self.btnDock = QPushButton(translations.TR_UNDOCK)
        vbox.addWidget(self.btnDock)

        ExplorerContainer.register_tab(translations.TR_TAB_WEB_INSPECTOR, self)
        IDE.register_service('web_inspector', self)

    def refresh_inspector(self):
        """Refresh WebInspector widget by hiding and showing"""
        self._webInspector.hide()
        self._webInspector.show()

    def set_inspection_page(self, page):
        """Method to load an argument page object on the WebInspector"""
        self._webInspector.setPage(page)
        self._webInspector.setVisible(True)
Example #5
0
class GRobot(object):
    _loop = None
    _liveRobot = 0
    _app = None
    exit_lock = RLock()

    def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING,
                 display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True,
                 cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"),
                 image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True,
                 plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'):
        """GRobot manages a QWebPage.
    
        @param user_agent: The default User-Agent header.
        @param operate_timeout: Operation timeout.
        @param loading_timeout: The page loading timeout.
        @param log_level: The optional logging level.
        @param display: A boolean that tells GRobot to displays UI.
        @param viewport_size: A tupple that sets initial viewport size.
        @param accept_language: Set the webkit accept language. 
        @param ignore_ssl_errors: A boolean that forces ignore ssl errors.
        @param cache_dir: A directory path where to store cache datas.
        @param image_enabled: Enable images.
        @param plugins_enabled: Enable plugins (like Flash).
        @param java_enabled: Enable Java JRE.
        @param javascript_enabled: Enable Javascript.
        @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins'])
        @param develop: Enable the Webkit Inspector.
        @param proxy: Set a Socks5,HTTP{S} Proxy
        @param sleep: Sleep `sleep` second,after operate
        @param jquery_namespace: Set the jQuery namespace.
        """

        GRobot.exit_lock.acquire()
        logger.setLevel(log_level)

        plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ]

        GRobot._liveRobot += 1

        self.develop = develop
        self.inspector = None
        self.plugin = False
        self.exitLoop = False

        self.set_proxy(proxy)

        self.sleep = sleep
        self.jquery_namespace = jquery_namespace
        self.popup_messages = None
        self.accept_language = accept_language

        self._loaded = True

        self._confirm_expected = None
        self._prompt_expected = None
        self._upload_file = None
        self._alert = None

        self.http_resources = []

        self.user_agent = user_agent

        self.loading_timeout = loading_timeout
        self.operate_timeout = operate_timeout

        self.ignore_ssl_errors = ignore_ssl_errors

        if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \
            and not hasattr(GRobot, 'xvfb'):
            try:
                os.environ['DISPLAY'] = ':99'
                GRobot.xvfb = subprocess.Popen(['Xvfb', ':99'])
            except OSError:
                raise Exception('Xvfb is required to a GRobot run oustside ' + \
                                'an X instance')

        self.display = display

        if not GRobot._app:
            GRobot._app = QApplication.instance() or QApplication(['GRobot'])
            if plugin_path:
                for p in plugin_path:
                    GRobot._app.addLibraryPath(p)

        self.page = GRobotWebPage(self, GRobot._app)

        QtWebKit.QWebSettings.setMaximumPagesInCache(0)
        QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0)
        QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True)

        self.page.setForwardUnsupportedContent(True)

        # Page signals
        self.page.loadFinished.connect(self._page_loaded)
        self.page.loadStarted.connect(self._page_load_started)
        self.page.unsupportedContent.connect(self._unsupported_content)

        self.manager = self.page.networkAccessManager()

        #TODO:Think about how to handle the network accessible signal
        #self.manager.networkAccessibleChanged.connect()

        self.manager.finished.connect(self._request_ended)
        self.manager.sslErrors.connect(self._on_manager_ssl_errors)

        # Cache
        self.cache = QNetworkDiskCache()
        self.cache.setCacheDirectory(cache_dir)

        self.manager.setCache(self.cache)

        # Cookie jar
        self.cookie_jar = QNetworkCookieJar()
        self.manager.setCookieJar(self.cookie_jar)

        # User Agent
        self.page.setUserAgent(self.user_agent)

        self.page.networkAccessManager().authenticationRequired \
            .connect(self._authenticate)
        self.page.networkAccessManager().proxyAuthenticationRequired \
            .connect(self._authenticate)

        self.main_frame = self.page.mainFrame()

        self.webview = None

        self.viewport_size = viewport_size

        self.webview = QtWebKit.QWebView()
        self.webview.setPage(self.page)

        self.webview.show() if display else self.webview.hide()

        self.set_viewport_size(*viewport_size)

        self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled)
        self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled)
        self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop)

        self.enable_image = image_enabled
        self.enable_javascript = javascript_enabled

        #always open link in current window instead of new window
        self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.page.linkClicked.connect(self._link_clicked)

        #start the qt main loop
        GRobot._loop = QtMainLoop(GRobot._app)
        GRobot._loop.start()
        GRobot.exit_lock.release()

    @property
    def popup_messages(self):
        return self._popup_messages

    @popup_messages.setter
    def popup_messages(self, value):
        self._popup_messages = unicode(value)


    @property
    def url(self):
        return unicode(self.main_frame.url().toString())


    @property
    def content(self):
        """Returns current frame HTML as a string."""
        return unicode(self.page.currentFrame().toHtml())

    @property
    def cookies(self):
        """Returns all cookies."""
        return self.cookie_jar.allCookies()


    @property
    def enable_image(self):
        """Disable the page images can speed up page loading.

        """
        return self._enable_image

    @enable_image.setter
    def enable_image(self, value):
        self.page.settings().setAttribute(QWebSettings.AutoLoadImages, value)
        self._enable_image = value

    #TODO:It seems not work?
    # @enable_image.deleter
    # def enable_image(self):
    #     raise NotImplemented

    @property
    def enable_javascript(self):
        """Disable the page javascript can speed up page loading.

        """
        return self._enable_javascript

    @enable_javascript.setter
    def enable_javascript(self, value):
        self.page.settings().setAttribute(QWebSettings.JavascriptEnabled, value)
        self._enable_javascript = value


    def open(self, address, method='get', headers=None, auth=None, body=None,
             default_popup_response=None):
        """Opens a web page.

        @param address: The resource URL.
        @param method: The Http method.
        @param headers: An optional dict of extra request hearders.
        @param auth: An optional tupple of HTTP auth (username, password).
        @param body: An optional string containing a payload.
        @param default_popup_response: the default response for any confirm/
        alert/prompt popup from the Javascript (replaces the need for the with
        blocks)
        """

        headers = headers or {}

        body = body or QByteArray()
        try:
            method = getattr(QNetworkAccessManager,
                             "%sOperation" % method.capitalize())
        except AttributeError:
            raise Exception("Invalid http method %s" % method)
        request = QNetworkRequest(QUrl(address))
        request.CacheLoadControl = 0
        for header in headers:
            request.setRawHeader(header, headers[header])
        self._auth = auth
        self._auth_attempt = 0  # Avoids reccursion
        self.page.mainFrame().load(request, method, body)
        self._loaded = False

        if default_popup_response is not None:
            self._prompt_expected = (default_popup_response, None)
            self._confirm_expected = (default_popup_response, None)

        return self.wait_for_page_loaded()

    def set_viewport_size(self, width, height):
        """Sets the page viewport size.

        @param width: An integer that sets width pixel count.
        @param height: An integer that sets height pixel count.
        """

        if self.display:
            self.webview.resize(QSize(width, height))
        self.page.setViewportSize(QSize(width, height))


    def set_proxy(self, proxy=None):
        """Set the proxy or using system configuration as None,supported socks5 http{s}.

        @param proxy: Example:socks5://username:[email protected]:7070
        """
        proxy_type = None
        if proxy:
            parse = urlparse(proxy)
            scheme = parse.scheme
            hostname = parse.hostname
            port = parse.port
            username = parse.username or ''
            password = parse.password or ''

            if scheme == 'socks5':
                proxy_type = QNetworkProxy.Socks5Proxy
            elif scheme in ('http', 'https'):
                proxy_type = QNetworkProxy.HttpProxy

        if proxy_type:
            self.page.networkAccessManager().setProxy(
                QNetworkProxy(proxy_type, hostname, port, username, password)
            )
        else:
            QNetworkProxyFactory.setUseSystemConfiguration(True)

    def first_element_position(self, selector):
        try:
            return self.elements_position(selector)[0]
        except IndexError:
            logger.warning("Can't locate selector " + selector)
            return None


    def elements_position(self, selector):
        """Get the position of elements whose match selector

        @param selector:
        @return: position of QPoint
        """
        attr, pattern, val = self.parser_selector(selector, attr='identifier')

        strip = lambda v: v.strip()

        if pattern:
            val = locals()[pattern](val)


        def identifier(query):
            return id(query) or name(query)

        def name(query):
            return css("*[name='%s']" % query)

        def id(query):
            return css('#' + query)

        def link(query):
            return xpath(u"//a[@text()='%s']" % query.replace("\'", "\\'"))

        def css(query):
            result = []
            for ele in self.main_frame.findAllElements(query):
                if not ele.isNull():
                    result.append(ele.geometry().center())
            return result

        def xpath(query):
            positions = self.evaluate(u"""
            function GetAbsoluteLocationEx(element)
            {
                if ( arguments.length != 1 || element == null )
                {
                    return null;
                }
                var elmt = element;
                var offsetTop = elmt.offsetTop;
                var offsetLeft = elmt.offsetLeft;
                var offsetWidth = elmt.offsetWidth;
                var offsetHeight = elmt.offsetHeight;
                while( elmt = elmt.offsetParent )
                {
                      // add this judge
                    if ( elmt.style.position == 'absolute' || elmt.style.position == 'relative'
                        || ( elmt.style.overflow != 'visible' && elmt.style.overflow != '' ) )
                    {
                        break;
                    }
                    offsetTop += elmt.offsetTop;
                    offsetLeft += elmt.offsetLeft;
                }
                return { absoluteTop: offsetTop, absoluteLeft: offsetLeft,
                    offsetWidth: offsetWidth, offsetHeight: offsetHeight };
            }
            result=[];
            for (var r = document.evaluate('%s', document, null, 5, null), n; n = r.iterateNext();) {
            pos=GetAbsoluteLocationEx(n)
            result.push([pos.absoluteLeft+pos.offsetWidth/2.0,pos.absoluteTop+pos.offsetHeight/2.0]);
            }
            result
            """ % query.replace("\'", "\\'"))

            return map(lambda x: QPoint(*tuple(x)), positions)

        return locals()[attr](val)


    def _move_page_center_to(self, qpoint):
        size = self.page.viewportSize()
        self.main_frame.setScrollPosition(qpoint - QPoint(size.width(), size.height()) / 2)


    def reload(self):
        """Reload page.

        @return:
        """

        self.trigger_action('Reload', expect_loading=True)

    def back(self):
        self.trigger_action('Back')

    def forward(self):
        self.trigger_action('Forward')


    @can_load_page
    def trigger_action(self, action):
        """Trigger QWebPage::WebAction

        @param action:
        """
        self.page.triggerAction(getattr(QWebPage, action))


    def parser_selector(self, selector, attr=None, pattern=None, val=None):
        index = selector.find('=')

        if index <= 0:
            val = selector
        else:
            attr = selector[:index]
            value_ = selector[index + 1:]
            index = value_.find(':')

            if index > 0:
                pattern = value_[:index]

            val = value_[index + 1:]

        return attr, pattern, val


    @can_load_page
    @have_a_break
    def click(self, selector):
        qpoint = self.first_element_position(selector)
        if qpoint:
            return self._click_position(qpoint)


    @can_load_page
    def _click_position(self, qpoint):
        self._move_page_center_to(qpoint)
        self.webview.repaint()
        pos = qpoint - self.main_frame.scrollPosition()

        self._move_to_position(pos)
        QTest.mouseClick(self.webview, Qt.LeftButton, pos=pos)
        gevent.sleep(1)
        return pos


    def qpoint_to_tuple(self, qpoint):
        return qpoint.x(), qpoint.y()

    @have_a_break
    def move_to(self, selector):
        qpoint = self.first_element_position(selector)
        if qpoint:
            self._move_to_position(qpoint)
            return qpoint_to_tuple(qpoint)

    def move_at(self, x, y):
        self._move_to_position(QPoint(x, y))

    def _move_to_position(self, qpoint):
        QTest.mouseMove(self.webview, pos=qpoint)
        return qpoint

    @have_a_break
    def click_at(self, x, y):
        self._click_position(QPoint(x, y))

    @have_a_break
    def key_clicks(self, selector, text):
        if selector:
            self.click(selector)
        QTest.keyClicks(self.webview, text, delay=50)

    @have_a_break
    def type(self, selector, text):
        position = self.click(selector)

        ele = self._hit_element_from(position)

        ele.setFocus()
        ele.evaluateJavaScript(
            u"""
            core.events.setValue(this, '%s')
            """ % (text.replace("\n", "\\n").replace("\'", "\\'"))
        )
        logger.debug('type %s %s' % (selector, text))


    def _hit_element_from(self, position):
        return self.main_frame.hitTestContent(position).element()

    def first_element(self, selector):
        position = self.first_element_position(selector)
        if position:
            return self.main_frame.hitTestContent(position).element(), position


    def wait_forever(self):
        self.wait_for(lambda: False, time_for_stop=-1)

    @have_a_break
    def check(self, selector, checked=True):
        ele, position = self.first_element(selector)
        if ele and ele.tagName() == 'INPUT':
            if ele.attribute('type') in ['checkbox', 'radio']:
                ele_checked = ele.attribute('checked') == 'checked' or False
                if ele_checked != checked:
                    self._click_position(position)
            else:
                raise ValueError, "%s is not a checkbox or radio" % selector

    @have_a_break
    def select(self, selector, value):

        def _select(query, select_by, select):
            select.evaluateJavaScript(u"""
            triggerEvent(this, 'focus', false);
            var changed = false;
            var optionToSelect = '%s';
            for (var i = 0; i < this.options.length; i++) {
                var option = this.options[i];
                if (option.selected && option.%s != optionToSelect) {
                    option.selected = false;
                    changed = true;
                }
                else if (!option.selected && option.%s == optionToSelect) {
                    option.selected = true;
                    changed = true;
                }
            }

            if (changed) {
                triggerEvent(this, 'change', true);
            }
            """ % ( query.replace("\'", "\\'"), select_by, select_by))

        def _add_selection(query, select_by, select, selected):
            select.evaluateJavaScript(u"""
            triggerEvent(this, 'focus', false);
            var optionToSelect = '%s';
            for (var i = 0; i < this.options.length; i++) {
                var option = this.options[i];
                if (option.%s == optionToSelect)
                {
                    option.selected = %s;
                    triggerEvent(this, 'change', true);
                }

            }
            """ % ( query.replace("\'", "\\'"), select_by, selected and 'true' or 'false'))

        ele, position = self.first_element(selector)

        if ele and ele.tagName() == 'SELECT':
            ele.setFocus()

            if ele.attribute('multiple') == 'multiple':
                assert isinstance(value, list)
                for value_, selected in value:
                    attr, pattern, val = self.parser_selector(value_, attr='text')
                    _add_selection(val, attr, ele, selected)
            else:
                attr, pattern, val = self.parser_selector(value, attr='text')
                _select(val, attr, ele)


    def choose_file(self, selector, file):
        self._upload_file = file
        self.click(selector)
        self._upload_file = None


    def capture(self, selector=None):
        """Capture the images of selector.

        @param selector: Css selector.
        @return: Images
        """

        elements = self.main_frame.documentElement().findAll(selector)
        imgs = []

        for element in elements:
            geo = element.geometry()
            img = QImage(geo.width(), geo.height(), QImage.Format_ARGB32)
            painter = QPainter(img)
            element.render(painter)
            painter.end()
            imgs.append(img)

        return imgs

    def capture_to(self, path, selector=None):
        """Capture the images of selector to files.

        @param path: File path with index suffix.
        @param selector: Css selector.
        @return: The paths of saving.
        """

        _, ext = os.path.splitext(path)
        ext = ext[1:]

        imgs = self.capture(selector)
        result = []
        for index, img in enumerate(imgs):
            filepath = '%s.%s' % (path, index)
            if img.save(filepath, ext.upper()):
                result.append(filepath)

        return result

    def capture_to_buf(self, selector=None):
        """capture the images of selector to StringIO

        @param selector: Css selector.
        @return: The StringIO list.
        """

        images = self.capture(selector)
        result = []

        for image in images:
            ba = QByteArray()
            buf = QBuffer(ba)
            buf.open(QIODevice.ReadWrite)
            image.save(buf, 'jpg')
            stream = StringIO(str(buf.buffer()))
            result.append(stream)

        return result


    @can_load_page
    def evaluate(self, script):
        """Evaluates script in page frame.

        @param script: The script to evaluate.
        """
        result = self.main_frame.evaluateJavaScript("%s" % script)
        # if isinstance(result,QString):
        #     result=unicode(result)
        return result

    def evaluate_js_file(self, path, encoding='utf-8'):
        """Evaluates javascript file at given path in current frame.
        Raises native IOException in case of invalid file.

        @param path: The path of the file.
        @param encoding: The file's encoding.
        """
        self.evaluate(codecs.open(path, encoding=encoding).read())

    def __del__(self):
        """Depend on the CG of Python.
        """
        self._exit()


    def delete_cookies(self):
        """Deletes all cookies."""
        self.cookie_jar.setAllCookies([])

    def exists(self, selector):
        """Checks if element exists for given selector.

        @param string: The element selector.
        """
        return not self.main_frame.findFirstElement(selector).isNull()


        #TODO: Still not work.

    #     def remove_css(self):
    #         """Remore the css,speed up page loading.
    #
    #         @return:
    #         """
    #
    #         return self.evaluate("""var targetelement="link";//determine element type to create nodelist from
    # var targetattr="href"//determine corresponding attribute to test for
    # var allsuspects=document.getElementsByTagName(targetelement)
    # for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove
    # if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null )
    # allsuspects[i].parentNode.removeChild(allsuspects[i]); //remove element by calling parentNode.removeChild()
    # }
    #         """)


    def filter_resources(self, pattern):
        """Filter resources with pattern.

        @param pattern: Match pattern.
        @param resources:
        @return: @raise:
        """
        if isinstance(pattern, basestring):
            is_match = lambda x: pattern == x
        elif isinstance(pattern, _pattern_type):
            is_match = lambda x: pattern.match(x)
        elif hasattr(pattern, '__call__'):
            is_match = pattern
        else:
            raise TypeError, 'pattern must be one of str,re.compile,callable'
        return filter(lambda x: is_match(x.request_url), self.http_resources)[:]


    def save(self, path):
        """Save current page content to the path.
        
        @param path: The path to save.
        """
        f = open(path, 'w')
        f.write(self.content.encode('utf-8'))
        f.close()

    def global_exists(self, global_name):
        """Checks if javascript global exists.

        @param global_name: The name of the global.
        """
        return self.evaluate('!(typeof %s === "undefined");' %
                             global_name)


    def load_cookies( self, cookie_storage, keep_old=False ):
        """load from cookielib's CookieJar or Set-Cookie3 format text file.

        @param cookie_storage: file location string on disk or CookieJar instance.
        @param keep_old: Don't reset, keep cookies not overridden.
        """

        def toQtCookieJar( PyCookieJar, QtCookieJar ):
            allCookies = QtCookieJar.cookies if keep_old else []
            for pc in PyCookieJar:
                qc = toQtCookie(pc)
                allCookies.append(qc)
            QtCookieJar.setAllCookies(allCookies)

        def toQtCookie(PyCookie):
            qc = QNetworkCookie(PyCookie.name, PyCookie.value)
            qc.setSecure(PyCookie.secure)
            if PyCookie.path_specified:
                qc.setPath(PyCookie.path)
            if PyCookie.domain != "":
                qc.setDomain(PyCookie.domain)
            if PyCookie.expires != 0:
                t = QDateTime()
                t.setTime_t(PyCookie.expires)
                qc.setExpirationDate(t)
                # not yet handled(maybe less useful):
            #   py cookie.rest / QNetworkCookie.setHttpOnly()
            return qc

        if cookie_storage.__class__.__name__ == 'str':
            cj = LWPCookieJar(cookie_storage)
            cj.load()
            toQtCookieJar(cj, self.cookie_jar)
        elif cookie_storage.__class__.__name__.endswith('CookieJar'):
            toQtCookieJar(cookie_storage, self.cookie_jar)
        else:
            raise ValueError, 'unsupported cookie_storage type.'


    def save_cookies(self, cookie_storage):
        """Save to cookielib's CookieJar or Set-Cookie3 format text file.

        @param cookie_storage: file location string or CookieJar instance.
        """

        def toPyCookieJar(QtCookieJar, PyCookieJar):
            for c in QtCookieJar.allCookies():
                PyCookieJar.set_cookie(toPyCookie(c))

        def toPyCookie(QtCookie):
            port = None
            port_specified = False
            secure = QtCookie.isSecure()
            name = str(QtCookie.name())
            value = str(QtCookie.value())
            v = str(QtCookie.path())
            path_specified = bool(v != "")
            path = v if path_specified else None
            v = str(QtCookie.domain())
            domain_specified = bool(v != "")
            domain = v
            domain_initial_dot = v.startswith('.') if domain_specified else None
            v = long(QtCookie.expirationDate().toTime_t())
            # Long type boundary on 32bit platfroms; avoid ValueError
            expires = 2147483647 if v > 2147483647 else v
            rest = {}
            discard = False
            return Cookie(0, name, value, port, port_specified, domain
                , domain_specified, domain_initial_dot, path, path_specified
                , secure, expires, discard, None, None, rest)

        if cookie_storage.__class__.__name__ == 'str':
            cj = LWPCookieJar(cookie_storage)
            toPyCookieJar(self.cookie_jar, cj)
            cj.save()
        elif cookie_storage.__class__.__name__.endswith('CookieJar'):
            toPyCookieJar(self.cookie_jar, cookie_storage)
        else:
            raise ValueError, 'unsupported cookie_storage type.'


    def wait_for_confirm(self, confirm=True, callback=None):
        """Statement that tells GRobot how to deal with javascript confirm().

        @param confirm: A bollean that confirm.
        @param callable: A callable that returns a boolean for confirmation.
        """

        self._robot._confirm_expected = (confirm, callback)
        self._robot.wait_for(lambda: self._robot._confirm_expected is None)
        return self.popup_messages


    def wait_for_text(self, text, time_for_stop=None):
        """Waits until given text appear on main frame.

        @param text: The text to wait for.
        @return:
        """

        logger.debug("Wait for text %s" % text)

        self.wait_for(lambda: text in self.content,
                      "Can\'t find '%s' in current frame" % text, time_for_stop=time_for_stop)

        return self.wait_for_page_loaded()

    def wait_for_xpath(self, expression, time_for_stop=None):
        self.wait_for(lambda: XPath(self.content).execute(expression),
                      "Can't find xpath=%s in current frame" % expression, time_for_stop=time_for_stop)
        return self.wait_for_page_loaded()


    def wait_for_selector(self, selector):
        """Waits until selector match an element on the frame.

        @param selector: The selector to wait for.
        """
        self.wait_for(lambda: self.exists(selector),
                      'Can\'t find element matching "%s"' % selector)

    def wait_for_page_loaded(self, time_for_stop=None):
        """Waits until page is loaded, assumed that a page as been requested.

        """
        return self.wait_for(lambda: self._loaded,
                             'Unable to load requested page', time_for_stop=time_for_stop)

    def wait_for(self, condition, timeout_message='', time_for_stop=None):
        """Waits until condition is True.

        @param condition: A callable that returns the condition.
        @param timeout_message: The exception message on timeout.-1 means never timeout.
        """

        if self._loaded:
            time_for_stop = time_for_stop or self.operate_timeout
        else:
            time_for_stop = time_for_stop or self.loading_timeout

        started_at = time.time()
        while not condition():
            if time_for_stop != -1 and time.time() > (started_at + time_for_stop):
                if self._loaded:
                    raise OperateTimeout, timeout_message
                else:
                    # raise LoadingTimeout, timeout_message
                    self.trigger_action('Stop') #QWebPage::Stop
                    self._loaded = True
                    logger.warning("Page loading timeout.Force to stop the page")
                    break

            gevent.sleep(2)

    def wait_for_alert(self):
        """Waits for main frame alert().
        """
        self.wait_for(lambda: self._alert is not None,
                      'User has not been alerted.')
        msg, self._alert = self._alert, None
        return msg

    def _release_last_resources(self):
        """Releases last loaded resources.

        :return: The released resources.
        """
        last_resources, self.http_resources = self.http_resources[:], []
        return last_resources


    def _page_loaded(self, success):
        if self.develop and self.display:
            if self.inspector is None:
                self.inspector = QWebInspector()

            self.inspector.setPage(self.page)
            self.inspector.show()

        scripts = [
            'atoms.js',
            'htmlutils.js',
        ]

        if self.jquery_namespace:
            scripts.append('jquery-1.9.1.min.js', )

        for script in scripts:
            self.evaluate_js_file(os.path.dirname(__file__) + '/javascripts/' + script)

        if self.jquery_namespace:
            self.evaluate(u"%s=jQuery.noConflict();" % self.jquery_namespace)

        self._loaded = True
        # self.cache.clear()
        logger.debug("Page load finished")

    def _page_load_started(self):
        logger.debug("Start load page")

        self._loaded = False

    def _unsupported_content(self, reply):
        """Adds an HttpResource object to http_resources with unsupported
        content.

        @param reply: The QNetworkReply object.
        """

        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute):
            self.http_resources.append(HttpResource(reply, self.cache,
                                                    reply.readAll()))

    def _link_clicked(self, href):
        """Contorl the page link clicked event,forbid open new window.

        @param href: The href attribute of a tag.
        """

        self.main_frame.load(href)

    def _request_ended(self, reply):
        """Adds an HttpResource object to http_resources.

        @param reply: The QNetworkReply object.
        """

        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute):
            self.http_resources.append(HttpResource(reply, self.cache))

    def _authenticate(self, mix, authenticator):
        """Called back on basic / proxy http auth.

        @param mix: The QNetworkReply or QNetworkProxy object.
        @param authenticator: The QAuthenticator object.
        """
        if self._auth_attempt == 0:
            username, password = self._auth
            authenticator.setUser(username)
            authenticator.setPassword(password)
            self._auth_attempt += 1

    def _on_manager_ssl_errors(self, reply, errors):
        """Ingore all the ssl error

        @param reply:
        @param errors:
        """
        url = unicode(reply.url().toString())
        if self.ignore_ssl_errors:
            reply.ignoreSslErrors()
        else:
            logger.warning('SSL certificate error: %s' % url)


    def _exit(self):
        """Destroy the Qt main event loop.

        """
        GRobot.exit_lock.acquire()
        if self.inspector:
            self.inspector.close()
            sip.delete(self.inspector)

        if self.display:
            self.webview.close()
            sip.delete(self.webview)

        if self.page and not sip.isdeleted(self.page):
            sip.delete(self.page)

        GRobot._liveRobot -= 1

        if GRobot._liveRobot == 0 and GRobot._loop is not None:

            GRobot._loop.stop()
            GRobot._loop = None
            GRobot._app = None
            if hasattr(self, 'xvfb'):
                GRobot.xvfb.terminate()
        GRobot.exit_lock.release()
Example #6
0
class GRobot(object):
    _loop = None
    _liveRobot = 0
    _app = None
    _kill_loop=None
    exit_lock = RLock()

    def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING,
                 display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True,
                 cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"),
                 image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True,
                 plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'):
        """GRobot manages a QWebPage.
    
        @param user_agent: The default User-Agent header.
        @param operate_timeout: Operation timeout.
        @param loading_timeout: The page loading timeout.
        @param log_level: The optional logging level.
        @param display: A boolean that tells GRobot to displays UI.
        @param viewport_size: A tupple that sets initial viewport size.
        @param accept_language: Set the webkit accept language. 
        @param ignore_ssl_errors: A boolean that forces ignore ssl errors.
        @param cache_dir: A directory path where to store cache datas.
        @param image_enabled: Enable images.
        @param plugins_enabled: Enable plugins (like Flash).
        @param java_enabled: Enable Java JRE.
        @param javascript_enabled: Enable Javascript.
        @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins'])
        @param develop: Enable the Webkit Inspector.
        @param proxy: Set a Socks5,HTTP{S} Proxy
        @param sleep: Sleep `sleep` second,after operate
        @param jquery_namespace: Set the jQuery namespace.
        """

        GRobot.exit_lock.acquire()

        if GRobot._kill_loop:
            gevent.kill(GRobot._kill_loop)
            GRobot._kill_loop=None

        logger.setLevel(log_level)

        plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ]

        GRobot._liveRobot += 1

        self.develop = develop
        self.inspector = None
        self.plugin = False
        self.exitLoop = False
        self._deleted = False

        self.set_proxy(proxy)

        self.sleep = sleep
        self.jquery_namespace = jquery_namespace
        self.popup_messages = None
        self.accept_language = accept_language

        self._loaded = True

        self._confirm_expected = None
        self._prompt_expected = None
        self._upload_file = None
        self._alert = None

        self.http_resources = []

        self.user_agent = user_agent

        self.loading_timeout = loading_timeout
        self.operate_timeout = operate_timeout

        self.ignore_ssl_errors = ignore_ssl_errors

        if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \
            and not hasattr(GRobot, 'xvfb'):
            try:
                os.environ['DISPLAY'] = ':99'
                GRobot.xvfb = subprocess.Popen(['Xvfb', ':99'])
            except OSError:
                raise Exception('Xvfb is required to a GRobot run oustside ' + \
                                'an X instance')

        self.display = display

        if not GRobot._app:
            GRobot._app = QApplication.instance() or QApplication(['GRobot'])
            if plugin_path:
                for p in plugin_path:
                    GRobot._app.addLibraryPath(p)

        self.page = GRobotWebPage(self, GRobot._app)

        QtWebKit.QWebSettings.setMaximumPagesInCache(0)
        QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0)
        QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True)

        self.page.setForwardUnsupportedContent(True)

        # Page signals
        self.page.loadFinished.connect(self._page_loaded)
        self.page.loadStarted.connect(self._page_load_started)
        self.page.unsupportedContent.connect(self._unsupported_content)

        self.manager = self.page.networkAccessManager()

        #TODO:Think about how to handle the network accessible signal
        #self.manager.networkAccessibleChanged.connect()

        self.manager.finished.connect(self._request_ended)
        self.manager.sslErrors.connect(self._on_manager_ssl_errors)

        # Cache
        self.cache = QNetworkDiskCache()
        self.cache.setCacheDirectory(cache_dir)

        self.manager.setCache(self.cache)

        # Cookie jar
        self.cookie_jar = QNetworkCookieJar()
        self.manager.setCookieJar(self.cookie_jar)

        # User Agent
        self.page.setUserAgent(self.user_agent)

        self.page.networkAccessManager().authenticationRequired \
            .connect(self._authenticate)
        self.page.networkAccessManager().proxyAuthenticationRequired \
            .connect(self._authenticate)

        self.main_frame = self.page.mainFrame()

        self.webview = None

        self.viewport_size = viewport_size

        self.webview = QtWebKit.QWebView()
        self.webview.setPage(self.page)

        self.webview.show() if display else self.webview.hide()

        self.set_viewport_size(*viewport_size)

        self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled)
        self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled)
        self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop)

        self.enable_image = image_enabled
        self.enable_javascript = javascript_enabled

        #always open link in current window instead of new window
        self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.page.linkClicked.connect(self._link_clicked)

        #start the qt main loop
        GRobot._loop = QtMainLoop(GRobot._app)
        GRobot._loop.start()
        GRobot.exit_lock.release()

    @property
    def popup_messages(self):
        return self._popup_messages

    @popup_messages.setter
    def popup_messages(self, value):
        self._popup_messages = str(value)


    @property
    def url(self):
        return str(self.main_frame.url().toString())



    def content(self):
        """Returns current frame HTML as a string."""
        return str(self.main_frame.toHtml())

    @property
    def cookies(self):
        """Returns all cookies."""
        return self.cookie_jar.allCookies()


    @property
    def enable_image(self):
        """Disable the page images can speed up page loading.

        """
        return self._enable_image

    @enable_image.setter
    def enable_image(self, value):
        self.page.settings().setAttribute(QWebSettings.AutoLoadImages, value)
        self._enable_image = value

    #TODO:It seems not work?
    # @enable_image.deleter
    # def enable_image(self):
    #     raise NotImplemented

    @property
    def enable_javascript(self):
        """Disable the page javascript can speed up page loading.

        """
        return self._enable_javascript

    @enable_javascript.setter
    def enable_javascript(self, value):
        self.page.settings().setAttribute(QWebSettings.JavascriptEnabled, value)
        self._enable_javascript = value


    def open(self, address, method='get', headers=None, auth=None, body=None,
             default_popup_response=None):
        """Opens a web page.

        @param address: The resource URL.
        @param method: The Http method.
        @param headers: An optional dict of extra request hearders.
        @param auth: An optional tupple of HTTP auth (username, password).
        @param body: An optional string containing a payload.
        @param default_popup_response: the default response for any confirm/
        alert/prompt popup from the Javascript (replaces the need for the with
        blocks)
        """

        headers = headers or {}

        body = body or QByteArray()
        try:
            method = getattr(QNetworkAccessManager,
                             "%sOperation" % method.capitalize())
        except AttributeError:
            raise Exception("Invalid http method %s" % method)
        request = QNetworkRequest(QUrl(address))
        request.CacheLoadControl = 0
        for header in headers:
            request.setRawHeader(header, headers[header])
        self._auth = auth
        self._auth_attempt = 0  # Avoids reccursion
        self.page.mainFrame().load(request, method, body)
        self._loaded = False

        if default_popup_response is not None:
            self._prompt_expected = (default_popup_response, None)
            self._confirm_expected = (default_popup_response, None)

        return self.wait_for_page_loaded()

    def set_viewport_size(self, width, height):
        """Sets the page viewport size.

        @param width: An integer that sets width pixel count.
        @param height: An integer that sets height pixel count.
        """

        if self.display:
            self.webview.resize(QSize(width, height))
        self.page.setViewportSize(QSize(width, height))


    def set_proxy(self, proxy=None):
        """Set the proxy or using system configuration as None,supported socks5 http{s}.

        @param proxy: Example:socks5://username:[email protected]:7070
        """
        proxy_type = None
        if proxy:
            parse = urlparse(proxy)
            scheme = parse.scheme
            hostname = parse.hostname
            port = parse.port
            username = parse.username or ''
            password = parse.password or ''

            if scheme == 'socks5':
                proxy_type = QNetworkProxy.Socks5Proxy
            elif scheme in ('http', 'https'):
                proxy_type = QNetworkProxy.HttpProxy

        if proxy_type:
            self.page.networkAccessManager().setProxy(
                QNetworkProxy(proxy_type, hostname, port, username, password)
            )
        else:
            QNetworkProxyFactory.setUseSystemConfiguration(True)

    def first_element_position(self, selector):
        try:
            return self.elements_position(selector)[0]
        except IndexError:
            logger.warning("Can't locate selector " + selector)
            return None


    def elements_position(self, selector):
        """Get the position of elements whose match selector

        @param selector:
        @return: position of QPoint
        """
        attr, pattern, val = self.parser_selector(selector, attr='identifier')

        strip = lambda v: v.strip()

        if pattern:
            val = locals()[pattern](val)


        def identifier(query):
            return id(query) or name(query)

        def name(query):
            return css("*[name='%s']" % query)

        def id(query):
            return css('#' + query)

        def link(query):
            return xpath("//a[text()='%s']" % query.replace("\'", "\\'"))

        def css(query):
            result = []
            for ele in self.main_frame.findAllElements(query):
                if not ele.isNull():
                    result.append(ele.geometry().center())
            return result

        def xpath(query):
            positions = self.evaluate("""
            function GetAbsoluteLocationEx(element)
            {
                if ( arguments.length != 1 || element == null )
                {
                    return null;
                }
                var elmt = element;
                var offsetTop = elmt.offsetTop;
                var offsetLeft = elmt.offsetLeft;
                var offsetWidth = elmt.offsetWidth;
                var offsetHeight = elmt.offsetHeight;
                while( elmt = elmt.offsetParent )
                {
                      // add this judge
                    if ( elmt.style.position == 'absolute' || elmt.style.position == 'relative'
                        || ( elmt.style.overflow != 'visible' && elmt.style.overflow != '' ) )
                    {
                        break;
                    }
                    offsetTop += elmt.offsetTop;
                    offsetLeft += elmt.offsetLeft;
                }
                return { absoluteTop: offsetTop, absoluteLeft: offsetLeft,
                    offsetWidth: offsetWidth, offsetHeight: offsetHeight };
            }
            result=[];
            for (var r = document.evaluate('%s', document, null, 5, null), n; n = r.iterateNext();) {
            pos=GetAbsoluteLocationEx(n)
            result.push([pos.absoluteLeft+pos.offsetWidth/2.0,pos.absoluteTop+pos.offsetHeight/2.0]);
            }
            result
            """ % query.replace("\'", "\\'"))

            return [QPoint(*tuple(x)) for x in positions]

        return locals()[attr](val)


    def _move_page_center_to(self, qpoint):
        size = self.page.viewportSize()
        self.main_frame.setScrollPosition(qpoint - QPoint(size.width(), size.height()) / 2)


    def reload(self):
        """Reload page.

        @return:
        """

        self.trigger_action('Reload', expect_loading=True)

    def back(self):
        self.trigger_action('Back')

    def forward(self):
        self.trigger_action('Forward')


    @can_load_page
    def trigger_action(self, action):
        """Trigger QWebPage::WebAction

        @param action:
        """
        self.page.triggerAction(getattr(QWebPage, action))


    def parser_selector(self, selector, attr=None, pattern=None, val=None):
        index = selector.find('=')

        if index <= 0:
            val = selector
        else:
            attr = selector[:index]
            value_ = selector[index + 1:]
            index = value_.find(':')

            if index > 0:
                pattern = value_[:index]

            val = value_[index + 1:]

        return attr, pattern, val


    @can_load_page
    @have_a_break
    def click(self, selector):
        qpoint = self.first_element_position(selector)
        if qpoint:
            return self.qpoint_to_tuple(self._click_position(qpoint))

    @can_load_page
    @have_a_break
    def test(self):
        return self.qpoint_to_tuple(QPoint(1, 2))


    @can_load_page
    def _click_position(self, qpoint):
        self._move_page_center_to(qpoint)
        self.webview.repaint()
        pos = qpoint - self.main_frame.scrollPosition()

        self._move_to_position(pos)
        QTest.mouseClick(self.webview, Qt.LeftButton, pos=pos)
        gevent.sleep(1)
        return pos


    def qpoint_to_tuple(self, qpoint):
        return qpoint.x(), qpoint.y()

    @have_a_break
    def move_to(self, selector):
        qpoint = self.first_element_position(selector)
        if qpoint:
            self._move_to_position(qpoint)
            return qpoint_to_tuple(qpoint)

    def move_at(self, x, y):
        return self._move_to_position(QPoint(x, y))

    def _move_to_position(self, qpoint):
        QTest.mouseMove(self.webview, pos=qpoint)
        return qpoint

    @can_load_page
    @have_a_break
    def click_at(self, x, y):
        return self._click_position(QPoint(x, y))


    @have_a_break
    def key_clicks(self, selector, text):
        if selector:
            self.click(selector)
        QTest.keyClicks(self.webview, text, delay=50)

    @have_a_break
    def type(self, selector, text):
        position = self.click(selector)

        ele = self._hit_element_from(QPoint(*position))

        ele.setFocus()
        ele.evaluateJavaScript(
            """
            core.events.setValue(this, '%s')
            """ % (text.replace("\n", "\\n").replace("\'", "\\'"))
        )
        logger.debug('type %s %s' % (selector, text))


    def _hit_element_from(self, position):
        return self.main_frame.hitTestContent(position).element()

    def first_element(self, selector):
        position = self.first_element_position(selector)
        if position:
            return self.main_frame.hitTestContent(position).element(), position


    def wait_forever(self):
        self.wait_for(lambda: False, time_for_stop=-1)

    @have_a_break
    def check(self, selector, checked=True):
        ele, position = self.first_element(selector)
        if ele and ele.tagName() == 'INPUT':
            if ele.attribute('type') in ['checkbox', 'radio']:
                ele_checked = ele.attribute('checked') == 'checked' or False
                if ele_checked != checked:
                    self._click_position(position)
            else:
                raise ValueError("%s is not a checkbox or radio" % selector)

    @have_a_break
    def select(self, selector, value):

        def _select(query, select_by, select):
            select.evaluateJavaScript("""
            triggerEvent(this, 'focus', false);
            var changed = false;
            var optionToSelect = '%s';
            for (var i = 0; i < this.options.length; i++) {
                var option = this.options[i];
                if (option.selected && option.%s != optionToSelect) {
                    option.selected = false;
                    changed = true;
                }
                else if (!option.selected && option.%s == optionToSelect) {
                    option.selected = true;
                    changed = true;
                }
            }

            if (changed) {
                triggerEvent(this, 'change', true);
            }
            """ % ( query.replace("\'", "\\'"), select_by, select_by))

        def _add_selection(query, select_by, select, selected):
            select.evaluateJavaScript("""
            triggerEvent(this, 'focus', false);
            var optionToSelect = '%s';
            for (var i = 0; i < this.options.length; i++) {
                var option = this.options[i];
                if (option.%s == optionToSelect)
                {
                    option.selected = %s;
                    triggerEvent(this, 'change', true);
                }

            }
            """ % ( query.replace("\'", "\\'"), select_by, selected and 'true' or 'false'))

        ele, position = self.first_element(selector)

        if ele and ele.tagName() == 'SELECT':
            ele.setFocus()

            if ele.attribute('multiple') == 'multiple':
                assert isinstance(value, list)
                for value_, selected in value:
                    attr, pattern, val = self.parser_selector(value_, attr='text')
                    _add_selection(val, attr, ele, selected)
            else:
                attr, pattern, val = self.parser_selector(value, attr='text')
                _select(val, attr, ele)


    def choose_file(self, selector, file):
        self._upload_file = file
        self.click(selector)
        self._upload_file = None


    def capture(self, selector=None):
        """Capture the images of selector.

        @param selector: Css selector.
        @return: Images
        """

        elements = self.main_frame.documentElement().findAll(selector)
        imgs = []

        for element in elements:
            geo = element.geometry()
            img = QImage(geo.width(), geo.height(), QImage.Format_ARGB32)
            painter = QPainter(img)
            element.render(painter)
            painter.end()
            imgs.append(img)

        return imgs

    def capture_to(self, path, selector=None):
        """Capture the images of selector to files.

        @param path: File path with index suffix.
        @param selector: Css selector.
        @return: The paths of saving.
        """

        _, ext = os.path.splitext(path)
        ext = ext[1:]

        imgs = self.capture(selector)
        result = []
        for index, img in enumerate(imgs):
            filepath = '%s.%s' % (path, index)
            if img.save(filepath, ext.upper()):
                result.append(filepath)

        return result

    def capture_to_buf(self, selector=None):
        """capture the images of selector to StringIO

        @param selector: Css selector.
        @return: The StringIO list.
        """

        images = self.capture(selector)
        result = []

        for image in images:
            ba = QByteArray()
            buf = QBuffer(ba)
            buf.open(QIODevice.ReadWrite)
            image.save(buf, 'jpg')
            stream = StringIO(str(buf.buffer()))
            result.append(stream)

        return result


    @can_load_page
    def evaluate(self, script):
        """Evaluates script in page frame.

        @param script: The script to evaluate.
        """
        result = self.main_frame.evaluateJavaScript("%s" % script)
        # if isinstance(result,QString):
        #     result=unicode(result)
        return result

    def evaluate_js_file(self, path, encoding='utf-8'):
        """Evaluates javascript file at given path in current frame.
        Raises native IOException in case of invalid file.

        @param path: The path of the file.
        @param encoding: The file's encoding.
        """
        self.evaluate(codecs.open(path, encoding=encoding).read())

    def __del__(self):
        """Depend on the CG of Python.
        """
        self.exit()


    def delete_cookies(self):
        """Deletes all cookies."""
        self.cookie_jar.setAllCookies([])

    def exists(self, selector):
        """Checks if element exists for given selector.

        @param string: The element selector.
        """
        return not self.main_frame.findFirstElement(selector).isNull()


        #TODO: Still not work.

    #     def remove_css(self):
    #         """Remore the css,speed up page loading.
    #
    #         @return:
    #         """
    #
    #         return self.evaluate("""var targetelement="link";//determine element type to create nodelist from
    # var targetattr="href"//determine corresponding attribute to test for
    # var allsuspects=document.getElementsByTagName(targetelement)
    # for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove
    # if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null )
    # allsuspects[i].parentNode.removeChild(allsuspects[i]); //remove element by calling parentNode.removeChild()
    # }
    #         """)


    def filter_resources(self, pattern):
        """Filter resources with pattern.

        @param pattern: Match pattern.
        @param resources:
        @return: @raise:
        """
        if isinstance(pattern, str):
            is_match = lambda x: pattern == x
        elif isinstance(pattern, _pattern_type):
            is_match = lambda x: pattern.match(x)
        elif hasattr(pattern, '__call__'):
            is_match = pattern
        else:
            raise TypeError('pattern must be one of str,re.compile,callable')
        return filter(lambda x: is_match(x.request_url), self.http_resources)[:]


    def save(self, path):
        """Save current page content to the path.
        
        @param path: The path to save.
        """
        f = open(path, 'w')
        f.write(self.content().encode('utf-8'))
        f.close()

    def global_exists(self, global_name):
        """Checks if javascript global exists.

        @param global_name: The name of the global.
        """
        return self.evaluate('!(typeof %s === "undefined");' %
                             global_name)


    def load_cookies( self, cookie_storage, keep_old=False ):
        """load from cookielib's CookieJar or Set-Cookie3 format text file.

        @param cookie_storage: file location string on disk or CookieJar instance.
        @param keep_old: Don't reset, keep cookies not overridden.
        """

        def toQtCookieJar( PyCookieJar, QtCookieJar ):
            allCookies = QtCookieJar.cookies if keep_old else []
            for pc in PyCookieJar:
                qc = toQtCookie(pc)
                allCookies.append(qc)
            QtCookieJar.setAllCookies(allCookies)

        def toQtCookie(PyCookie):
            qc = QNetworkCookie(PyCookie.name, PyCookie.value)
            qc.setSecure(PyCookie.secure)
            if PyCookie.path_specified:
                qc.setPath(PyCookie.path)
            if PyCookie.domain != "":
                qc.setDomain(PyCookie.domain)
            if PyCookie.expires != 0:
                t = QDateTime()
                t.setTime_t(PyCookie.expires)
                qc.setExpirationDate(t)
                # not yet handled(maybe less useful):
            #   py cookie.rest / QNetworkCookie.setHttpOnly()
            return qc

        if cookie_storage.__class__.__name__ == 'str':
            cj = LWPCookieJar(cookie_storage)
            cj.load()
            toQtCookieJar(cj, self.cookie_jar)
        elif cookie_storage.__class__.__name__.endswith('CookieJar'):
            toQtCookieJar(cookie_storage, self.cookie_jar)
        else:
            raise ValueError('unsupported cookie_storage type.')


    def save_cookies(self, cookie_storage):
        """Save to cookielib's CookieJar or Set-Cookie3 format text file.

        @param cookie_storage: file location string or CookieJar instance.
        """

        def toPyCookieJar(QtCookieJar, PyCookieJar):
            for c in QtCookieJar.allCookies():
                PyCookieJar.set_cookie(toPyCookie(c))

        def toPyCookie(QtCookie):
            port = None
            port_specified = False
            secure = QtCookie.isSecure()
            name = str(QtCookie.name())
            value = str(QtCookie.value())
            v = str(QtCookie.path())
            path_specified = bool(v != "")
            path = v if path_specified else None
            v = str(QtCookie.domain())
            domain_specified = bool(v != "")
            domain = v
            domain_initial_dot = v.startswith('.') if domain_specified else None
            v = int(QtCookie.expirationDate().toTime_t())
            # Long type boundary on 32bit platfroms; avoid ValueError
            expires = 2147483647 if v > 2147483647 else v
            rest = {}
            discard = False
            return Cookie(0, name, value, port, port_specified, domain
                , domain_specified, domain_initial_dot, path, path_specified
                , secure, expires, discard, None, None, rest)

        if cookie_storage.__class__.__name__ == 'str':
            cj = LWPCookieJar(cookie_storage)
            toPyCookieJar(self.cookie_jar, cj)
            cj.save()
        elif cookie_storage.__class__.__name__.endswith('CookieJar'):
            toPyCookieJar(self.cookie_jar, cookie_storage)
        else:
            raise ValueError('unsupported cookie_storage type.')


    def wait_for_confirm(self, confirm=True, callback=None):
        """Statement that tells GRobot how to deal with javascript confirm().

        @param confirm: A bollean that confirm.
        @param callable: A callable that returns a boolean for confirmation.
        """

        self._robot._confirm_expected = (confirm, callback)
        self._robot.wait_for(lambda: self._robot._confirm_expected is None)
        return self.popup_messages


    def wait_for_text(self, text, time_for_stop=None):
        """Waits until given text appear on main frame.

        @param text: The text to wait for.
        @return:
        """

        logger.debug("Wait for text %s" % text)

        self.wait_for(lambda: text in self.content(),
                      "Can\'t find '%s' in current frame" % text, time_for_stop=time_for_stop)

        return self.wait_for_page_loaded()

    def wait_for_xpath(self, expression, time_for_stop=None):
        self.wait_for(lambda: XPath(self.content()).execute(expression),
                      "Can't find xpath=%s in current frame" % expression, time_for_stop=time_for_stop)
        return self.wait_for_page_loaded()


    def wait_for_selector(self, selector):
        """Waits until selector match an element on the frame.

        @param selector: The selector to wait for.
        """
        self.wait_for(lambda: self.exists(selector),
                      'Can\'t find element matching "%s"' % selector)

    def wait_for_page_loaded(self, time_for_stop=None):
        """Waits until page is loaded, assumed that a page as been requested.

        """
        return self.wait_for(lambda: self._loaded,
                             'Unable to load requested page', time_for_stop=time_for_stop)

    def wait_for(self, condition, timeout_message='', time_for_stop=None):
        """Waits until condition is True.

        @param condition: A callable that returns the condition.
        @param timeout_message: The exception message on timeout.-1 means never timeout.
        """

        if self._loaded:
            time_for_stop = time_for_stop or self.operate_timeout
        else:
            time_for_stop = time_for_stop or self.loading_timeout

        started_at = time.time()
        while not condition():
            if time_for_stop != -1 and time.time() > (started_at + time_for_stop):
                if self._loaded:
                    raise OperateTimeout(timeout_message)
                else:
                    # raise LoadingTimeout, timeout_message
                    self.trigger_action('Stop') #QWebPage::Stop
                    self._loaded = True
                    logger.warning("Page loading timeout.Force to stop the page")
                    break

            gevent.sleep(2)

    def wait_for_alert(self):
        """Waits for main frame alert().
        """
        self.wait_for(lambda: self._alert is not None,
                      'User has not been alerted.')
        msg, self._alert = self._alert, None
        return msg

    def _release_last_resources(self):
        """Releases last loaded resources.

        :return: The released resources.
        """
        last_resources, self.http_resources = self.http_resources[:], []
        return last_resources


    def _page_loaded(self, success):
        if self.develop and self.display:
            if self.inspector is None:
                self.inspector = QWebInspector()

            self.inspector.setPage(self.page)
            self.inspector.show()

        scripts = [
            'atoms.js',
            'htmlutils.js',
        ]

        if self.jquery_namespace:
            scripts.append('jquery-1.9.1.min.js', )

        for script in scripts:
            self.evaluate_js_file(os.path.dirname(__file__) + '/javascripts/' + script)

        if self.jquery_namespace:
            self.evaluate("%s=jQuery.noConflict();" % self.jquery_namespace)

        self._loaded = True
        # self.cache.clear()
        logger.debug("Page load finished")

    def _page_load_started(self):
        logger.debug("Start load page")

        self._loaded = False

    def _unsupported_content(self, reply):
        """Adds an HttpResource object to http_resources with unsupported
        content.

        @param reply: The QNetworkReply object.
        """

        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute):
            self.http_resources.append(HttpResource(reply, self.cache,
                                                    reply.readAll()))

    def _link_clicked(self, href):
        """Contorl the page link clicked event,forbid open new window.

        @param href: The href attribute of a tag.
        """

        self.main_frame.load(href)

    def _request_ended(self, reply):
        """Adds an HttpResource object to http_resources.

        @param reply: The QNetworkReply object.
        """

        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute):
            self.http_resources.append(HttpResource(reply, self.cache))

    def _authenticate(self, mix, authenticator):
        """Called back on basic / proxy http auth.

        @param mix: The QNetworkReply or QNetworkProxy object.
        @param authenticator: The QAuthenticator object.
        """
        if self._auth_attempt == 0:
            username, password = self._auth
            authenticator.setUser(username)
            authenticator.setPassword(password)
            self._auth_attempt += 1

    def _on_manager_ssl_errors(self, reply, errors):
        """Ingore all the ssl error

        @param reply:
        @param errors:
        """
        url = str(reply.url().toString())
        if self.ignore_ssl_errors:
            reply.ignoreSslErrors()
        else:
            logger.warning('SSL certificate error: %s' % url)


    def exit(self):
        """Destroy the Qt main event loop.

        """
        GRobot.exit_lock.acquire()
        if not self._deleted:
            if self.inspector:
                self.inspector.close()
                sip.delete(self.inspector)

            if self.display:
                self.webview.close()
                sip.delete(self.webview)

            if self.page and not sip.isdeleted(self.page):
                sip.delete(self.page)

            GRobot._liveRobot -= 1

            if GRobot._liveRobot == 0 and GRobot._loop is not None:
                GRobot._kill_loop=gevent.spawn_later(20,self.kill_loop)


            self._deleted = True

        GRobot.exit_lock.release()

    def kill_loop(self):
        GRobot._loop.stop()
        GRobot._loop = None
        GRobot._app = None
        if hasattr(self, 'xvfb'):
            GRobot.xvfb.terminate()
Example #7
0
class Web_Page( QWebPage ):

    # =======================================================================
    def __init__(self, parent, parentMain, UID):

        # -------------------------------------------------------------------
        QWebPage.__init__(self, parent);

        # -------------------------------------------------------------------
        self.PARENT                                 = parentMain;
        self.UID                                    = UID;
        self.PARENT_TAB                             = parent;
        self.DEBUG                                  = False;
        self.LOG_TAG                                = str(self.__class__.__name__).upper();

        # -------------------------------------------------------------------
        self.L_BTN_CLICK                            = False;
        self.M_BTN_CLICK                            = False;
        self.R_BTN_CLICK                            = False;

        # -------------------------------------------------------------------
        self.PROGRESS_BAR                           = None;

        self.PROGRESS_BAR_PR                        = float( self.PARENT.WIDTH / 100);
        self.PROGRESS_BAR_W                         = 0;

        # -------------------------------------------------------------------

        # SEARCH_INPUT
        self.SEARCH_INPUT                           =  QLineEdit("", self.PARENT);
        self.SEARCH_INPUT.setGeometry(0, 615, 400, 30);
        self.SEARCH_INPUT.setStyleSheet("QLineEdit{ background-color: rgba(0,0,0, 180); font-size: 12px; font-family: monospace; color: #fff; padding-left: 10px; border-style: none;}");
        self.connect( self.SEARCH_INPUT, SIGNAL('textChanged(const QString)') , self.FIND_ON_PAGE );
        self.connect( self.SEARCH_INPUT, SIGNAL('returnPressed()') , self.FIND_ON_PAGE );
        #self.SEARCH_INPUT.setReadOnly( True );
        self.SEARCH_INPUT_IS_OPEN                   = False;
        self.SEARCH_INPUT.hide();

        # -------------------------------------------------------------------
        self.AGENTS                                 = [

            # Chrome 41.0.2228.0
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
            # Chrome 41.0.2227.1
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
            # Chrome 41.0.2227.0
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            # Chrome 41.0.2226.0
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
            # Chrome 41.0.2225.0
            "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36",
            # Chrome 41.0.2224.3
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
            # Chrome 40.0.2214.93
            "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
            # Chrome 37.0.2062.124
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36",
            # Chrome 37.0.2049.0
            "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36",
            # Chrome 36.0.1985.67
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36",
            # Chrome 36.0.1985.125
            "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
            # Chrome 36.0.1944.0
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36",
            # Chrome 35.0.3319.102
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
            # Chrome 35.0.2309.372
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36",
            # Chrome 35.0.2117.157
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36",
            # Chrome 35.0.1916.47
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36",
            # Chrome 34.0.1866.237
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36",
            # Chrome 34.0.1847.137
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F",
            # Chrome 34.0.1847.116
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36",
            "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10",
            

        ];


        # -------------------------------------------------------------------
        self.USER_AGENT                             = self.AGENTS[0];

        self.HEADERS                                = {
            "User-Agent"        : "[USER-AGENT]",
            "Accept"            : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",

            #"Accept-Language"   : "en-US,en;q=0.5",
            #"Referer"           : "https://btc-e.com/",
            #"Connection"        : "keep-alive",
            #"Cache-Control"     : "max-age=0",
            #"Cookie"            : ""

        }

        # -------------------------------------------------------------------
        #self.setContentEditable( True );
        self.setLinkDelegationPolicy( QWebPage.DelegateAllLinks );
        self.setForwardUnsupportedContent( True );
        self.setNetworkAccessManager( self.PARENT.NET_MANAGER );

        self.linkClicked.connect( self.ON_LINK_CLICKED );
        self.linkHovered.connect( self.ON_LINK_HOVERED );
        self.loadProgress.connect( self.ON_PAGE_LOAD_PROGRESS );

        self.downloadRequested.connect( self.PARENT.DOWNLOAD_MANAGER.REQUEST );
        self.unsupportedContent.connect( self.PARENT.DOWNLOAD_MANAGER.REQUEST );

        # -------------------------------------------------------------------
        self.PRINT_ACTION = QAction( self );
        self.PRINT_ACTION.setShortcuts( QKeySequence.Print );
        self.PRINT_ACTION.triggered.connect( self.PRINT );
        self.PARENT.addAction( self.PRINT_ACTION );

        # -------------------------------------------------------------------
        # SETTINGS
       
        self.SETTINGS                               = self.settings();
        self.SETTINGS.setDefaultTextEncoding("utf-8"); # <= [TO-CONFIG.FILE] + ALL FROM BELOW

        #self.SETTINGS.clearIconDatabase();
        #self.SETTINGS.clearMemoryCaches();

        self.SETTINGS.setAttribute( QWebSettings.AutoLoadImages, True );
        self.SETTINGS.setAttribute( QWebSettings.DnsPrefetchEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.JavascriptEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.JavaEnabled, False );
        self.SETTINGS.setAttribute( QWebSettings.PluginsEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.PrivateBrowsingEnabled, False );
        self.SETTINGS.setAttribute( QWebSettings.JavascriptCanOpenWindows, True ); #
        self.SETTINGS.setAttribute( QWebSettings.JavascriptCanCloseWindows, False ); #
        self.SETTINGS.setAttribute( QWebSettings.JavascriptCanAccessClipboard, True ); #
        self.SETTINGS.setAttribute( QWebSettings.DeveloperExtrasEnabled, True ); #
        self.SETTINGS.setAttribute( QWebSettings.SpatialNavigationEnabled, False );
        self.SETTINGS.setAttribute( QWebSettings.LinksIncludedInFocusChain, True );
        self.SETTINGS.setAttribute( QWebSettings.ZoomTextOnly, False );
        self.SETTINGS.setAttribute( QWebSettings.PrintElementBackgrounds, False );
        self.SETTINGS.setAttribute( QWebSettings.OfflineStorageDatabaseEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.OfflineWebApplicationCacheEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.LocalStorageEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.LocalStorageDatabaseEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.LocalContentCanAccessRemoteUrls, True );
        self.SETTINGS.setAttribute( QWebSettings.LocalContentCanAccessFileUrls, True );
        self.SETTINGS.setAttribute( QWebSettings.XSSAuditingEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.AcceleratedCompositingEnabled, True );
        self.SETTINGS.setAttribute( QWebSettings.TiledBackingStoreEnabled, False );
        self.SETTINGS.setAttribute( QWebSettings.FrameFlatteningEnabled, False );
        self.SETTINGS.setAttribute( QWebSettings.SiteSpecificQuirksEnabled, True );

        # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

        self.SETTINGS.enablePersistentStorage( self.PARENT.STORAGE_ROOT );
        self.SETTINGS.setLocalStoragePath( self.PARENT.STORAGE_ROOT+"local_storage/" );
        self.SETTINGS.setIconDatabasePath ( self.PARENT.STORAGE_ROOT+"icons_path/" )
        self.SETTINGS.setMaximumPagesInCache ( 10 );

        # FIXME: Create settings config file [TO-CONFIG.FILE]

        self.SETTINGS.setOfflineWebApplicationCachePath( self.PARENT.STORAGE_ROOT+"/cache/" );
        self.SETTINGS.setOfflineWebApplicationCacheQuota ( 25 * 1024 *1024 );

        self.SETTINGS.setOfflineStoragePath( self.PARENT.STORAGE_ROOT );
        self.SETTINGS.setOfflineStorageDefaultQuota ( 25 * 1024 *1024 );
        #self.SETTINGS.setObjectCacheCapacities (int cacheMinDeadCapacity, int cacheMaxDead, int totalCapacity)

        # -------------------------------------------------------------------
        self.INSPECTOR    = QWebInspector( None );

        """
        __init__ (self, QWidget parent = None)
        closeEvent (self, QCloseEvent event)
        bool event (self, QEvent)
        hideEvent (self, QHideEvent event)
        QWebPage page (self)
        resizeEvent (self, QResizeEvent event)
        setPage (self, QWebPage page)
        showEvent (self, QShowEvent event)
        QSize sizeHint (self)
        """

        # -------------------------------------------------------------------
        self.INIT();
        # -------------------------------------------------------------------

    # =======================================================================
    def INIT( self ):

        # -------------------------------------------------------------------
        self.HEADERS["User-Agent"] = self.USER_AGENT;

        # -------------------------------------------------------------------

    # =======================================================================
    def POST_INIT( self ):

        # -------------------------------------------------------------------
        self.PROGRESS_BAR = QFrame( self.PARENT.WEB_VIEWS[ self.UID ]["WEB_VIEW"] );
        self.PROGRESS_BAR.setStyleSheet( "QFrame{ background-color: #F00; }" );
        self.PROGRESS_BAR.setGeometry( 0, 0, 0, 5 );
        # -------------------------------------------------------------------

    # =======================================================================
    def EXEC_JS( self, _JS ):

        # -------------------------------------------------------------------
        #frame = self.PARENT.WEB_VIEW.page().mainFrame();
        #frame = self.mainFrame();
        #frame.evaluateJavaScript( _JS );
        
        self.mainFrame().evaluateJavaScript( _JS.encode("utf-8") );
        
        # -------------------------------------------------------------------

    # =======================================================================
    def GET_EXTENTIONS( self ):

        # -------------------------------------------------------------------
        #supportsExtension();
        pass;
        # -------------------------------------------------------------------

    # =======================================================================
    def GET_META_DATA( self ):

        # -------------------------------------------------------------------
        print( "GET_META_DATA:" );

        frame = self.mainFrame();

        meta_data =frame.metaData(); #dict-of-QString-list-of-QString metaData

        for meta in meta_data:
            print(meta);

        # -------------------------------------------------------------------

    # =======================================================================
    def GET_PAGE_HTML( self ):

        # -------------------------------------------------------------------
        frame = self.mainFrame();
        return unicode(frame.toHtml()).encode('utf-8');

        # -------------------------------------------------------------------

    # =======================================================================
    def GET_PAGE_TITLE( self ):

        # -------------------------------------------------------------------
        return str(self.mainFrame().title());

        # -------------------------------------------------------------------

    # =======================================================================
    def CMD( self, _CMD ):

        # -------------------------------------------------------------------
        #__exec:page:exec:js:function_name

        # -------------------------------------------------------------------
        try:
    
            # -----------------------------------------------
            if _CMD[0] == "exec":
                if _CMD[1] == "js":

                    self.EXEC_JS( "".join(_CMD[2:]) );
                    self.LOCAL_INFO_LOG( str(_CMD) );
                    return True;

            # -----------------------------------------------
            elif _CMD[0] == "set":
                if _CMD[1] == "user-agent":

                    self.USER_AGENT = "".join(_CMD[2:]).replace('\"', '\\"').replace("\'", "\\'");
                    self.EXEC_JS( "alert('set:user-agent:"+self.USER_AGENT+"');" );
                    self.LOCAL_INFO_LOG( str(_CMD) );
                    return True;

            # -----------------------------------------------
            elif _CMD[0] == "get":
                if _CMD[1] == "user-agent":

                    self.EXEC_JS( "alert('get:user-agent:"+self.USER_AGENT+"');" );
                    self.LOCAL_INFO_LOG( str(_CMD) );
                    return True;

            # -----------------------------------------------

            self.LOCAL_ERROR_LOG( "UNKNOWN: "+str(_CMD)+" CMD" );                    
            return False;

            # -----------------------------------------------

        except Exception as _err:
            self.LOCAL_ERROR_LOG( str(_CMD)+": "+str(_err) );
            return False;
    
        # -------------------------------------------------------------------

    # =======================================================================
    def AAA_user_BBB_AgentForUrl( self, _url ):

        # -------------------------------------------------------------------
        # Chrome Win/NT 32 Doc-Browser');

        #print("-------------------------------------------------------------")
        #_ByteArray = QUrl.toPercentEncoding( _url.toString(), "{}", " ");
       
        #encoded_url = str( _url.encodedQuery() )
        #print( encoded_url );


        """
        print( "authority(): "+str( _url.authority() ) );
        print( "encodedHost(): "+str( _url.encodedHost() ) );
        print( "encodedPath(): "+str( _url.encodedPath() ) );
        print( "encodedQuery(): "+str( _url.encodedQuery() ) );
        
        print( "encodedUserName(): "+str( _url.encodedUserName() ) );
        print( "host(): "+str( _url.host() ) );
        print( "isLocalFile(): "+str( _url.isLocalFile() ) );
        print( "path(): "+str( _url.path() ) );
        print( "port(): "+str( _url.port() ) );
        print( "userInfo(): "+str( _url.userInfo() ) );
        print( "userName(): "+str( _url.userName() ) );
        print( "topLevelDomain(): "+str( _url.topLevelDomain() ) );
        print( "toString(): "+str( _url.toString() ) );
        """


        #print("-------------------------------------------------------------")
        return self.USER_AGENT;
        # -------------------------------------------------------------------

    # =======================================================================
    def REPAINT_REQUESTED ( self, _QRect ): #repaintRequested (const QRect&)

        # -------------------------------------------------------------------
        print("NOT-IMPLEMENTED: repaintRequested (const QRect&):");
        # -------------------------------------------------------------------
    
    # =======================================================================
    def SHOW_INSPECTOR( self ):

        # -------------------------------------------------------------------
        self.INSPECTOR.setPage( self );


        self.INSPECTOR.setGeometry( 0,0, 800, 450 );
        self.INSPECTOR.show( );
        #self.INSPECTOR.showMaximized( );

        toolbar_items = [
            ".elements", ".resources", ".network", ".scripts", 
            ".timeline", ".profiles", ".audits", ".console"
        ];

        self.mainFrame().evaluateJavaScript("""
            document.addEventListener('DOMContentLoaded',function(){
                setTimeout(function(){
                    document.querySelector('.toolbar-item.network').click()
                }, 2000);
            });
        """);

        # -------------------------------------------------------------------

    # =======================================================================
    def ON_LINK_HOVERED(self, _url_A, _url_B, _url_C ): # (const QString&,const QString&,const QString&)

        # -------------------------------------------------------------------
        #print( "ON_LINK_HOVERED: "+self.UID+": ["+str(_url_A)+"]" );
        
        self.PARENT.STATUS_BAR.setText( unicode(str(_url_A)) );
        # -------------------------------------------------------------------

    # =======================================================================
    def ON_LINK_CLICKED(self, _url):

        # -------------------------------------------------------------------
        _url = unicode( str(_url.toString()).replace("file://", "") );

        # -------------------------------------------------------------------
        #print("ON_LINK_CLICKED: "+self.UID );

        if self.M_BTN_CLICK:
            self.PARENT.CREATE_TAB( _url );
            self.PARENT.URL_BAR.SET_TEXT( _url );

        else:
            self.PARENT.URL_BAR.SET_TEXT( _url );
            self.PARENT.GO_TO_URL();


        self.PARENT.HISTORY_HANDLER.hide();
        # -------------------------------------------------------------------
        #toAscii();
        #toUtf8();
        #toLatin1();
        #toLocal8Bit();
        self.L_BTN_CLICK = False;
        self.M_BTN_CLICK = False;
        self.R_BTN_CLICK = False;
        # -------------------------------------------------------------------

    # =======================================================================
    def ON_PAGE_LOAD_PROGRESS(self, _int_pr):

        # -------------------------------------------------------------------
        if _int_pr >= 100:

            if self.PARENT.CURRENT_TAB_BAR_UID == self.UID:
                self.PARENT.LOADING_BAR.setText( "Loading: Done." );
            self.PROGRESS_BAR_W = 0;


        else:

            if self.PARENT.CURRENT_TAB_BAR_UID == self.UID:
                self.PARENT.LOADING_BAR.setText( "Loading: ["+str(_int_pr)+"]" );

            self.PROGRESS_BAR_W = self.PROGRESS_BAR_PR * _int_pr;

        self.PROGRESS_BAR.setGeometry( 0, 0, self.PROGRESS_BAR_W, 5 );
        # -------------------------------------------------------------------

    # =======================================================================
    def SEARCH_INPUT_CTRL( self, _action ):

        # -------------------------------------------------------------------
        if _action == "show":
            self.SEARCH_INPUT_IS_OPEN = True;
            self.SEARCH_INPUT.setFocus();
            self.SEARCH_INPUT.show();

        else:
            self.SEARCH_INPUT_IS_OPEN = False;
            self.SEARCH_INPUT.clearFocus();
            self.SEARCH_INPUT.hide();
        # -------------------------------------------------------------------

    # =======================================================================
    def FIND_ON_PAGE(self):

        # -------------------------------------------------------------------
        #print("FIND_ON_PAGE: "+self.UID );

        try:

            self.SEARCH_INPUT.setFocus();
            self.SEARCH_INPUT_CTRL("show");

            _value = unicode( str(self.SEARCH_INPUT.text()).strip() );

            #findText (self, QString subString, FindFlags options = 0)
            self.findText (_value, QWebPage.FindWrapsAroundDocument);

            # QWebPage.HighlightAllOccurrences

        except Exception as _err:
            self.LOCAL_ERROR_LOG( str(_err) );

        # -------------------------------------------------------------------

    # =======================================================================
    def PRINT( self ):

        # -------------------------------------------------------------------
        try: 

            pdf_name = self.GET_PAGE_TITLE()+"_"+str(self.PARENT.GET_TIME(True))+"_";
            pdf_name = pdf_name.replace( " ", "-" ).replace( ":", "-" )+".pdf";

            self.PRINTER = QPrinter( QPrinter.ScreenResolution );
            self.PRINTER.setFontEmbeddingEnabled( False );
            self.PRINTER.setFullPage( True );
            #self.PRINTER.setPrinterName ("name of printer");
            #self.PRINTER.setPrintProgram ( "print program");

            self.PRINTER.setOutputFileName ( self.PARENT.DOWNLOAD_DIR+pdf_name );

            # ------ >
            #self.PRINTER.setOutputFormat( QPrinter.NativeFormat ); # <= IMAGE
            self.PRINTER.setOutputFormat( QPrinter.PdfFormat ); # <= TEXT
            #self.PRINTER.setOutputFormat( QPrinter.PostScriptFormat ); # ???

            #QPrinter will print output using a method defined by the platform it is running on. 
            # This mode is the default when printing directly to a printer.
            #QPrinter.NativeFormat [0]   
            
            #QPrinter will generate its output as a searchable PDF file. This mode is the default when printing to a file.
            #QPrinter.PdfFormat [1]   

            #QPrinter will generate its output as in the PostScript format. (This feature was introduced in Qt 4.2.)
            #QPrinter.PostScriptFormat [2]

            self.mainFrame().print_( self.PRINTER );

            self.LOCAL_INFO_LOG( "PDF: Saved ["+str(self.PARENT.DOWNLOAD_DIR+pdf_name)+"]" );


        except Exception as _err:
            self.LOCAL_ERROR_LOG( str(_err) );

        # -------------------------------------------------------------------

    # =======================================================================
    def SEND_ESC( self ):

        # -------------------------------------------------------------------
        #print("SEND_ESC: START");
        js = '''
                
            try{

                var e = document.createEvent('KeyboardEvent');
                e.initKeyboardEvent("keyup", true, true, window, 0,0,0,0,0, 27,0 );
                document.dispatchEvent(e);

                var e = document.createEvent('KeyboardEvent');
                e.initKeyboardEvent("keydown", true, true, window, 0,0,0,0,0, 27,0 );
                document.dispatchEvent(e);

                var e = document.createEvent('KeyboardEvent');
                e.initKeyboardEvent("keypress", true, true, window, 0,0,0,0,0, 27,0 );
                document.dispatchEvent(e);



            }catch(e){
                alert(e);
            }

        '''


        # -------------------------------------------------------------------
        self.mainFrame().evaluateJavaScript( js );
        #print("SEND_ESC: DONE");
        # -------------------------------------------------------------------

    # =======================================================================
    def event( self, _evt ):

        # -------------------------------------------------------------------
        #self.L_BTN_CLICK = False;
        #self.M_BTN_CLICK = False;
        #self.R_BTN_CLICK = False;

        # -------------------------------------------------------------------
        # http://pyqt.sourceforge.net/Docs/PyQt4/qevent.html#Type-enum
        #print( _evt.type() );
        # -------------------------------------------------------------------
        #if _evt.type() == QEvent.FocusOut:      # (QFocusEvent) == 9 == focus OUT BY CLICK
        #elif _evt.type() == QEvent.FocusIn:     # (QFocusEvent) == 8 == focus IN BY CLICK

        # -------------------------------------------------------------------
        #if _evt.type() == QEvent.ContextMenu:   # (QContextMenuEvent) == 82

        # -------------------------------------------------------------------
        #QEvent.MouseButtonDblClick  4   Mouse press again (QMouseEvent).
        #QEvent.MouseButtonRelease   3   Mouse release (QMouseEvent).
        #QPoint _evt.globalPos(); | int _evt.global[X|Y](); | QPoint _evt.pos(); | QPointF _evt.posF(); | int _evt.[x|y]()
        #_evt.button() == ( Qt.LeftButton | Qt.RightButton | Qt.MidButton )
        if _evt.type() == QEvent.MouseButtonPress: # 2   Mouse press (QMouseEvent).

            #print( "WEB_PAGE["+str(self.UID)+"].event:" );
            if _evt.button() == Qt.LeftButton:
                #print("Qt.LeftButton: "+str(Qt.LeftButton))
                self.L_BTN_CLICK = True;

            elif _evt.button() == Qt.MidButton:
                #print("Qt.MidButton: "+str(Qt.MidButton))
                self.M_BTN_CLICK = True;
            
            elif _evt.button() == Qt.RightButton:
                #print("Qt.RightButton: "+str(Qt.RightButton))
                self.R_BTN_CLICK = True;

        # -------------------------------------------------------------------
        """
        #QEvent.KeyPress 6   Key press (QKeyEvent).
        #QEvent.KeyRelease   7   Key release (QKeyEvent).
        if _evt.type() == QEvent.KeyRelease:
            print("KeyRelease: ["+str(_evt.text())+", "+str(_evt.nativeScanCode())+", "+str(_evt.key())+"]");

        int count (self)
        bool isAutoRepeat (self)
        int key (self)
        bool matches (self, QKeySequence.StandardKey key)
        Qt.KeyboardModifiers modifiers (self)
        int nativeModifiers (self)
        int nativeScanCode (self)
        int nativeVirtualKey (self)
        QString text (self)
        """

        # -------------------------------------------------------------------

        """
        if _evt.type() == QEvent.Enter:         # 10

            self.setStyleSheet( self.STL["hovered"] );
            self.HAS_FOCUS = True;

        elif _evt.type() == QEvent.Leave:       # 11 

            self.setStyleSheet( self.STL["default"] );
            self.HAS_FOCUS = False;
        """

        # -------------------------------------------------------------------
        return QWebPage.event(self, _evt);
        # -------------------------------------------------------------------

    # =======================================================================
    def LOCAL_INFO_LOG( self, _msg, METHOD=None ):

        # -------------------------------------------------------------------
        if METHOD is None:
            self.PARENT.LOCAL_INFO_LOG( "['"+self.LOG_TAG+"']: ["+_msg+"]" );
        else:
            self.PARENT.LOCAL_INFO_LOG( "['"+self.LOG_TAG+"."+METHOD+"']: ["+_msg+"]" );
        # -------------------------------------------------------------------

    # =======================================================================
    def LOCAL_ERROR_LOG( self, _msg, METHOD=None ):

        # -------------------------------------------------------------------
        if self.DEBUG or self.PARENT.DEBUG_GLOBAL: self.PARENT.DEBUGGER.DEBUG();
        # -------------------------------------------------------------------
        if METHOD is None:
            self.PARENT.LOCAL_ERROR_LOG( "['"+self.LOG_TAG+"']: ["+_msg+"]" );
        else:
            self.PARENT.LOCAL_ERROR_LOG( "['"+self.LOG_TAG+"."+METHOD+"']: ["+_msg+"]" );
        # -------------------------------------------------------------------

    # =======================================================================
    def LOCAL_WARNING_LOG( self, _msg, METHOD=None ):

        # -------------------------------------------------------------------
        if METHOD is None:
            self.PARENT.LOCAL_WARNING_LOG( "['"+self.LOG_TAG+"']: ["+_msg+"]" );
        else:
            self.PARENT.LOCAL_WARNING_LOG( "['"+self.LOG_TAG+"."+METHOD+"']: ["+_msg+"]" );