Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  atspi2.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */


#include "atspi2.hxx"

#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>

#include <comphelper/propertyvalue.hxx>
#include <o3tl/string_view.hxx>
#include <sfx2/zoomitem.hxx>
#include <unotest/macros_test.hxx>

#include <test/a11y/AccessibilityTools.hxx>

#include "atspiwrapper.hxx"

using namespace css;

// from gtk3/a11y/atkwrapper.cxx
static AtspiRole mapToAtspiRole(sal_Int16 nRole, sal_Int64 nStates)
{
    switch (nRole)
    {
#define MAP(lo, atspi)                                                                             \
    case accessibility::AccessibleRole::lo:                                                        \
        return ATSPI_ROLE_##atspi
#define MAP_DIRECT(a) MAP(a, a)

        MAP_DIRECT(UNKNOWN);
        MAP_DIRECT(ALERT);
        MAP_DIRECT(BLOCK_QUOTE);
        MAP_DIRECT(COLUMN_HEADER);
        MAP_DIRECT(CANVAS);
        MAP_DIRECT(CHECK_BOX);
        MAP_DIRECT(CHECK_MENU_ITEM);
        MAP_DIRECT(COLOR_CHOOSER);
        MAP_DIRECT(COMBO_BOX);
        MAP_DIRECT(DATE_EDITOR);
        MAP_DIRECT(DESKTOP_ICON);
        MAP(DESKTOP_PANE, DESKTOP_FRAME);
        MAP_DIRECT(DIRECTORY_PANE);
        MAP_DIRECT(DIALOG);
        MAP(DOCUMENT, DOCUMENT_FRAME);
        MAP(EMBEDDED_OBJECT, EMBEDDED);
        MAP(END_NOTE, FOOTNOTE);
        MAP_DIRECT(FILE_CHOOSER);
        MAP_DIRECT(FILLER);
        MAP_DIRECT(FONT_CHOOSER);
        MAP_DIRECT(FOOTER);
        MAP_DIRECT(FOOTNOTE);
        MAP_DIRECT(FRAME);
        MAP_DIRECT(GLASS_PANE);
        MAP(GRAPHIC, IMAGE);
        MAP(GROUP_BOX, GROUPING);
        MAP_DIRECT(HEADER);
        MAP_DIRECT(HEADING);
        MAP(HYPER_LINK, LINK);
        MAP_DIRECT(ICON);
        MAP_DIRECT(INTERNAL_FRAME);
        MAP_DIRECT(LABEL);
        MAP_DIRECT(LAYERED_PANE);
        MAP_DIRECT(LIST);
        MAP_DIRECT(LIST_ITEM);
        MAP_DIRECT(MENU);
        MAP_DIRECT(MENU_BAR);
        MAP_DIRECT(MENU_ITEM);
        MAP_DIRECT(OPTION_PANE);
        MAP_DIRECT(PAGE_TAB);
        MAP_DIRECT(PAGE_TAB_LIST);
        MAP_DIRECT(PANEL);
        MAP_DIRECT(PARAGRAPH);
        MAP_DIRECT(PASSWORD_TEXT);
        MAP_DIRECT(POPUP_MENU);
        MAP_DIRECT(PUSH_BUTTON);
        MAP_DIRECT(PROGRESS_BAR);
        MAP_DIRECT(RADIO_BUTTON);
        MAP_DIRECT(RADIO_MENU_ITEM);
        MAP_DIRECT(ROW_HEADER);
        MAP_DIRECT(ROOT_PANE);
        MAP_DIRECT(SCROLL_BAR);
        MAP_DIRECT(SCROLL_PANE);
        MAP(SHAPE, PANEL);
        MAP_DIRECT(SEPARATOR);
        MAP_DIRECT(SLIDER);
        MAP(SPIN_BOX, SPIN_BUTTON);
        MAP_DIRECT(SPLIT_PANE);
        MAP_DIRECT(STATUS_BAR);
        MAP_DIRECT(TABLE);
        MAP_DIRECT(TABLE_CELL);
        MAP_DIRECT(TEXT);
        MAP(TEXT_FRAME, PANEL);
        MAP_DIRECT(TOGGLE_BUTTON);
        MAP_DIRECT(TOOL_BAR);
        MAP_DIRECT(TOOL_TIP);
        MAP_DIRECT(TREE);
        MAP(VIEW_PORT, VIEWPORT);
        MAP_DIRECT(WINDOW);
#if ATSPI_ROLE_COUNT > 130 /* ATSPI_ROLE_PUSH_BUTTON_MENU is 129 */
        MAP(BUTTON_MENU, PUSH_BUTTON_MENU);
#else
        MAP(BUTTON_MENU, PUSH_BUTTON);
#endif
        MAP_DIRECT(CAPTION);
        MAP_DIRECT(CHART);
        MAP(EDIT_BAR, EDITBAR);
        MAP_DIRECT(FORM);
        MAP_DIRECT(IMAGE_MAP);
        MAP(NOTE, COMMENT);
        MAP_DIRECT(PAGE);
        MAP_DIRECT(RULER);
        MAP_DIRECT(SECTION);
        MAP_DIRECT(TREE_ITEM);
        MAP_DIRECT(TREE_TABLE);
        MAP_DIRECT(COMMENT);
        MAP(COMMENT_END, UNKNOWN);
        MAP_DIRECT(DOCUMENT_PRESENTATION);
        MAP_DIRECT(DOCUMENT_SPREADSHEET);
        MAP_DIRECT(DOCUMENT_TEXT);
        MAP_DIRECT(STATIC);
        MAP_DIRECT(NOTIFICATION);

#undef MAP_DIRECT
#undef MAP
        case css::accessibility::AccessibleRole::BUTTON_DROPDOWN:
            if (nStates & css::accessibility::AccessibleStateType::CHECKABLE)
                return ATSPI_ROLE_TOGGLE_BUTTON;
            return ATSPI_ROLE_PUSH_BUTTON;
        default:
            SAL_WARN("vcl.gtk""Unmapped accessible role: " << nRole);
            return ATSPI_ROLE_UNKNOWN;
    }
}

