com.vaadin.client.LayoutManager.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.client.LayoutManager.java

Source

/*
 * Copyright 2000-2018 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.client;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.user.client.Timer;
import com.vaadin.client.MeasuredSize.MeasureResult;
import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.client.ui.layout.LayoutDependencyTree;

public class LayoutManager {
    private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server.";

    private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";

    private static final boolean DEBUG_LOGGING = false;

    private ApplicationConnection connection;
    private final Set<Element> measuredNonConnectorElements = new HashSet<>();
    private final MeasuredSize nullSize = new MeasuredSize();

    private LayoutDependencyTree currentDependencyTree;

    private FastStringSet needsHorizontalLayout = FastStringSet.create();
    private FastStringSet needsVerticalLayout = FastStringSet.create();

    private FastStringSet needsMeasure = FastStringSet.create();

    private FastStringSet pendingOverflowFixes = FastStringSet.create();

    private final Map<Element, Collection<ElementResizeListener>> elementResizeListeners = new HashMap<>();
    private final Set<Element> listenersToFire = new HashSet<>();

    private boolean layoutPending = false;
    private Timer layoutTimer = new Timer() {
        @Override
        public void run() {
            layoutNow();
        }
    };
    private boolean everythingNeedsMeasure = false;

    /**
     * Sets the application connection this instance is connected to. Called
     * internally by the framework.
     *
     * @param connection
     *            the application connection this instance is connected to
     */
    public void setConnection(ApplicationConnection connection) {
        if (this.connection != null) {
            throw new RuntimeException("LayoutManager connection can never be changed");
        }
        this.connection = connection;
    }

    /**
     * Returns the application connection for this layout manager.
     *
     * @return connection
     */
    protected ApplicationConnection getConnection() {
        return connection;
    }

    /**
     * Gets the layout manager associated with the given
     * {@link ApplicationConnection}.
     *
     * @param connection
     *            the application connection to get a layout manager for
     * @return the layout manager associated with the provided application
     *         connection
     */
    public static LayoutManager get(ApplicationConnection connection) {
        return connection.getLayoutManager();
    }

    /**
     * Registers that a ManagedLayout is depending on the size of an Element.
     * This causes this layout manager to measure the element in the beginning
     * of every layout phase and call the appropriate layout method of the
     * managed layout if the size of the element has changed.
     *
     * @param owner
     *            the ManagedLayout that depends on an element
     * @param element
     *            the Element that should be measured
     */
    public void registerDependency(ManagedLayout owner, Element element) {
        MeasuredSize measuredSize = ensureMeasured(element);
        setNeedsLayout(owner);
        measuredSize.addDependent(owner.getConnectorId());
    }

    private MeasuredSize ensureMeasured(Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            measuredSize = new MeasuredSize();

            if (ConnectorMap.get(connection).getConnector(element) == null) {
                measuredNonConnectorElements.add(element);
            }
            setMeasuredSize(element, measuredSize);
        }
        return measuredSize;
    }

    private boolean needsMeasure(Element e) {
        ComponentConnector connector = connection.getConnectorMap().getConnector(e);
        if (connector != null && needsMeasureForManagedLayout(connector)) {
            return true;
        } else if (elementResizeListeners.containsKey(e)) {
            return true;
        }
        return getMeasuredSize(e, nullSize).hasDependents();
    }

    private boolean needsMeasureForManagedLayout(ComponentConnector connector) {
        if (connector instanceof ManagedLayout) {
            return true;
        }
        return connector.getParent() instanceof ManagedLayout;
    }

    /**
     * Assigns a measured size to an element. Method defined as protected for
     * legacy reasons.
     *
     * @param element
     *            the dom element to attach the measured size to
     * @param measuredSize
     *            the measured size to attach to the element. If
     *            <code>null</code>, any previous measured size is removed.
     */
    protected native void setMeasuredSize(Element element, MeasuredSize measuredSize)
    /*-{
    if (measuredSize) {
        element.vMeasuredSize = measuredSize;
    } else {
        delete element.vMeasuredSize;
    }
    }-*/;

    /**
     * Gets the measured size for an element. Method defined as protected for
     * legacy reasons.
     *
     * @param element
     *            The element to get measured size for
     * @param defaultSize
     *            The size to return if no measured size could be found
     * @return The measured size for the element or {@literal defaultSize}
     */
    protected native MeasuredSize getMeasuredSize(Element element, MeasuredSize defaultSize)
    /*-{
    return element.vMeasuredSize || defaultSize;
    }-*/;

    private final MeasuredSize getMeasuredSize(Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            measuredSize = new MeasuredSize();
            setMeasuredSize(element, measuredSize);
        }
        return measuredSize;
    }

    /**
     * Registers that a ManagedLayout is no longer depending on the size of an
     * Element.
     *
     * @see #registerDependency(ManagedLayout, Element)
     *
     * @param owner
     *            the ManagedLayout no longer depends on an element
     * @param element
     *            the Element that that no longer needs to be measured
     */
    public void unregisterDependency(ManagedLayout owner, Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            return;
        }
        measuredSize.removeDependent(owner.getConnectorId());
        stopMeasuringIfUnecessary(element);
    }

    public boolean isLayoutRunning() {
        return currentDependencyTree != null;
    }

    private void countLayout(FastStringMap<Integer> layoutCounts, ManagedLayout layout) {
        Integer count = layoutCounts.get(layout.getConnectorId());
        if (count == null) {
            count = Integer.valueOf(0);
        } else {
            count = Integer.valueOf(count.intValue() + 1);
        }
        layoutCounts.put(layout.getConnectorId(), count);
        if (count.intValue() > 2) {
            getLogger()
                    .severe(Util.getConnectorString(layout) + " has been layouted " + count.intValue() + " times");
        }
    }

    public void layoutLater() {
        if (!layoutPending) {
            layoutPending = true;
            layoutTimer.schedule(100);
        }
    }

    public void layoutNow() {
        if (isLayoutRunning()) {
            throw new IllegalStateException(
                    "Can't start a new layout phase before the previous layout phase ends.");
        }

        if (connection.getMessageHandler().isUpdatingState()) {
            // If assertions are enabled, throw an exception
            assert false : STATE_CHANGE_MESSAGE;

            // Else just log a warning and postpone the layout
            getLogger().warning(STATE_CHANGE_MESSAGE);

            // Framework will call layoutNow when the state update is completed
            return;
        }

        layoutPending = false;
        layoutTimer.cancel();
        try {
            currentDependencyTree = new LayoutDependencyTree(connection);
            doLayout();
        } finally {
            currentDependencyTree = null;
        }
    }

    /**
     * Called once per iteration in the layout loop before size calculations so
     * different browsers quirks can be handled. Mainly this exists for legacy
     * reasons.
     */
    protected void performBrowserLayoutHacks() {
        // Permutations implement this
    }

    private void doLayout() {
        getLogger().info("Starting layout phase");
        Profiler.enter("LayoutManager phase init");

        FastStringMap<Integer> layoutCounts = FastStringMap.create();

        int passes = 0;
        Duration totalDuration = new Duration();

        ConnectorMap connectorMap = ConnectorMap.get(connection);

        JsArrayString dump = needsHorizontalLayout.dump();
        int dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            String layoutId = dump.get(i);
            currentDependencyTree.setNeedsHorizontalLayout(layoutId, true);
        }

        dump = needsVerticalLayout.dump();
        dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            String layoutId = dump.get(i);
            currentDependencyTree.setNeedsVerticalLayout(layoutId, true);
        }
        needsHorizontalLayout = FastStringSet.create();
        needsVerticalLayout = FastStringSet.create();

        dump = needsMeasure.dump();
        dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            ServerConnector connector = connectorMap.getConnector(dump.get(i));
            if (connector != null) {
                currentDependencyTree.setNeedsMeasure((ComponentConnector) connector, true);
            }
        }
        needsMeasure = FastStringSet.create();

        measureNonConnectors();

        Profiler.leave("LayoutManager phase init");

        while (true) {
            Profiler.enter("Layout pass");
            passes++;

            performBrowserLayoutHacks();

            Profiler.enter("Layout measure connectors");
            int measuredConnectorCount = measureConnectors(currentDependencyTree, everythingNeedsMeasure);
            Profiler.leave("Layout measure connectors");

            everythingNeedsMeasure = false;
            if (measuredConnectorCount == 0) {
                getLogger().info("No more changes in pass " + passes);
                Profiler.leave("Layout pass");
                break;
            }

            int firedListeners = 0;
            if (!listenersToFire.isEmpty()) {
                HashSet<Element> listenersCopy = new HashSet<Element>(listenersToFire);
                listenersToFire.clear();
                firedListeners = listenersToFire.size();
                Profiler.enter("Layout fire resize events");
                for (Element element : listenersCopy) {
                    Collection<ElementResizeListener> listeners = elementResizeListeners.get(element);
                    if (listeners != null) {
                        Profiler.enter("Layout fire resize events - listeners not null");
                        Profiler.enter("ElementResizeListener.onElementResize copy list");
                        ElementResizeListener[] array = listeners
                                .toArray(new ElementResizeListener[listeners.size()]);
                        Profiler.leave("ElementResizeListener.onElementResize copy list");
                        ElementResizeEvent event = new ElementResizeEvent(this, element);
                        for (ElementResizeListener listener : array) {
                            try {
                                String key = null;
                                if (Profiler.isEnabled()) {
                                    Profiler.enter("ElementResizeListener.onElementResize construct profiler key");
                                    key = "ElementResizeListener.onElementResize for "
                                            + listener.getClass().getSimpleName();
                                    Profiler.leave("ElementResizeListener.onElementResize construct profiler key");
                                    Profiler.enter(key);
                                }

                                listener.onElementResize(event);
                                if (Profiler.isEnabled()) {
                                    Profiler.leave(key);
                                }
                            } catch (RuntimeException e) {
                                getLogger().log(Level.SEVERE, "Error in resize listener", e);
                            }
                        }
                        Profiler.leave("Layout fire resize events - listeners not null");
                    }
                }
                Profiler.leave("Layout fire resize events");
            }

            Profiler.enter("LayoutManager handle ManagedLayout");

            FastStringSet updatedSet = FastStringSet.create();

            int layoutCount = 0;
            while (currentDependencyTree.hasHorizontalConnectorToLayout()
                    || currentDependencyTree.hasVerticaConnectorToLayout()) {

                JsArrayString layoutTargets = currentDependencyTree.getHorizontalLayoutTargetsJsArray();
                int length = layoutTargets.length();
                for (int i = 0; i < length; i++) {
                    ManagedLayout layout = (ManagedLayout) connectorMap.getConnector(layoutTargets.get(i));
                    if (layout instanceof DirectionalManagedLayout) {
                        currentDependencyTree.markAsHorizontallyLayouted(layout);
                        DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layoutHorizontally() for " + cl.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            cl.layoutHorizontally();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE, "Error in ManagedLayout handling", e);
                        }
                        countLayout(layoutCounts, cl);
                    } else {
                        currentDependencyTree.markAsHorizontallyLayouted(layout);
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        SimpleManagedLayout rr = (SimpleManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layout() for " + rr.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            rr.layout();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE, "Error in SimpleManagedLayout (horizontal) handling", e);

                        }
                        countLayout(layoutCounts, rr);
                    }
                    if (DEBUG_LOGGING) {
                        updatedSet.add(layout.getConnectorId());
                    }
                }

                layoutTargets = currentDependencyTree.getVerticalLayoutTargetsJsArray();
                length = layoutTargets.length();
                for (int i = 0; i < length; i++) {
                    ManagedLayout layout = (ManagedLayout) connectorMap.getConnector(layoutTargets.get(i));
                    if (layout instanceof DirectionalManagedLayout) {
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layoutVertically() for " + cl.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            cl.layoutVertically();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE, "Error in DirectionalManagedLayout handling", e);
                        }
                        countLayout(layoutCounts, cl);
                    } else {
                        currentDependencyTree.markAsHorizontallyLayouted(layout);
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        SimpleManagedLayout rr = (SimpleManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layout() for " + rr.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            rr.layout();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE, "Error in SimpleManagedLayout (vertical) handling", e);
                        }
                        countLayout(layoutCounts, rr);
                    }
                    if (DEBUG_LOGGING) {
                        updatedSet.add(layout.getConnectorId());
                    }
                }
            }

            Profiler.leave("LayoutManager handle ManagedLayout");

            if (DEBUG_LOGGING) {
                JsArrayString changedCids = updatedSet.dump();

                StringBuilder b = new StringBuilder("  ");
                b.append(changedCids.length());
                b.append(" requestLayout invocations ");
                if (changedCids.length() < 30) {
                    for (int i = 0; i < changedCids.length(); i++) {
                        if (i != 0) {
                            b.append(", ");
                        } else {
                            b.append(": ");
                        }
                        String connectorString = changedCids.get(i);
                        if (changedCids.length() < 10) {
                            ServerConnector connector = ConnectorMap.get(connection).getConnector(connectorString);
                            connectorString = Util.getConnectorString(connector);
                        }
                        b.append(connectorString);
                    }
                }
                getLogger().info(b.toString());
            }

            Profiler.leave("Layout pass");

            getLogger().info("Pass " + passes + " measured " + measuredConnectorCount + " elements, fired "
                    + firedListeners + " listeners and did " + layoutCount + " layouts.");

            if (passes > 100) {
                getLogger().severe(LOOP_ABORT_MESSAGE);
                if (ApplicationConfiguration.isDebugMode()) {
                    VNotification
                            .createNotification(VNotification.DELAY_FOREVER,
                                    connection.getUIConnector().getWidget())
                            .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED, "error");
                }
                break;
            }
        }

        Profiler.enter("layout PostLayoutListener");
        JsArrayObject<ComponentConnector> componentConnectors = connectorMap.getComponentConnectorsAsJsArray();
        int size = componentConnectors.size();
        for (int i = 0; i < size; i++) {
            ComponentConnector connector = componentConnectors.get(i);
            if (connector instanceof PostLayoutListener) {
                String key = null;
                if (Profiler.isEnabled()) {
                    key = "layout PostLayoutListener for " + connector.getClass().getSimpleName();
                    Profiler.enter(key);
                }

                ((PostLayoutListener) connector).postLayout();

                if (Profiler.isEnabled()) {
                    Profiler.leave(key);
                }
            }
        }
        Profiler.leave("layout PostLayoutListener");

        // Ensure temporary variables are cleaned
        if (!pendingOverflowFixes.isEmpty()) {
            getLogger().warning(
                    "pendingOverflowFixes is not empty at the end of doLayout: " + pendingOverflowFixes.dump());
            pendingOverflowFixes = FastStringSet.create();
        }

        getLogger().info("Total layout phase time: " + totalDuration.elapsedMillis() + "ms");
    }

    private void logConnectorStatus(int connectorId) {
        currentDependencyTree.logDependencyStatus(
                (ComponentConnector) ConnectorMap.get(connection).getConnector(Integer.toString(connectorId)));
    }

    private int measureConnectors(LayoutDependencyTree layoutDependencyTree, boolean measureAll) {
        Profiler.enter("Layout overflow fix handling");
        JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes.dump();
        int pendingOverflowCount = pendingOverflowConnectorsIds.length();
        ConnectorMap connectorMap = ConnectorMap.get(connection);
        if (pendingOverflowCount > 0) {
            Map<Element, String> originalOverflows = new HashMap<>();

            FastStringSet delayedOverflowFixes = FastStringSet.create();

            // First set overflow to hidden (and save previous value so it can
            // be restored later)
            for (int i = 0; i < pendingOverflowCount; i++) {
                String connectorId = pendingOverflowConnectorsIds.get(i);
                ComponentConnector componentConnector = (ComponentConnector) connectorMap.getConnector(connectorId);

                if (delayOverflowFix(componentConnector)) {
                    delayedOverflowFixes.add(connectorId);
                    continue;
                }

                if (DEBUG_LOGGING) {
                    getLogger().info("Doing overflow fix for " + Util.getConnectorString(componentConnector)
                            + " in " + Util.getConnectorString(componentConnector.getParent()));
                }
                Profiler.enter("Overflow fix apply");

                Element parentElement = componentConnector.getWidget().getElement().getParentElement();
                Style style = parentElement.getStyle();
                String originalOverflow = style.getOverflow();

                if (originalOverflow != null && !originalOverflows.containsKey(parentElement)) {
                    // Store original value for restore, but only the first time
                    // the value is changed
                    originalOverflows.put(parentElement, originalOverflow);
                }

                style.setOverflow(Overflow.HIDDEN);
                Profiler.leave("Overflow fix apply");
            }

            pendingOverflowFixes.removeAll(delayedOverflowFixes);

            JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump();
            int remainingCount = remainingOverflowFixIds.length();

            Profiler.enter("Overflow fix reflow");
            // Then ensure all scrolling elements are reflowed by measuring
            for (int i = 0; i < remainingCount; i++) {
                ComponentConnector componentConnector = (ComponentConnector) connectorMap
                        .getConnector(remainingOverflowFixIds.get(i));
                componentConnector.getWidget().getElement().getParentElement().getOffsetHeight();
            }
            Profiler.leave("Overflow fix reflow");

            Profiler.enter("Overflow fix restore");
            // Finally restore old overflow value and update bookkeeping
            for (int i = 0; i < remainingCount; i++) {
                String connectorId = remainingOverflowFixIds.get(i);
                ComponentConnector componentConnector = (ComponentConnector) connectorMap.getConnector(connectorId);
                Element parentElement = componentConnector.getWidget().getElement().getParentElement();
                parentElement.getStyle().setProperty("overflow", originalOverflows.get(parentElement));

                layoutDependencyTree.setNeedsMeasure(componentConnector, true);
            }
            Profiler.leave("Overflow fix restore");

            if (!pendingOverflowFixes.isEmpty()) {
                getLogger().info("Did overflow fix for " + remainingCount + " elements");
            }
            pendingOverflowFixes = delayedOverflowFixes;
        }
        Profiler.leave("Layout overflow fix handling");

        int measureCount = 0;
        if (measureAll) {
            Profiler.enter("Layout measureAll");
            JsArrayObject<ComponentConnector> allConnectors = connectorMap.getComponentConnectorsAsJsArray();
            int size = allConnectors.size();

            // Find connectors that should actually be measured
            JsArrayObject<ComponentConnector> connectors = JsArrayObject.createArray().cast();
            for (int i = 0; i < size; i++) {
                ComponentConnector candidate = allConnectors.get(i);
                if (!Util.shouldSkipMeasurementOfConnector(candidate)
                        && needsMeasure(candidate.getWidget().getElement())) {
                    connectors.add(candidate);
                }
            }

            int connectorCount = connectors.size();
            for (int i = 0; i < connectorCount; i++) {
                measureConnector(connectors.get(i));
            }
            for (int i = 0; i < connectorCount; i++) {
                layoutDependencyTree.setNeedsMeasure(connectors.get(i), false);
            }
            measureCount += connectorCount;

            Profiler.leave("Layout measureAll");
        }

        Profiler.enter("Layout measure from tree");
        while (layoutDependencyTree.hasConnectorsToMeasure()) {
            JsArrayString measureTargets = layoutDependencyTree.getMeasureTargetsJsArray();
            int length = measureTargets.length();
            for (int i = 0; i < length; i++) {
                ComponentConnector connector = (ComponentConnector) connectorMap
                        .getConnector(measureTargets.get(i));
                measureConnector(connector);
                measureCount++;
            }
            for (int i = 0; i < length; i++) {
                ComponentConnector connector = (ComponentConnector) connectorMap
                        .getConnector(measureTargets.get(i));
                layoutDependencyTree.setNeedsMeasure(connector, false);
            }
        }
        Profiler.leave("Layout measure from tree");

        return measureCount;
    }

    /*
     * Delay the overflow fix if the involved connectors might still change
     */
    private boolean delayOverflowFix(ComponentConnector componentConnector) {
        if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) {
            return true;
        }
        ServerConnector parent = componentConnector.getParent();
        if (parent instanceof ComponentConnector
                && !currentDependencyTree.noMoreChangesExpected((ComponentConnector) parent)) {
            return true;
        }

        return false;
    }

    private void measureConnector(ComponentConnector connector) {
        Profiler.enter("LayoutManager.measureConnector");
        Element element = connector.getWidget().getElement();
        MeasuredSize measuredSize = getMeasuredSize(element);
        MeasureResult measureResult = measuredAndUpdate(element, measuredSize);

        if (measureResult.isChanged()) {
            onConnectorChange(connector, measureResult.isWidthChanged(), measureResult.isHeightChanged());
        }
        Profiler.leave("LayoutManager.measureConnector");
    }

    private void onConnectorChange(ComponentConnector connector, boolean widthChanged, boolean heightChanged) {
        Profiler.enter("LayoutManager.onConnectorChange");
        Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix");
        setNeedsOverflowFix(connector);
        Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix");
        Profiler.enter("LayoutManager.onConnectorChange heightChanged");
        if (heightChanged) {
            currentDependencyTree.markHeightAsChanged(connector);
        }
        Profiler.leave("LayoutManager.onConnectorChange heightChanged");
        Profiler.enter("LayoutManager.onConnectorChange widthChanged");
        if (widthChanged) {
            currentDependencyTree.markWidthAsChanged(connector);
        }
        Profiler.leave("LayoutManager.onConnectorChange widthChanged");
        Profiler.leave("LayoutManager.onConnectorChange");
    }

    private void setNeedsOverflowFix(ComponentConnector connector) {
        // IE9 doesn't need the original fix, but for some reason it needs this
        if (BrowserInfo.get().requiresOverflowAutoFix()) {
            ComponentConnector scrollingBoundary = currentDependencyTree.getScrollingBoundary(connector);
            if (scrollingBoundary != null) {
                pendingOverflowFixes.add(scrollingBoundary.getConnectorId());
            }
        }
    }

    private void measureNonConnectors() {
        Profiler.enter("LayoutManager.measureNonConenctors");
        for (Element element : measuredNonConnectorElements) {
            measuredAndUpdate(element, getMeasuredSize(element, null));
        }
        Profiler.leave("LayoutManager.measureNonConenctors");
        getLogger().info("Measured " + measuredNonConnectorElements.size() + " non connector elements");
    }

    private MeasureResult measuredAndUpdate(Element element, MeasuredSize measuredSize) {
        MeasureResult measureResult = measuredSize.measure(element);
        if (measureResult.isChanged()) {
            notifyListenersAndDepdendents(element, measureResult.isWidthChanged(), measureResult.isHeightChanged());
        }
        return measureResult;
    }

    private void notifyListenersAndDepdendents(Element element, boolean widthChanged, boolean heightChanged) {
        assert widthChanged || heightChanged;

        Profiler.enter("LayoutManager.notifyListenersAndDepdendents");

        MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
        JsArrayString dependents = measuredSize.getDependents();
        for (int i = 0; i < dependents.length(); i++) {
            String pid = dependents.get(i);
            if (pid != null) {
                if (heightChanged) {
                    currentDependencyTree.setNeedsVerticalLayout(pid, true);
                }
                if (widthChanged) {
                    currentDependencyTree.setNeedsHorizontalLayout(pid, true);
                }
            }
        }
        if (elementResizeListeners.containsKey(element)) {
            listenersToFire.add(element);
        }
        Profiler.leave("LayoutManager.notifyListenersAndDepdendents");
    }

    private static boolean isManagedLayout(ComponentConnector connector) {
        return connector instanceof ManagedLayout;
    }

    public void forceLayout() {
        ConnectorMap connectorMap = connection.getConnectorMap();
        JsArrayObject<ComponentConnector> componentConnectors = connectorMap.getComponentConnectorsAsJsArray();
        int size = componentConnectors.size();
        for (int i = 0; i < size; i++) {
            ComponentConnector connector = componentConnectors.get(i);
            if (connector instanceof ManagedLayout) {
                setNeedsLayout((ManagedLayout) connector);
            }
        }
        setEverythingNeedsMeasure();
        layoutNow();
    }

    /**
     * Marks that a ManagedLayout should be layouted in the next layout phase
     * even if none of the elements managed by the layout have been resized.
     * <p>
     * This method should not be invoked during a layout phase since it only
     * controls what will happen in the beginning of the next phase. If you want
     * to explicitly cause some layout to be considered in an ongoing layout
     * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
     * instead.
     *
     * @param layout
     *            the managed layout that should be layouted
     */
    public final void setNeedsLayout(ManagedLayout layout) {
        setNeedsHorizontalLayout(layout);
        setNeedsVerticalLayout(layout);
    }

    /**
     * Marks that a ManagedLayout should be layouted horizontally in the next
     * layout phase even if none of the elements managed by the layout have been
     * resized horizontally.
     * <p>
     * For SimpleManagedLayout which is always layouted in both directions, this
     * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
     * <p>
     * This method should not be invoked during a layout phase since it only
     * controls what will happen in the beginning of the next phase. If you want
     * to explicitly cause some layout to be considered in an ongoing layout
     * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
     * instead.
     *
     * @param layout
     *            the managed layout that should be layouted
     */
    public final void setNeedsHorizontalLayout(ManagedLayout layout) {
        if (isLayoutRunning()) {
            getLogger().warning("setNeedsHorizontalLayout should not be run while a layout phase is in progress.");
        }
        needsHorizontalLayout.add(layout.getConnectorId());
    }

    /**
     * Marks that a ManagedLayout should be layouted vertically in the next
     * layout phase even if none of the elements managed by the layout have been
     * resized vertically.
     * <p>
     * For SimpleManagedLayout which is always layouted in both directions, this
     * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
     * <p>
     * This method should not be invoked during a layout phase since it only
     * controls what will happen in the beginning of the next phase. If you want
     * to explicitly cause some layout to be considered in an ongoing layout
     * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
     * instead.
     *
     * @param layout
     *            the managed layout that should be layouted
     */
    public final void setNeedsVerticalLayout(ManagedLayout layout) {
        if (isLayoutRunning()) {
            getLogger().warning("setNeedsVerticalLayout should not be run while a layout phase is in progress.");
        }
        needsVerticalLayout.add(layout.getConnectorId());
    }

    /**
     * Gets the outer height (including margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     * <p>
     * The value returned by this method is always rounded up. To get the exact
     * outer width, use {@link #getOuterHeightDouble(Element)}
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured outer height (including margins, paddings and
     *         borders) of the element in pixels.
     */
    public final int getOuterHeight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return (int) Math.ceil(getMeasuredSize(element, nullSize).getOuterHeight());
    }

    /**
     * Gets the outer height (including margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     *
     * @since 7.5.1
     * @param element
     *            the element to get the measured size for
     * @return the measured outer height (including margins, paddings and
     *         borders) of the element in pixels.
     */
    public final double getOuterHeightDouble(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getOuterHeight();
    }

    /**
     * Gets the outer width (including margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     * <p>
     * The value returned by this method is always rounded up. To get the exact
     * outer width, use {@link #getOuterWidthDouble(Element)}
     *
     * @since 7.5.1
     * @param element
     *            the element to get the measured size for
     * @return the measured outer width (including margins, paddings and
     *         borders) of the element in pixels.
     */
    public final int getOuterWidth(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return (int) Math.ceil(getMeasuredSize(element, nullSize).getOuterWidth());
    }

    /**
     * Gets the outer width (including margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured outer width (including margins, paddings and
     *         borders) of the element in pixels.
     */
    public final double getOuterWidthDouble(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getOuterWidth();
    }

    /**
     * Gets the inner height (excluding margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     * <p>
     * The value returned by this method is always rounded up. To get the exact
     * outer width, use {@link #getInnerHeightDouble(Element)}
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured inner height (excluding margins, paddings and
     *         borders) of the element in pixels.
     */
    public final int getInnerHeight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return (int) Math.ceil(getMeasuredSize(element, nullSize).getInnerHeight());
    }

    /**
     * Gets the inner height (excluding margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     *
     * @since 7.5.1
     * @param element
     *            the element to get the measured size for
     * @return the measured inner height (excluding margins, paddings and
     *         borders) of the element in pixels.
     */
    public final double getInnerHeightDouble(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getInnerHeight();
    }

    /**
     * Gets the inner width (excluding margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     * <p>
     * The value returned by this method is always rounded up. To get the exact
     * outer width, use {@link #getOuterHeightDouble(Element)}
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured inner width (excluding margins, paddings and
     *         borders) of the element in pixels.
     */
    public final int getInnerWidth(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return (int) Math.ceil(getMeasuredSize(element, nullSize).getInnerWidth());
    }

    /**
     * Gets the inner width (excluding margins, paddings and borders) of the
     * given element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * -1 is returned if the element has not been measured. If 0 is returned, it
     * might indicate that the element is not attached to the DOM.
     *
     * @since 7.5.1
     * @param element
     *            the element to get the measured size for
     * @return the measured inner width (excluding margins, paddings and
     *         borders) of the element in pixels.
     */
    public final double getInnerWidthDouble(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getInnerWidth();
    }

    /**
     * Gets the border height (top border + bottom border) of the given element,
     * provided that it has been measured. These elements are guaranteed to be
     * measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured border height (top border + bottom border) of the
     *         element in pixels.
     */
    public final int getBorderHeight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderHeight();
    }

    /**
     * Gets the padding height (top padding + bottom padding) of the given
     * element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured padding height (top padding + bottom padding) of the
     *         element in pixels.
     */
    public int getPaddingHeight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingHeight();
    }

    /**
     * Gets the border width (left border + right border) of the given element,
     * provided that it has been measured. These elements are guaranteed to be
     * measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured border width (left border + right border) of the
     *         element in pixels.
     */
    public int getBorderWidth(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderWidth();
    }

    /**
     * Gets the top border of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured top border of the element in pixels.
     */
    public int getBorderTop(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderTop();
    }

    /**
     * Gets the left border of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured left border of the element in pixels.
     */
    public int getBorderLeft(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderLeft();
    }

    /**
     * Gets the bottom border of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured bottom border of the element in pixels.
     */
    public int getBorderBottom(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderBottom();
    }

    /**
     * Gets the right border of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured right border of the element in pixels.
     */
    public int getBorderRight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getBorderRight();
    }

    /**
     * Gets the padding width (left padding + right padding) of the given
     * element, provided that it has been measured. These elements are
     * guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured padding width (left padding + right padding) of the
     *         element in pixels.
     */
    public int getPaddingWidth(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingWidth();
    }

    /**
     * Gets the top padding of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured top padding of the element in pixels.
     */
    public int getPaddingTop(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingTop();
    }

    /**
     * Gets the left padding of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured left padding of the element in pixels.
     */
    public int getPaddingLeft(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingLeft();
    }

    /**
     * Gets the bottom padding of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured bottom padding of the element in pixels.
     */
    public int getPaddingBottom(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingBottom();
    }

    /**
     * Gets the right padding of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured right padding of the element in pixels.
     */
    public int getPaddingRight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getPaddingRight();
    }

    /**
     * Gets the top margin of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured top margin of the element in pixels.
     */
    public int getMarginTop(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getMarginTop();
    }

    /**
     * Gets the right margin of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured right margin of the element in pixels.
     */
    public int getMarginRight(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getMarginRight();
    }

    /**
     * Gets the bottom margin of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured bottom margin of the element in pixels.
     */
    public int getMarginBottom(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getMarginBottom();
    }

    /**
     * Gets the left margin of the given element, provided that it has been
     * measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured size for
     * @return the measured left margin of the element in pixels.
     */
    public int getMarginLeft(Element element) {
        assert needsMeasure(element) : "Getting measurement for element that is not measured";
        return getMeasuredSize(element, nullSize).getMarginLeft();
    }

    /**
     * Gets the combined top & bottom margin of the given element, provided that
     * they have been measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured margin for
     * @return the measured top+bottom margin of the element in pixels.
     */
    public int getMarginHeight(Element element) {
        return getMarginTop(element) + getMarginBottom(element);
    }

    /**
     * Gets the combined left & right margin of the given element, provided that
     * they have been measured. These elements are guaranteed to be measured:
     * <ul>
     * <li>ManagedLayouts and their child Connectors
     * <li>Elements for which there is at least one ElementResizeListener
     * <li>Elements for which at least one ManagedLayout has registered a
     * dependency
     * </ul>
     *
     * A negative number is returned if the element has not been measured. If 0
     * is returned, it might indicate that the element is not attached to the
     * DOM.
     *
     * @param element
     *            the element to get the measured margin for
     * @return the measured left+right margin of the element in pixels.
     */
    public int getMarginWidth(Element element) {
        return getMarginLeft(element) + getMarginRight(element);
    }

    /**
     * Registers the outer height (including margins, borders and paddings) of a
     * component. This can be used as an optimization by ManagedLayouts; by
     * informing the LayoutManager about what size a component will have, the
     * layout propagation can continue directly without first measuring the
     * potentially resized elements.
     *
     * @param component
     *            the component for which the size is reported
     * @param outerHeight
     *            the new outer height (including margins, borders and paddings)
     *            of the component in pixels
     */
    public void reportOuterHeight(ComponentConnector component, int outerHeight) {
        Element element = component.getWidget().getElement();
        MeasuredSize measuredSize = getMeasuredSize(element);
        if (isLayoutRunning()) {
            boolean heightChanged = measuredSize.setOuterHeight(outerHeight);

            if (heightChanged) {
                onConnectorChange(component, false, true);
                notifyListenersAndDepdendents(element, false, true);
            }
            currentDependencyTree.setNeedsVerticalMeasure(component, false);
        } else if (measuredSize.getOuterHeight() != outerHeight) {
            setNeedsMeasure(component);
        }
    }

    /**
     * Registers the height reserved for a relatively sized component. This can
     * be used as an optimization by ManagedLayouts; by informing the
     * LayoutManager about what size a component will have, the layout
     * propagation can continue directly without first measuring the potentially
     * resized elements.
     *
     * @param component
     *            the relatively sized component for which the size is reported
     * @param assignedHeight
     *            the inner height of the relatively sized component's parent
     *            element in pixels
     */
    public void reportHeightAssignedToRelative(ComponentConnector component, int assignedHeight) {
        assert component.isRelativeHeight();

        float percentSize = parsePercent(component.getState().height == null ? "" : component.getState().height);
        int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));

        reportOuterHeight(component, effectiveHeight);
    }

    /**
     * Registers the width reserved for a relatively sized component. This can
     * be used as an optimization by ManagedLayouts; by informing the
     * LayoutManager about what size a component will have, the layout
     * propagation can continue directly without first measuring the potentially
     * resized elements.
     *
     * @param component
     *            the relatively sized component for which the size is reported
     * @param assignedWidth
     *            the inner width of the relatively sized component's parent
     *            element in pixels
     */
    public void reportWidthAssignedToRelative(ComponentConnector component, int assignedWidth) {
        assert component.isRelativeWidth();

        float percentSize = parsePercent(component.getState().width == null ? "" : component.getState().width);
        int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));

        reportOuterWidth(component, effectiveWidth);
    }

    private static float parsePercent(String size) {
        return Float.parseFloat(size.substring(0, size.length() - 1));
    }

    /**
     * Registers the outer width (including margins, borders and paddings) of a
     * component. This can be used as an optimization by ManagedLayouts; by
     * informing the LayoutManager about what size a component will have, the
     * layout propagation can continue directly without first measuring the
     * potentially resized elements.
     *
     * @param component
     *            the component for which the size is reported
     * @param outerWidth
     *            the new outer width (including margins, borders and paddings)
     *            of the component in pixels
     */
    public void reportOuterWidth(ComponentConnector component, int outerWidth) {
        Element element = component.getWidget().getElement();
        MeasuredSize measuredSize = getMeasuredSize(element);
        if (isLayoutRunning()) {
            boolean widthChanged = measuredSize.setOuterWidth(outerWidth);

            if (widthChanged) {
                onConnectorChange(component, true, false);
                notifyListenersAndDepdendents(element, true, false);
            }
            currentDependencyTree.setNeedsHorizontalMeasure(component, false);
        } else if (measuredSize.getOuterWidth() != outerWidth) {
            setNeedsMeasure(component);
        }
    }

    /**
     * Adds a listener that will be notified whenever the size of a specific
     * element changes. Adding a listener to an element also ensures that all
     * sizes for that element will be available starting from the next layout
     * phase.
     *
     * @param element
     *            the element that should be checked for size changes
     * @param listener
     *            an ElementResizeListener that will be informed whenever the
     *            size of the target element has changed
     */
    public void addElementResizeListener(Element element, ElementResizeListener listener) {
        Collection<ElementResizeListener> listeners = elementResizeListeners.get(element);
        if (listeners == null) {
            listeners = new HashSet<>();
            elementResizeListeners.put(element, listeners);
            ensureMeasured(element);
        }
        listeners.add(listener);
    }

    /**
     * Removes an element resize listener from the provided element. This might
     * cause this LayoutManager to stop tracking the size of the element if no
     * other sources are interested in the size.
     *
     * @param element
     *            the element to which the element resize listener was
     *            previously added
     * @param listener
     *            the ElementResizeListener that should no longer get informed
     *            about size changes to the target element.
     */
    public void removeElementResizeListener(Element element, ElementResizeListener listener) {
        Collection<ElementResizeListener> listeners = elementResizeListeners.get(element);
        if (listeners != null) {
            listeners.remove(listener);
            if (listeners.isEmpty()) {
                elementResizeListeners.remove(element);
                stopMeasuringIfUnecessary(element);
            }
        }
    }

    private void stopMeasuringIfUnecessary(Element element) {
        if (!needsMeasure(element)) {
            measuredNonConnectorElements.remove(element);
            setMeasuredSize(element, null);
        }
    }

    /**
     * Informs this LayoutManager that the size of a component might have
     * changed. This method should be used whenever the size of an individual
     * component might have changed from outside of Vaadin's normal update
     * phase, e.g. when an icon has been loaded or when the user resizes some
     * part of the UI using the mouse.
     * <p>
     * To set an entire component hierarchy to be measured, use
     * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead.
     * <p>
     * If there is no upcoming layout phase, a new layout phase is scheduled.
     *
     * @param component
     *            the component whose size might have changed.
     */
    public void setNeedsMeasure(ComponentConnector component) {
        if (isLayoutRunning()) {
            currentDependencyTree.setNeedsMeasure(component, true);
        } else {
            needsMeasure.add(component.getConnectorId());
            layoutLater();
        }
    }

    /**
     * Informs this LayoutManager that some sizes in a component hierarchy might
     * have changed. This method should be used whenever the size of any child
     * component might have changed from outside of Vaadin's normal update
     * phase, e.g. when a CSS class name related to sizing has been changed.
     * <p>
     * To set a single component to be measured, use
     * {@link #setNeedsMeasure(ComponentConnector)} instead.
     * <p>
     * If there is no upcoming layout phase, a new layout phase is scheduled.
     *
     * @since 7.2
     * @param component
     *            the component at the root of the component hierarchy to
     *            measure
     */
    public void setNeedsMeasureRecursively(ComponentConnector component) {
        setNeedsMeasure(component);

        if (component instanceof HasComponentsConnector) {
            HasComponentsConnector hasComponents = (HasComponentsConnector) component;
            for (ComponentConnector child : hasComponents.getChildComponents()) {
                setNeedsMeasureRecursively(child);
            }
        }
    }

    public void setEverythingNeedsMeasure() {
        everythingNeedsMeasure = true;
    }

    private static Logger getLogger() {
        return Logger.getLogger(LayoutManager.class.getName());
    }

    /**
     * Checks if there is something waiting for a layout to take place.
     *
     * @since 7.5.6
     * @return true if there are connectors waiting for measurement or layout,
     *         false otherwise
     */
    public boolean isLayoutNeeded() {
        if (!needsHorizontalLayout.isEmpty() || !needsVerticalLayout.isEmpty()) {
            return true;
        }
        if (!needsMeasure.isEmpty()) {
            return true;
        }

        if (everythingNeedsMeasure) {
            return true;
        }

        return false;
    }
}