static AtspiStateType mapAtspiState(sal_Int64 nState)
{
    // A perfect / complete mapping ...
    switch (nState)
    {
#define MAP(lo, atspi)                                                                             \
    case accessibility::AccessibleStateType::lo:                                                   \
        return ATSPI_STATE_##atspi
#define MAP_DIRECT(a) MAP(a, a)

        MAP_DIRECT(INVALID);
        MAP_DIRECT(ACTIVE);
        MAP_DIRECT(ARMED);
        MAP_DIRECT(BUSY);
        MAP_DIRECT(CHECKABLE);
        MAP_DIRECT(CHECKED);
        MAP_DIRECT(EDITABLE);
        MAP_DIRECT(ENABLED);
        MAP_DIRECT(EXPANDABLE);
        MAP_DIRECT(EXPANDED);
        MAP_DIRECT(FOCUSABLE);
        MAP_DIRECT(FOCUSED);
        MAP_DIRECT(HORIZONTAL);
        MAP_DIRECT(ICONIFIED);
        MAP_DIRECT(INDETERMINATE);
        MAP_DIRECT(MANAGES_DESCENDANTS);
        MAP_DIRECT(MODAL);
        MAP_DIRECT(MULTI_LINE);
        MAP(MULTI_SELECTABLE, MULTISELECTABLE);
        MAP_DIRECT(OPAQUE);
        MAP_DIRECT(PRESSED);
        MAP_DIRECT(RESIZABLE);
        MAP_DIRECT(SELECTABLE);
        MAP_DIRECT(SELECTED);
        MAP_DIRECT(SENSITIVE);
        MAP_DIRECT(SHOWING);
        MAP_DIRECT(SINGLE_LINE);
        MAP_DIRECT(STALE);
        MAP_DIRECT(TRANSIENT);
        MAP_DIRECT(VERTICAL);
        MAP_DIRECT(VISIBLE);
        MAP(DEFAULT, IS_DEFAULT);
        // a spelling error ...
        MAP(DEFUNC, DEFUNCT);

#undef MAP_DIRECT
#undef MAP

        default:
            //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
            //NOTE! Do not report it
            return ATSPI_STATE_LAST_DEFINED;
    }
}

static AtspiRelationType mapRelationType(AccessibleRelationType eRelation)
{
    switch (eRelation)
    {
        case accessibility::AccessibleRelationType_CONTENT_FLOWS_FROM:
            return ATSPI_RELATION_FLOWS_FROM;
        case accessibility::AccessibleRelationType_CONTENT_FLOWS_TO:
            return ATSPI_RELATION_FLOWS_TO;
        case accessibility::AccessibleRelationType_CONTROLLED_BY:
            return ATSPI_RELATION_CONTROLLED_BY;
        case accessibility::AccessibleRelationType_CONTROLLER_FOR:
            return ATSPI_RELATION_CONTROLLER_FOR;
        case accessibility::AccessibleRelationType_DESCRIBED_BY:
            return ATSPI_RELATION_DESCRIBED_BY;
        case accessibility::AccessibleRelationType_LABEL_FOR:
            return ATSPI_RELATION_LABEL_FOR;
        case accessibility::AccessibleRelationType_LABELED_BY:
            return ATSPI_RELATION_LABELLED_BY;
        case accessibility::AccessibleRelationType_MEMBER_OF:
            return ATSPI_RELATION_MEMBER_OF;
        case accessibility::AccessibleRelationType_SUB_WINDOW_OF:
            return ATSPI_RELATION_SUBWINDOW_OF;
        case accessibility::AccessibleRelationType_NODE_CHILD_OF:
            return ATSPI_RELATION_NODE_CHILD_OF;
        default:
            return ATSPI_RELATION_NULL;
    }
}

static std::string debugString(const Atspi::Accessible& pAtspiAccessible)
{
    CPPUNIT_NS::OStringStream ost;

    ost << "(" << static_cast<const void*>(pAtspiAccessible.get()) << ")";
    if (pAtspiAccessible)
    {
        ost << " role=\"" << pAtspiAccessible.getRoleName() << '"';
        ost << " name=\"" << pAtspiAccessible.getName() << '"';
        ost << " description=\"" << pAtspiAccessible.getDescription() << '"';
    }

    return ost.str();
}

static void dumpAtspiTree(const Atspi::Accessible& pAcc, const int depth = 0)
{
    std::cout << debugString(pAcc) << std::endl;

    sal_Int32 i = 0;
    for (const auto pChild : pAcc)
    {
        for (auto j = decltype(depth){ 0 }; j < depth; j++)
            std::cout << " ";
        std::cout << " * child " << i++ << ": ";
        dumpAtspiTree(pChild, depth + 1);
    }
}

void Atspi2TestTree::compareObjects(const uno::Reference<accessibility::XAccessible>&&nbsp;xLOAccessible,
                                    const Atspi::Accessible& pAtspiAccessible,
                                    const sal_uInt16 recurseFlags)
{
    if (recurseFlags != RecurseFlags::NONE)
        std::cout << "checking " << debugString(pAtspiAccessible) << " against "
                  << AccessibilityTools::debugString(xLOAccessible) << std::endl;

    CPPUNIT_ASSERT(xLOAccessible);
    CPPUNIT_ASSERT(pAtspiAccessible);

    auto xLOContext = xLOAccessible->getAccessibleContext();

    /* role: we translate to ATSPI role, because the value was created by LO already and converted
     * to ATK, which in turn converts it to ATSPI.  However, ATK and ATSPI are roughly equivalent
     * (ATK basically follows ATSPI), but LO's internal might have more complex mappings that can't
     * be represented with a round trip. */

    const AtspiRole nLORole
        = mapToAtspiRole(xLOContext->getAccessibleRole(), xLOContext->getAccessibleStateSet());
    const auto nAtspiRole = pAtspiAccessible.getRole();
    CPPUNIT_ASSERT_EQUAL(nLORole, nAtspiRole);
    /* name (no need to worry about debugging suffixes as AccessibilityTools::nameEquals does, as
     * that will also be part of the name sent to ATSPI) */

    CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleName(),
                         OUString::fromUtf8(pAtspiAccessible.getName()));
    // description
    CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleDescription(),
                         OUString::fromUtf8(pAtspiAccessible.getDescription()));

    // parent relationship (this is conditional as the ATSPI tree has additional parents, as well as
    // because we don't want to recurse up the tree)
    if (recurseFlags & RecurseFlags::PARENT)
    {
        // index in parent
        CPPUNIT_ASSERT_EQUAL(xLOContext->getAccessibleIndexInParent(),
                             sal_Int64(pAtspiAccessible.getIndexInParent()));

        // parent (well, that's making things a lot more expensive...)
        compareObjects(xLOContext->getAccessibleParent(), pAtspiAccessible.getParent(),
                       RecurseFlags::NONE);
    }

    // state set
    const auto loStateSet = xLOContext->getAccessibleStateSet();
    const auto atspiStateSet = pAtspiAccessible.getStateSet();
    const auto nBits
        = (sizeof(decltype(loStateSet)) * 8) - (std::is_signed_v<decltype(loStateSet)> ? 1 : 0);
    for (auto shift = decltype(nBits){ 0 }; shift < nBits; shift++)
    {
        const auto loState = decltype(loStateSet){ 1 } << shift;
        const auto atspiState = mapAtspiState(loState);

        // ignore a state that does not map to Atspi
        if (atspiState == ATSPI_STATE_LAST_DEFINED)
            continue;

        /* FIXME: The ATK implementation in LO adds FOCUSED if the obj == atk_get_focus_object()
         * (see atkwrapper.cxx::wrapper_ref_state_set()), but there seem to be some bug (or delay?
         * as it's done in idle) in the tracking, so we can end up with extra FOCUSED states on the
         * Atspi side.  To work around that, we skip the case where it's not set on LO's side */

        if (atspiState == ATSPI_STATE_FOCUSED && !(loStateSet & loState))
            continue;

        CPPUNIT_ASSERT_EQUAL_MESSAGE("Unmatched state: " + Atspi::State::getName(atspiState),
                                     (loStateSet & loState) != 0,
                                     atspiStateSet.contains(atspiState));
    }

    // attributes
    if (auto xLOAttrs
        = uno::Reference<accessibility::XAccessibleExtendedAttributes>(xLOContext, uno::UNO_QUERY))
    {
        // see atktextattributes.cxx:attribute_set_new_from_extended_attributes
        const OUString sExtendedAttrs = xLOAttrs->getExtendedAttributes();
        sal_Int32 nIndex = 0;

        const auto atspiAttrs = pAtspiAccessible.getAttributes();

        do
        {
            OUString sProperty = sExtendedAttrs.getToken(0, ';', nIndex);

            sal_Int32 nColonPos = 0;
            const OString sPropertyName = OUStringToOString(
                o3tl::getToken(sProperty, 0, ':', nColonPos), RTL_TEXTENCODING_UTF8);
            const OString sPropertyValue = OUStringToOString(
                o3tl::getToken(sProperty, 0, ':', nColonPos), RTL_TEXTENCODING_UTF8);

            const auto atspiAttrIter = atspiAttrs.find(std::string(sPropertyName));
            CPPUNIT_ASSERT_MESSAGE(std::string("Missing attribute: ") + sPropertyName.getStr(),
                                   atspiAttrIter != atspiAttrs.end());
            CPPUNIT_ASSERT_EQUAL(std::string_view(sPropertyName),
                                 std::string_view(atspiAttrIter->first));
            CPPUNIT_ASSERT_EQUAL(std::string_view(sPropertyValue),
                                 std::string_view(atspiAttrIter->second));
        } while (nIndex >= 0 && nIndex < sExtendedAttrs.getLength());
    }

    // relations
    const auto xLORelationSet = xLOContext->getAccessibleRelationSet();
    const auto aAtspiRelationSet = pAtspiAccessible.getRelationSet();
    const auto nLORelationCount = xLORelationSet.is() ? xLORelationSet->getRelationCount() : 0;
    CPPUNIT_ASSERT_EQUAL(nLORelationCount, sal_Int32(aAtspiRelationSet.size()));
    for (auto i = decltype(nLORelationCount){ 0 }; i < nLORelationCount; i++)
    {
        const auto xLORelation = xLORelationSet->getRelation(i);
        const auto pAtspiRelation = aAtspiRelationSet[i];
        const auto nLOTargetsCount = xLORelation.TargetSet.getLength();

        CPPUNIT_ASSERT_EQUAL(mapRelationType(xLORelation.RelationType),
                             pAtspiRelation.getRelationType());
        CPPUNIT_ASSERT_EQUAL(nLOTargetsCount, static_cast<sal_Int32>(pAtspiRelation.getNTargets()));

        if (recurseFlags & RecurseFlags::RELATIONS_TARGETS)
        {
            for (auto j = decltype(nLOTargetsCount){ 0 }; j < nLOTargetsCount; j++)
            {
                uno::Reference<accessibility::XAccessible> xLOTarget = xLORelation.TargetSet[j];
                compareObjects(xLOTarget, pAtspiRelation.getTarget(j), RecurseFlags::NONE);
            }
        }
    }

    // other interfaces
    if (auto xLOText = uno::Reference<accessibility::XAccessibleText>(xLOContext, uno::UNO_QUERY))
    {
        Atspi::Text pAtspiText;
        CPPUNIT_ASSERT_NO_THROW(pAtspiText = pAtspiAccessible.queryText());
        compareTextObjects(xLOText, pAtspiText);
    }

    // TODO: more checks here...
}

void Atspi2TestTree::compareTrees(const uno::Reference<accessibility::XAccessible>&&nbsp;xLOAccessible,
                                  const Atspi::Accessible& xAtspiAccessible, const int depth)
{
    sal_uInt16 recurseFlags = RecurseFlags::ALL;
    if (depth == 0)
        recurseFlags ^= RecurseFlags::PARENT;
    compareObjects(xLOAccessible, xAtspiAccessible, recurseFlags);

    if (!xLOAccessible || !xAtspiAccessible)
        return;

    auto xLOContext = xLOAccessible->getAccessibleContext();
    CPPUNIT_ASSERT(xLOContext);

    const auto nLOChildCount = xLOContext->getAccessibleChildCount();
    const auto nAtspiChildCount = decltype(nLOChildCount){ xAtspiAccessible.getChildCount() };
    /* We use >= instead of == because GTK exposes scrollbar objects LO doesn't.  We possibly
     * should check better than merely accept more children, but it's probably OK if there are
     * *more* children as viewed by ATSPI, rather than less.  And we're comparing them anyway. */

    CPPUNIT_ASSERT_GREATEREQUAL(nLOChildCount, nAtspiChildCount);

    for (auto nthChild = decltype(nLOChildCount){ 0 }; nthChild < nLOChildCount; nthChild++)
    {
        for (auto i = decltype(depth){ 0 }; i < depth; i++)
            std::cout << " ";
        std::cout << "* child " << nthChild << ": ";
        compareTrees(xLOContext->getAccessibleChild(nthChild),
                     xAtspiAccessible.getChildAtIndex(nthChild), depth + 1);
    }

    /* We need to scrolling test here, because they might modify the tree and invalidate children,
     * so we can't do it from the children themselves as they might get disposed during the test */

    if (nLOChildCount > 0
        && accessibility::AccessibleRole::DOCUMENT_TEXT == xLOContext->getAccessibleRole())
    {
        testSwScroll(xLOContext, xAtspiAccessible);
    }
}

// gets the nth child of @p pAcc and check its role is @p role
static Atspi::Accessible getDescendentAtPath(const Atspi::Accessible& xAcc, int nthChild,
                                             AtspiRole role)
{
    CPPUNIT_ASSERT(xAcc);
    CPPUNIT_ASSERT_GREATER(nthChild, xAcc.getChildCount());
    auto xChild = xAcc.getChildAtIndex(nthChild);
    CPPUNIT_ASSERT(xChild);
    CPPUNIT_ASSERT_EQUAL(role, xChild.getRole());
    return xChild;
}

// gets the nth child of @p pAcc and check its role is @p role, then gets the nth child of that one, etc.
template <typename... Ts>
static Atspi::Accessible getDescendentAtPath(const Atspi::Accessible& xAcc, int nthChild,
                                             AtspiRole role, Ts... args)
{
    return getDescendentAtPath(getDescendentAtPath(xAcc, nthChild, role), args...);
}

CPPUNIT_TEST_FIXTURE(Atspi2TestTree, Test1)
{
    loadFromSrc(u"vcl/qa/cppunit/a11y/atspi2/testdocuments/ecclectic.fodt"_ustr);

    /* FIXME: We zoom out for everything to fit in the view not to have off-screen children
     * that the controller code fails to clean up properly in some situations.
     * Once the root issue is fixed in LO, remove this.
     * Note that zooming out like so, and not having off-screen children, renders the
     * Atspi2TestTree::testSwScroll() test useless as it has nothing to scroll into view. */

    unotest::MacrosTest::dispatchCommand(mxDocument, ".uno:ZoomPage", {});
    unotest::MacrosTest::dispatchCommand(
        mxDocument, ".uno:ViewLayout",
        {
            comphelper::makePropertyValue("ViewLayout.Columns", sal_Int16(2)),
            comphelper::makePropertyValue("ViewLayout.BookMode"false),
        });
    /* HACK: verify the whole content of the document is actually visible (nothing overflows)
     * after zooming out above */

    const auto xLODocContext = getDocumentAccessibleContext();
    const auto xLODocFirstChild = xLODocContext->getAccessibleChild(0);
    CPPUNIT_ASSERT(xLODocFirstChild.is());
    CPPUNIT_ASSERT(
        !getFirstRelationTargetOfType(xLODocFirstChild->getAccessibleContext(),
                                      accessibility::AccessibleRelationType_CONTENT_FLOWS_FROM));
    const auto nLODocChildCount = xLODocContext->getAccessibleChildCount();
    const auto xLODocLastChild = xLODocContext->getAccessibleChild(nLODocChildCount - 1);
    CPPUNIT_ASSERT(xLODocLastChild.is());
    CPPUNIT_ASSERT(
        !getFirstRelationTargetOfType(xLODocLastChild->getAccessibleContext(),
                                      accessibility::AccessibleRelationType_CONTENT_FLOWS_TO));
    // END HACK

    auto xContext = getWindowAccessibleContext();
    CPPUNIT_ASSERT(xContext.is());

    //~ dumpA11YTree(xContext);

    // get the window manager frame
    auto xAtspiWindow = getDescendentAtPath(m_pAtspiApp, 0, ATSPI_ROLE_FRAME);
    CPPUNIT_ASSERT(xAtspiWindow);
    dumpAtspiTree(xAtspiWindow);

    /* The ATSPI representation has extra nodes around the relevant ones, which look like leftovers
     * from the start center.  Ignore those and dive directly to the meaningful node (which is the
     * 1st child of the 2nd child of the 1st child -- ask me how I know) */

    auto xAtspiPane = getDescendentAtPath(xAtspiWindow, 0, ATSPI_ROLE_PANEL, 1, ATSPI_ROLE_PANEL, 0,
                                          ATSPI_ROLE_ROOT_PANE);

    compareTrees(getWindowAccessible(), xAtspiPane);
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

Messung V0.5
C=88 H=96 G=91

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge