diff options
Diffstat (limited to 'WebCore/page')
239 files changed, 34579 insertions, 0 deletions
diff --git a/WebCore/page/AXObjectCache.h b/WebCore/page/AXObjectCache.h new file mode 100644 index 0000000..d73a5a3 --- /dev/null +++ b/WebCore/page/AXObjectCache.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003, 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AXObjectCache_h +#define AXObjectCache_h + +#include <limits.h> + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +#ifdef __OBJC__ +@class WebCoreAXObject; +@class WebCoreTextMarker; +#else +class WebCoreAXObject; +class WebCoreTextMarker; +#endif + +namespace WebCore { + + class RenderObject; + class String; + class VisiblePosition; + + typedef unsigned AXID; + + struct AXIDHashTraits : WTF::GenericHashTraits<unsigned> { + static TraitType deletedValue() { return UINT_MAX; } + }; + + class AXObjectCache { + public: + ~AXObjectCache(); + + WebCoreAXObject* get(RenderObject*); + void remove(RenderObject*); + + void removeAXID(WebCoreAXObject*); + + WebCoreTextMarker* textMarkerForVisiblePosition(const VisiblePosition&); + VisiblePosition visiblePositionForTextMarker(WebCoreTextMarker*); + + void childrenChanged(RenderObject*); + void postNotification(RenderObject*, const String& message); + void postNotificationToElement(RenderObject*, const String& message); + void handleFocusedUIElementChanged(); + +#if PLATFORM(MAC) + static void enableAccessibility() { gAccessibilityEnabled = true; } + static bool accessibilityEnabled() { return gAccessibilityEnabled; } +#else + static bool accessibilityEnabled() { return false; } +#endif + + private: +#if PLATFORM(MAC) + static bool gAccessibilityEnabled; +#endif + + AXID getAXID(WebCoreAXObject*); + + HashMap<RenderObject*, WebCoreAXObject*> m_objects; + HashSet<AXID, IntHash<AXID>, AXIDHashTraits> m_idsInUse; + }; + +#if !PLATFORM(MAC) + inline AXObjectCache::~AXObjectCache() { } + inline WebCoreAXObject* AXObjectCache::get(RenderObject*) { return 0; } + inline void AXObjectCache::remove(RenderObject*) { } + inline void AXObjectCache::removeAXID(WebCoreAXObject*) { } + inline void AXObjectCache::childrenChanged(RenderObject*) { } + inline void AXObjectCache::postNotification(RenderObject*, const String&) { } + inline void AXObjectCache::postNotificationToElement(RenderObject*, const String&) { } + inline void AXObjectCache::handleFocusedUIElementChanged() { } +#endif + +} + +#endif diff --git a/WebCore/page/AbstractView.idl b/WebCore/page/AbstractView.idl new file mode 100644 index 0000000..ca02c82 --- /dev/null +++ b/WebCore/page/AbstractView.idl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module views { + + // Introduced in DOM Level 2: + interface AbstractView { + readonly attribute Document document; + }; + +} diff --git a/WebCore/page/AnimationController.cpp b/WebCore/page/AnimationController.cpp new file mode 100644 index 0000000..9d7a1f7 --- /dev/null +++ b/WebCore/page/AnimationController.cpp @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AnimationController.h" + +#include "CSSPropertyNames.h" +#include "Document.h" +#include "FloatConversion.h" +#include "Frame.h" +#include "RenderObject.h" +#include "RenderStyle.h" +#include "SystemTime.h" +#include "Timer.h" + +namespace WebCore { + +static const double cAnimationTimerDelay = 0.025; + +struct CurveData { + CurveData(double p1x, double p1y, double p2x, double p2y) + { + // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). + cx = 3.0 * p1x; + bx = 3.0 * (p2x - p1x) - cx; + ax = 1.0 - cx -bx; + + cy = 3.0 * p1y; + by = 3.0 * (p2y - p1y) - cy; + ay = 1.0 - cy - by; + } + + double sampleCurveX(double t) + { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((ax * t + bx) * t + cx) * t; + } + + double sampleCurveY(double t) + { + return ((ay * t + by) * t + cy) * t; + } + + double sampleCurveDerivativeX(double t) + { + return (3.0 * ax * t + 2.0 * bx) * t + cx; + } + + // Given an x value, find a parametric value it came from. + double solveCurveX(double x, double epsilon) + { + double t0; + double t1; + double t2; + double x2; + double d2; + int i; + + // First try a few iterations of Newton's method -- normally very fast. + for (t2 = x, i = 0; i < 8; i++) { + x2 = sampleCurveX(t2) - x; + if (fabs (x2) < epsilon) + return t2; + d2 = sampleCurveDerivativeX(t2); + if (fabs(d2) < 1e-6) + break; + t2 = t2 - x2 / d2; + } + + // Fall back to the bisection method for reliability. + t0 = 0.0; + t1 = 1.0; + t2 = x; + + if (t2 < t0) + return t0; + if (t2 > t1) + return t1; + + while (t0 < t1) { + x2 = sampleCurveX(t2); + if (fabs(x2 - x) < epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 - t0) * .5 + t0; + } + + // Failure. + return t2; + } + +private: + double ax; + double bx; + double cx; + + double ay; + double by; + double cy; +}; + +// The epsilon value we pass to solveCurveX given that the animation is going to run over |dur| seconds. The longer the +// animation, the more precision we need in the timing function result to avoid ugly discontinuities. +static inline double solveEpsilon(double duration) { return 1. / (200. * duration); } + +static inline double solveCubicBezierFunction(double p1x, double p1y, double p2x, double p2y, double t, double duration) +{ + // Convert from input time to parametric value in curve, then from + // that to output time. + CurveData c(p1x, p1y, p2x, p2y); + t = c.solveCurveX(t, solveEpsilon(duration)); + t = c.sampleCurveY(t); + return t; +} + +class CompositeImplicitAnimation; + +class ImplicitAnimation : public Noncopyable { +public: + ImplicitAnimation(const Transition*); + ~ImplicitAnimation(); + + void animate(CompositeImplicitAnimation*, RenderObject*, RenderStyle* currentStyle, RenderStyle* targetStyle, RenderStyle*& animatedStyle); + + void reset(RenderObject*, RenderStyle* from, RenderStyle* to); + + double progress() const; + + bool finished() const { return m_finished; } + +private: + // The two styles that we are blending. + RenderStyle* m_fromStyle; + RenderStyle* m_toStyle; + + int m_property; + TimingFunction m_function; + double m_duration; + + int m_repeatCount; + int m_iteration; + + bool m_finished; + double m_startTime; + bool m_paused; + double m_pauseTime; +}; + +class CompositeImplicitAnimation : public Noncopyable { +public: + ~CompositeImplicitAnimation() { deleteAllValues(m_animations); } + + RenderStyle* animate(RenderObject*, RenderStyle* currentStyle, RenderStyle* targetStyle); + + bool animating() const; + + bool hasAnimationForProperty(int prop) const { return m_animations.contains(prop); } + + void reset(RenderObject*); + +private: + HashMap<int, ImplicitAnimation*> m_animations; +}; + +ImplicitAnimation::ImplicitAnimation(const Transition* transition) +: m_fromStyle(0) +, m_toStyle(0) +, m_property(transition->transitionProperty()) +, m_function(transition->transitionTimingFunction()) +, m_duration(transition->transitionDuration() / 1000.0) +, m_repeatCount(transition->transitionRepeatCount()) +, m_iteration(0) +, m_finished(false) +, m_startTime(currentTime()) +, m_paused(false) +, m_pauseTime(m_startTime) +{ +} + +ImplicitAnimation::~ImplicitAnimation() +{ + ASSERT(!m_fromStyle && !m_toStyle); +} + +void ImplicitAnimation::reset(RenderObject* renderer, RenderStyle* from, RenderStyle* to) +{ + if (m_fromStyle) + m_fromStyle->deref(renderer->renderArena()); + if (m_toStyle) + m_toStyle->deref(renderer->renderArena()); + m_fromStyle = from; + if (m_fromStyle) + m_fromStyle->ref(); + m_toStyle = to; + if (m_toStyle) + m_toStyle->ref(); + m_finished = false; + if (from || to) + m_startTime = currentTime(); +} + +double ImplicitAnimation::progress() const +{ + double elapsedTime = currentTime() - m_startTime; + + if (m_finished || !m_duration || elapsedTime >= m_duration) + return 1.0; + + if (m_function.type() == LinearTimingFunction) + return elapsedTime / m_duration; + + // Cubic bezier. + return solveCubicBezierFunction(m_function.x1(), m_function.y1(), + m_function.x2(), m_function.y2(), + elapsedTime / m_duration, m_duration); +} + +static inline int blendFunc(int from, int to, double progress) +{ + return int(from + (to - from) * progress); +} + +static inline double blendFunc(double from, double to, double progress) +{ + return from + (to - from) * progress; +} + +static inline float blendFunc(float from, float to, double progress) +{ + return narrowPrecisionToFloat(from + (to - from) * progress); +} + +static inline Color blendFunc(const Color& from, const Color& to, double progress) +{ + return Color(blendFunc(from.red(), to.red(), progress), + blendFunc(from.green(), to.green(), progress), + blendFunc(from.blue(), to.blue(), progress), + blendFunc(from.alpha(), to.alpha(), progress)); +} + +static inline Length blendFunc(const Length& from, const Length& to, double progress) +{ + return to.blend(from, progress); +} + +static inline IntSize blendFunc(const IntSize& from, const IntSize& to, double progress) +{ + return IntSize(blendFunc(from.width(), to.width(), progress), + blendFunc(from.height(), to.height(), progress)); +} + +static inline ShadowData* blendFunc(const ShadowData* from, const ShadowData* to, double progress) +{ + ASSERT(from && to); + return new ShadowData(blendFunc(from->x, to->x, progress), blendFunc(from->y, to->y, progress), blendFunc(from->blur, to->blur, progress), blendFunc(from->color, to->color, progress)); +} + +static inline TransformOperations blendFunc(const TransformOperations& from, const TransformOperations& to, double progress) +{ + // Blend any operations whose types actually match up. Otherwise don't bother. + unsigned fromSize = from.size(); + unsigned toSize = to.size(); + unsigned size = max(fromSize, toSize); + TransformOperations result; + for (unsigned i = 0; i < size; i++) { + TransformOperation* fromOp = i < fromSize ? from[i].get() : 0; + TransformOperation* toOp = i < toSize ? to[i].get() : 0; + TransformOperation* blendedOp = toOp ? toOp->blend(fromOp, progress) : fromOp->blend(0, progress, true); + result.append(blendedOp); + } + return result; +} + +static inline EVisibility blendFunc(EVisibility from, EVisibility to, double progress) +{ + // Any non-zero result means we consider the object to be visible. Only at 0 do we consider the object to be + // invisible. The invisible value we use (HIDDEN vs. COLLAPSE) depends on the specified from/to values. + double fromVal = from == VISIBLE ? 1. : 0.; + double toVal = to == VISIBLE ? 1. : 0.; + if (fromVal == toVal) + return to; + double result = blendFunc(fromVal, toVal, progress); + return result > 0. ? VISIBLE : (to != VISIBLE ? to : from); +} + +#define BLEND(prop, getter, setter) \ + if (m_property == prop && m_toStyle->getter() != targetStyle->getter()) \ + reset(renderer, currentStyle, targetStyle); \ + \ + if ((m_property == cAnimateAll && !animation->hasAnimationForProperty(prop)) || m_property == prop) { \ + if (m_fromStyle->getter() != m_toStyle->getter()) {\ + m_finished = false; \ + if (!animatedStyle) \ + animatedStyle = new (renderer->renderArena()) RenderStyle(*targetStyle); \ + animatedStyle->setter(blendFunc(m_fromStyle->getter(), m_toStyle->getter(), progress()));\ + if (m_property == prop) \ + return; \ + }\ + }\ + +#define BLEND_MAYBE_INVALID_COLOR(prop, getter, setter) \ + if (m_property == prop && m_toStyle->getter() != targetStyle->getter()) \ + reset(renderer, currentStyle, targetStyle); \ + \ + if ((m_property == cAnimateAll && !animation->hasAnimationForProperty(prop)) || m_property == prop) { \ + Color fromColor = m_fromStyle->getter(); \ + Color toColor = m_toStyle->getter(); \ + if (!fromColor.isValid()) \ + fromColor = m_fromStyle->color(); \ + if (!toColor.isValid()) \ + toColor = m_toStyle->color(); \ + if (fromColor != toColor) {\ + m_finished = false; \ + if (!animatedStyle) \ + animatedStyle = new (renderer->renderArena()) RenderStyle(*targetStyle); \ + animatedStyle->setter(blendFunc(fromColor, toColor, progress()));\ + if (m_property == prop) \ + return; \ + }\ + }\ + +#define BLEND_SHADOW(prop, getter, setter) \ + if (m_property == prop && (!m_toStyle->getter() || !targetStyle->getter() || *m_toStyle->getter() != *targetStyle->getter())) \ + reset(renderer, currentStyle, targetStyle); \ + \ + if ((m_property == cAnimateAll && !animation->hasAnimationForProperty(prop)) || m_property == prop) { \ + if (m_fromStyle->getter() && m_toStyle->getter() && *m_fromStyle->getter() != *m_toStyle->getter()) {\ + m_finished = false; \ + if (!animatedStyle) \ + animatedStyle = new (renderer->renderArena()) RenderStyle(*targetStyle); \ + animatedStyle->setter(blendFunc(m_fromStyle->getter(), m_toStyle->getter(), progress()));\ + if (m_property == prop) \ + return; \ + }\ + } + +void ImplicitAnimation::animate(CompositeImplicitAnimation* animation, RenderObject* renderer, RenderStyle* currentStyle, RenderStyle* targetStyle, RenderStyle*& animatedStyle) +{ + // FIXME: If we have no transition-property, then the only way to tell if our goal state changed is to check + // every single animatable property. For now we'll just diff the styles to ask that question, + // but we should really exclude non-animatable properties. + if (!m_toStyle || (m_property == cAnimateAll && targetStyle->diff(m_toStyle))) + reset(renderer, currentStyle, targetStyle); + + // FIXME: Blow up shorthands so that they can be honored. + m_finished = true; + BLEND(CSS_PROP_LEFT, left, setLeft); + BLEND(CSS_PROP_RIGHT, right, setRight); + BLEND(CSS_PROP_TOP, top, setTop); + BLEND(CSS_PROP_BOTTOM, bottom, setBottom); + BLEND(CSS_PROP_WIDTH, width, setWidth); + BLEND(CSS_PROP_HEIGHT, height, setHeight); + BLEND(CSS_PROP_BORDER_LEFT_WIDTH, borderLeftWidth, setBorderLeftWidth); + BLEND(CSS_PROP_BORDER_RIGHT_WIDTH, borderRightWidth, setBorderRightWidth); + BLEND(CSS_PROP_BORDER_TOP_WIDTH, borderTopWidth, setBorderTopWidth); + BLEND(CSS_PROP_BORDER_BOTTOM_WIDTH, borderBottomWidth, setBorderBottomWidth); + BLEND(CSS_PROP_MARGIN_LEFT, marginLeft, setMarginLeft); + BLEND(CSS_PROP_MARGIN_RIGHT, marginRight, setMarginRight); + BLEND(CSS_PROP_MARGIN_TOP, marginTop, setMarginTop); + BLEND(CSS_PROP_MARGIN_BOTTOM, marginBottom, setMarginBottom); + BLEND(CSS_PROP_PADDING_LEFT, paddingLeft, setPaddingLeft); + BLEND(CSS_PROP_PADDING_RIGHT, paddingRight, setPaddingRight); + BLEND(CSS_PROP_PADDING_TOP, paddingTop, setPaddingTop); + BLEND(CSS_PROP_PADDING_BOTTOM, paddingBottom, setPaddingBottom); + BLEND(CSS_PROP_OPACITY, opacity, setOpacity); + BLEND(CSS_PROP_COLOR, color, setColor); + BLEND(CSS_PROP_BACKGROUND_COLOR, backgroundColor, setBackgroundColor); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP__WEBKIT_COLUMN_RULE_COLOR, columnRuleColor, setColumnRuleColor); + BLEND(CSS_PROP__WEBKIT_COLUMN_RULE_WIDTH, columnRuleWidth, setColumnRuleWidth); + BLEND(CSS_PROP__WEBKIT_COLUMN_GAP, columnGap, setColumnGap); + BLEND(CSS_PROP__WEBKIT_COLUMN_COUNT, columnCount, setColumnCount); + BLEND(CSS_PROP__WEBKIT_COLUMN_WIDTH, columnWidth, setColumnWidth); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP__WEBKIT_TEXT_STROKE_COLOR, textStrokeColor, setTextStrokeColor); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP__WEBKIT_TEXT_FILL_COLOR, textFillColor, setTextFillColor); + BLEND(CSS_PROP__WEBKIT_BORDER_HORIZONTAL_SPACING, horizontalBorderSpacing, setHorizontalBorderSpacing); + BLEND(CSS_PROP__WEBKIT_BORDER_VERTICAL_SPACING, verticalBorderSpacing, setVerticalBorderSpacing); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP_BORDER_LEFT_COLOR, borderLeftColor, setBorderLeftColor); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP_BORDER_RIGHT_COLOR, borderRightColor, setBorderRightColor); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP_BORDER_TOP_COLOR, borderTopColor, setBorderTopColor); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP_BORDER_BOTTOM_COLOR, borderBottomColor, setBorderBottomColor); + BLEND(CSS_PROP_Z_INDEX, zIndex, setZIndex); + BLEND(CSS_PROP_LINE_HEIGHT, lineHeight, setLineHeight); + BLEND_MAYBE_INVALID_COLOR(CSS_PROP_OUTLINE_COLOR, outlineColor, setOutlineColor); + BLEND(CSS_PROP_OUTLINE_OFFSET, outlineOffset, setOutlineOffset); + BLEND(CSS_PROP_OUTLINE_WIDTH, outlineWidth, setOutlineWidth); + BLEND(CSS_PROP_LETTER_SPACING, letterSpacing, setLetterSpacing); + BLEND(CSS_PROP_WORD_SPACING, wordSpacing, setWordSpacing); + BLEND_SHADOW(CSS_PROP__WEBKIT_BOX_SHADOW, boxShadow, setBoxShadow); + BLEND_SHADOW(CSS_PROP_TEXT_SHADOW, textShadow, setTextShadow); + BLEND(CSS_PROP__WEBKIT_TRANSFORM, transform, setTransform); + BLEND(CSS_PROP__WEBKIT_TRANSFORM_ORIGIN_X, transformOriginX, setTransformOriginX); + BLEND(CSS_PROP__WEBKIT_TRANSFORM_ORIGIN_Y, transformOriginY, setTransformOriginY); + BLEND(CSS_PROP__WEBKIT_BORDER_TOP_LEFT_RADIUS, borderTopLeftRadius, setBorderTopLeftRadius); + BLEND(CSS_PROP__WEBKIT_BORDER_TOP_RIGHT_RADIUS, borderTopRightRadius, setBorderTopRightRadius); + BLEND(CSS_PROP__WEBKIT_BORDER_BOTTOM_LEFT_RADIUS, borderBottomLeftRadius, setBorderBottomLeftRadius); + BLEND(CSS_PROP__WEBKIT_BORDER_BOTTOM_RIGHT_RADIUS, borderBottomRightRadius, setBorderBottomRightRadius); + BLEND(CSS_PROP_VISIBILITY, visibility, setVisibility); +} + +RenderStyle* CompositeImplicitAnimation::animate(RenderObject* renderer, RenderStyle* currentStyle, RenderStyle* targetStyle) +{ + const Transition* currentTransitions = currentStyle->transitions(); + const Transition* targetTransitions = targetStyle->transitions(); + if (currentTransitions != targetTransitions && !(currentTransitions && targetTransitions && *currentTransitions == *targetTransitions)) { + reset(renderer); + deleteAllValues(m_animations); + m_animations.clear(); + } + + // Get the animation layers from the target style. + // For each one, we need to create a new animation unless one exists already (later occurrences of duplicate + // triggers in the layer list get ignored). + if (m_animations.isEmpty()) { + for (const Transition* transition = currentTransitions; transition; transition = transition->next()) { + int property = transition->transitionProperty(); + int duration = transition->transitionDuration(); + int repeatCount = transition->transitionRepeatCount(); + if (property && duration && repeatCount && !m_animations.contains(property)) { + ImplicitAnimation* animation = new ImplicitAnimation(transition); + m_animations.set(property, animation); + } + } + } + + // Now that we have animation objects ready, let them know about the new goal state. We want them + // to fill in a RenderStyle*& only if needed. + RenderStyle* result = 0; + HashMap<int, ImplicitAnimation*>::iterator end = m_animations.end(); + for (HashMap<int, ImplicitAnimation*>::iterator it = m_animations.begin(); it != end; ++it) + it->second->animate(this, renderer, currentStyle, targetStyle, result); + + if (result) + return result; + + return targetStyle; +} + +bool CompositeImplicitAnimation::animating() const +{ + HashMap<int, ImplicitAnimation*>::const_iterator end = m_animations.end(); + for (HashMap<int, ImplicitAnimation*>::const_iterator it = m_animations.begin(); it != end; ++it) + if (!it->second->finished()) + return true; + return false; +} + +void CompositeImplicitAnimation::reset(RenderObject* renderer) +{ + HashMap<int, ImplicitAnimation*>::const_iterator end = m_animations.end(); + for (HashMap<int, ImplicitAnimation*>::const_iterator it = m_animations.begin(); it != end; ++it) + it->second->reset(renderer, 0, 0); +} + +class AnimationControllerPrivate { +public: + AnimationControllerPrivate(Frame*); + ~AnimationControllerPrivate(); + + CompositeImplicitAnimation* get(RenderObject*); + bool clear(RenderObject*); + + void timerFired(Timer<AnimationControllerPrivate>*); + void updateTimer(); + + bool hasImplicitAnimations() const { return !m_animations.isEmpty(); } + +private: + HashMap<RenderObject*, CompositeImplicitAnimation*> m_animations; + Timer<AnimationControllerPrivate> m_timer; + Frame* m_frame; +}; + +AnimationControllerPrivate::AnimationControllerPrivate(Frame* frame) + : m_timer(this, &AnimationControllerPrivate::timerFired) + , m_frame(frame) +{ +} + +AnimationControllerPrivate::~AnimationControllerPrivate() +{ + deleteAllValues(m_animations); +} + +CompositeImplicitAnimation* AnimationControllerPrivate::get(RenderObject* renderer) +{ + CompositeImplicitAnimation* animation = m_animations.get(renderer); + if (!animation && renderer->style()->transitions()) { + animation = new CompositeImplicitAnimation(); + m_animations.set(renderer, animation); + } + return animation; +} + +bool AnimationControllerPrivate::clear(RenderObject* renderer) +{ + CompositeImplicitAnimation* animation = m_animations.take(renderer); + if (!animation) + return false; + animation->reset(renderer); + delete animation; + return true; +} + +void AnimationControllerPrivate::updateTimer() +{ + bool animating = false; + HashMap<RenderObject*, CompositeImplicitAnimation*>::iterator end = m_animations.end(); + for (HashMap<RenderObject*, CompositeImplicitAnimation*>::iterator it = m_animations.begin(); it != end; ++it) { + if (it->second->animating()) { + animating = true; + break; + } + } + + if (animating) { + if (!m_timer.isActive()) + m_timer.startRepeating(cAnimationTimerDelay); + } else if (m_timer.isActive()) + m_timer.stop(); +} + +void AnimationControllerPrivate::timerFired(Timer<AnimationControllerPrivate>* timer) +{ + // When the timer fires, all we do is call setChanged on all DOM nodes with running animations and then do an immediate + // updateRendering. It will then call back to us with new information. + bool animating = false; + HashMap<RenderObject*, CompositeImplicitAnimation*>::iterator end = m_animations.end(); + for (HashMap<RenderObject*, CompositeImplicitAnimation*>::iterator it = m_animations.begin(); it != end; ++it) { + if (it->second->animating()) { + animating = true; + it->first->element()->setChanged(); + } + } + + m_frame->document()->updateRendering(); + + updateTimer(); +} + +AnimationController::AnimationController(Frame* frame) +:m_data(new AnimationControllerPrivate(frame)) +{ + +} + +AnimationController::~AnimationController() +{ + delete m_data; +} + +void AnimationController::cancelImplicitAnimations(RenderObject* renderer) +{ + if (!m_data->hasImplicitAnimations()) + return; + + if (m_data->clear(renderer)) + renderer->element()->setChanged(); +} + +RenderStyle* AnimationController::updateImplicitAnimations(RenderObject* renderer, RenderStyle* newStyle) +{ + // Fetch our current set of implicit animations from a hashtable. We then compare them + // against the animations in the style and make sure we're in sync. If destination values + // have changed, we reset the animation. We then do a blend to get new values and we return + // a new style. + ASSERT(renderer->element()); // FIXME: We do not animate generated content yet. + + CompositeImplicitAnimation* animation = m_data->get(renderer); + if (!animation) + return newStyle; + + RenderStyle* result = animation->animate(renderer, renderer->style(), newStyle); + m_data->updateTimer(); + return result; +} + +void AnimationController::suspendAnimations() +{ + // FIXME: Walk the whole hashtable and call pause on each animation. + // Kill our timer. +} + +void AnimationController::resumeAnimations() +{ + // FIXME: Walk the whole hashtable and call resume on each animation. + // Start our timer. +} + +} diff --git a/WebCore/page/AnimationController.h b/WebCore/page/AnimationController.h new file mode 100644 index 0000000..fda1143 --- /dev/null +++ b/WebCore/page/AnimationController.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AnimationController_h +#define AnimationController_h + +namespace WebCore { + +class AnimationControllerPrivate; +class Frame; +class RenderObject; +class RenderStyle; + +class AnimationController +{ +public: + AnimationController(Frame*); + ~AnimationController(); + + void cancelImplicitAnimations(RenderObject*); + RenderStyle* updateImplicitAnimations(RenderObject*, RenderStyle* newStyle); + + void suspendAnimations(); + void resumeAnimations(); + +private: + AnimationControllerPrivate* m_data; +}; + +} + +#endif diff --git a/WebCore/page/BarInfo.cpp b/WebCore/page/BarInfo.cpp new file mode 100644 index 0000000..153aee0 --- /dev/null +++ b/WebCore/page/BarInfo.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "BarInfo.h" + +#include "Chrome.h" +#include "Frame.h" +#include "Page.h" + +namespace WebCore { + +BarInfo::BarInfo(Frame* frame, Type type) + : m_frame(frame) + , m_type(type) +{ +} + +void BarInfo::disconnectFrame() +{ + m_frame = 0; +} + +bool BarInfo::visible() const +{ + if (!m_frame) + return false; + + switch (m_type) { + case Locationbar: + return m_frame->page()->chrome()->toolbarsVisible(); + case Toolbar: + return m_frame->page()->chrome()->toolbarsVisible(); + case Personalbar: + return m_frame->page()->chrome()->toolbarsVisible(); + case Menubar: + return m_frame->page()->chrome()->menubarVisible(); + case Scrollbars: + return m_frame->page()->chrome()->scrollbarsVisible(); + case Statusbar: + return m_frame->page()->chrome()->statusbarVisible(); + default: + return false; + } +} + +} // namespace WebCore diff --git a/WebCore/page/BarInfo.h b/WebCore/page/BarInfo.h new file mode 100644 index 0000000..4cbbcfc --- /dev/null +++ b/WebCore/page/BarInfo.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BarInfo_h +#define BarInfo_h + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + + class Frame; + + class BarInfo : public RefCounted<BarInfo> { + public: + enum Type { Locationbar, Menubar, Personalbar, Scrollbars, Statusbar, Toolbar }; + + static PassRefPtr<BarInfo> create(Frame* frame, Type type) { return adoptRef(new BarInfo(frame, type)); } + + void disconnectFrame(); + + bool visible() const; + + private: + BarInfo(Frame*, Type); + Frame* m_frame; + Type m_type; + }; + +} // namespace WebCore + +#endif // BarInfo_h diff --git a/WebCore/page/BarInfo.idl b/WebCore/page/BarInfo.idl new file mode 100644 index 0000000..42041c51 --- /dev/null +++ b/WebCore/page/BarInfo.idl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module window { + + interface BarInfo { + readonly attribute boolean visible; + }; + +} diff --git a/WebCore/page/Chrome.cpp b/WebCore/page/Chrome.cpp new file mode 100644 index 0000000..fecbe79 --- /dev/null +++ b/WebCore/page/Chrome.cpp @@ -0,0 +1,398 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Trolltech ASA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "Chrome.h" + +#include "ChromeClient.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameTree.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "InspectorController.h" +#include "Page.h" +#include "ResourceHandle.h" +#include "Settings.h" +#include "WindowFeatures.h" +#include "kjs_window.h" +#include "PausedTimeouts.h" +#include "SecurityOrigin.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +using namespace HTMLNames; +using namespace std; + +class PageGroupLoadDeferrer : Noncopyable { +public: + PageGroupLoadDeferrer(Page*, bool deferSelf); + ~PageGroupLoadDeferrer(); +private: + Vector<RefPtr<Frame>, 16> m_deferredFrames; +#if !PLATFORM(MAC) + Vector<pair<RefPtr<Frame>, PausedTimeouts*>, 16> m_pausedTimeouts; +#endif +}; + +Chrome::Chrome(Page* page, ChromeClient* client) + : m_page(page) + , m_client(client) +{ + ASSERT(m_client); +} + +Chrome::~Chrome() +{ + m_client->chromeDestroyed(); +} + +void Chrome::setWindowRect(const FloatRect& rect) const +{ + m_client->setWindowRect(rect); +} + +FloatRect Chrome::windowRect() const +{ + return m_client->windowRect(); +} + +FloatRect Chrome::pageRect() const +{ + return m_client->pageRect(); +} + +float Chrome::scaleFactor() +{ + return m_client->scaleFactor(); +} + +void Chrome::focus() const +{ + m_client->focus(); +} + +void Chrome::unfocus() const +{ + m_client->unfocus(); +} + +bool Chrome::canTakeFocus(FocusDirection direction) const +{ + return m_client->canTakeFocus(direction); +} + +void Chrome::takeFocus(FocusDirection direction) const +{ + m_client->takeFocus(direction); +} + +Page* Chrome::createWindow(Frame* frame, const FrameLoadRequest& request, const WindowFeatures& features) const +{ + return m_client->createWindow(frame, request, features); +} + +void Chrome::show() const +{ + m_client->show(); +} + +bool Chrome::canRunModal() const +{ + return m_client->canRunModal(); +} + +bool Chrome::canRunModalNow() const +{ + // If loads are blocked, we can't run modal because the contents + // of the modal dialog will never show up! + return canRunModal() && !ResourceHandle::loadsBlocked(); +} + +void Chrome::runModal() const +{ + if (m_page->defersLoading()) { + LOG_ERROR("Tried to run modal in a page when it was deferring loading -- should never happen."); + return; + } + + // Defer callbacks in all the other pages in this group, so we don't try to run JavaScript + // in a way that could interact with this view. + PageGroupLoadDeferrer deferrer(m_page, false); + + TimerBase::fireTimersInNestedEventLoop(); + m_client->runModal(); +} + +void Chrome::setToolbarsVisible(bool b) const +{ + m_client->setToolbarsVisible(b); +} + +bool Chrome::toolbarsVisible() const +{ + return m_client->toolbarsVisible(); +} + +void Chrome::setStatusbarVisible(bool b) const +{ + m_client->setStatusbarVisible(b); +} + +bool Chrome::statusbarVisible() const +{ + return m_client->statusbarVisible(); +} + +void Chrome::setScrollbarsVisible(bool b) const +{ + m_client->setScrollbarsVisible(b); +} + +bool Chrome::scrollbarsVisible() const +{ + return m_client->scrollbarsVisible(); +} + +void Chrome::setMenubarVisible(bool b) const +{ + m_client->setMenubarVisible(b); +} + +bool Chrome::menubarVisible() const +{ + return m_client->menubarVisible(); +} + +void Chrome::setResizable(bool b) const +{ + m_client->setResizable(b); +} + +void Chrome::addMessageToConsole(MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID) +{ + if (source == JSMessageSource) + m_client->addMessageToConsole(message, lineNumber, sourceID); + + m_page->inspectorController()->addMessageToConsole(source, level, message, lineNumber, sourceID); +} + +bool Chrome::canRunBeforeUnloadConfirmPanel() +{ + return m_client->canRunBeforeUnloadConfirmPanel(); +} + +bool Chrome::runBeforeUnloadConfirmPanel(const String& message, Frame* frame) +{ + // Defer loads in case the client method runs a new event loop that would + // otherwise cause the load to continue while we're in the middle of executing JavaScript. + PageGroupLoadDeferrer deferrer(m_page, true); + + return m_client->runBeforeUnloadConfirmPanel(message, frame); +} + +void Chrome::closeWindowSoon() +{ + m_client->closeWindowSoon(); +} + +void Chrome::runJavaScriptAlert(Frame* frame, const String& message) +{ + // Defer loads in case the client method runs a new event loop that would + // otherwise cause the load to continue while we're in the middle of executing JavaScript. + PageGroupLoadDeferrer deferrer(m_page, true); + + ASSERT(frame); + String text = message; + text.replace('\\', frame->backslashAsCurrencySymbol()); + + m_client->runJavaScriptAlert(frame, text); +} + +bool Chrome::runJavaScriptConfirm(Frame* frame, const String& message) +{ + // Defer loads in case the client method runs a new event loop that would + // otherwise cause the load to continue while we're in the middle of executing JavaScript. + PageGroupLoadDeferrer deferrer(m_page, true); + + ASSERT(frame); + String text = message; + text.replace('\\', frame->backslashAsCurrencySymbol()); + + return m_client->runJavaScriptConfirm(frame, text); +} + +bool Chrome::runJavaScriptPrompt(Frame* frame, const String& prompt, const String& defaultValue, String& result) +{ + // Defer loads in case the client method runs a new event loop that would + // otherwise cause the load to continue while we're in the middle of executing JavaScript. + PageGroupLoadDeferrer deferrer(m_page, true); + + ASSERT(frame); + String promptText = prompt; + promptText.replace('\\', frame->backslashAsCurrencySymbol()); + String defaultValueText = defaultValue; + defaultValueText.replace('\\', frame->backslashAsCurrencySymbol()); + + bool ok = m_client->runJavaScriptPrompt(frame, promptText, defaultValueText, result); + + if (ok) + result.replace(frame->backslashAsCurrencySymbol(), '\\'); + + return ok; +} + +void Chrome::setStatusbarText(Frame* frame, const String& status) +{ + ASSERT(frame); + String text = status; + text.replace('\\', frame->backslashAsCurrencySymbol()); + + m_client->setStatusbarText(text); +} + +bool Chrome::shouldInterruptJavaScript() +{ + // Defer loads in case the client method runs a new event loop that would + // otherwise cause the load to continue while we're in the middle of executing JavaScript. + PageGroupLoadDeferrer deferrer(m_page, true); + + return m_client->shouldInterruptJavaScript(); +} + +IntRect Chrome::windowResizerRect() const +{ + return m_client->windowResizerRect(); +} + +void Chrome::addToDirtyRegion(const IntRect& rect) +{ + m_client->addToDirtyRegion(rect); +} + +void Chrome::scrollBackingStore(int dx, int dy, const IntRect& scrollViewRect, const IntRect& clipRect) +{ + m_client->scrollBackingStore(dx, dy, scrollViewRect, clipRect); +} + +void Chrome::updateBackingStore() +{ + m_client->updateBackingStore(); +} + +void Chrome::mouseDidMoveOverElement(const HitTestResult& result, unsigned modifierFlags) +{ + m_client->mouseDidMoveOverElement(result, modifierFlags); +} + +void Chrome::setToolTip(const HitTestResult& result) +{ + // First priority is a potential toolTip representing a spelling or grammar error + String toolTip = result.spellingToolTip(); + + // Next priority is a toolTip from a URL beneath the mouse (if preference is set to show those). + if (toolTip.isEmpty() && m_page->settings()->showsURLsInToolTips()) { + if (Node* node = result.innerNonSharedNode()) { + // Get tooltip representing form action, if relevant + if (node->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + if (input->inputType() == HTMLInputElement::SUBMIT) + if (HTMLFormElement* form = input->form()) + toolTip = form->action(); + } + } + + // Get tooltip representing link's URL + if (toolTip.isEmpty()) + // FIXME: Need to pass this URL through userVisibleString once that's in WebCore + toolTip = result.absoluteLinkURL().string(); + } + + // Lastly we'll consider a tooltip for element with "title" attribute + if (toolTip.isEmpty()) + toolTip = result.title(); + + m_client->setToolTip(toolTip); +} + +void Chrome::print(Frame* frame) +{ + m_client->print(frame); +} + +PageGroupLoadDeferrer::PageGroupLoadDeferrer(Page* page, bool deferSelf) +{ + const HashSet<Page*>* group = page->frameNamespace(); + + if (!group) + return; + + HashSet<Page*>::const_iterator end = group->end(); + for (HashSet<Page*>::const_iterator it = group->begin(); it != end; ++it) { + Page* otherPage = *it; + if ((deferSelf || otherPage != page)) { + if (!otherPage->defersLoading()) + m_deferredFrames.append(otherPage->mainFrame()); + +#if !PLATFORM(MAC) + for (Frame* frame = otherPage->mainFrame(); frame; frame = frame->tree()->traverseNext()) { + if (KJS::Window* window = KJS::Window::retrieveWindow(frame)) { + PausedTimeouts* timeouts = window->pauseTimeouts(); + + m_pausedTimeouts.append(make_pair(frame, timeouts)); + } + } +#endif + } + } + + size_t count = m_deferredFrames.size(); + for (size_t i = 0; i < count; ++i) + if (Page* page = m_deferredFrames[i]->page()) + page->setDefersLoading(true); +} + +PageGroupLoadDeferrer::~PageGroupLoadDeferrer() +{ + size_t count = m_deferredFrames.size(); + for (size_t i = 0; i < count; ++i) + if (Page* page = m_deferredFrames[i]->page()) + page->setDefersLoading(false); + +#if !PLATFORM(MAC) + count = m_pausedTimeouts.size(); + + for (size_t i = 0; i < count; i++) { + KJS::Window* window = KJS::Window::retrieveWindow(m_pausedTimeouts[i].first.get()); + if (window) + window->resumeTimeouts(m_pausedTimeouts[i].second); + delete m_pausedTimeouts[i].second; + } +#endif +} + + +} // namespace WebCore diff --git a/WebCore/page/Chrome.h b/WebCore/page/Chrome.h new file mode 100644 index 0000000..9bc42e0 --- /dev/null +++ b/WebCore/page/Chrome.h @@ -0,0 +1,139 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef Chrome_h +#define Chrome_h + +#include "FocusDirection.h" +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +#if PLATFORM(MAC) +#ifndef __OBJC__ +class NSView; +#endif +#endif + +namespace WebCore { + + class ChromeClient; + class ContextMenu; + class FloatRect; + class Frame; + class HitTestResult; + class IntRect; + class Page; + class String; + + struct FrameLoadRequest; + struct WindowFeatures; + + enum MessageSource { + HTMLMessageSource, + XMLMessageSource, + JSMessageSource, + CSSMessageSource, + OtherMessageSource + }; + + enum MessageLevel { + TipMessageLevel, + LogMessageLevel, + WarningMessageLevel, + ErrorMessageLevel + }; + + class Chrome : Noncopyable { + public: + Chrome(Page*, ChromeClient*); + ~Chrome(); + + ChromeClient* client() { return m_client; } + + void setWindowRect(const FloatRect&) const; + FloatRect windowRect() const; + + FloatRect pageRect() const; + + float scaleFactor(); + + void focus() const; + void unfocus() const; + + bool canTakeFocus(FocusDirection) const; + void takeFocus(FocusDirection) const; + + Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&) const; + void show() const; + + bool canRunModal() const; + bool canRunModalNow() const; + void runModal() const; + + void setToolbarsVisible(bool) const; + bool toolbarsVisible() const; + + void setStatusbarVisible(bool) const; + bool statusbarVisible() const; + + void setScrollbarsVisible(bool) const; + bool scrollbarsVisible() const; + + void setMenubarVisible(bool) const; + bool menubarVisible() const; + + void setResizable(bool) const; + + void addMessageToConsole(MessageSource, MessageLevel, const String& message, unsigned lineNumber, const String& sourceID); + + bool canRunBeforeUnloadConfirmPanel(); + bool runBeforeUnloadConfirmPanel(const String& message, Frame* frame); + + void closeWindowSoon(); + + void runJavaScriptAlert(Frame*, const String&); + bool runJavaScriptConfirm(Frame*, const String&); + bool runJavaScriptPrompt(Frame*, const String& message, const String& defaultValue, String& result); + void setStatusbarText(Frame*, const String&); + bool shouldInterruptJavaScript(); + + IntRect windowResizerRect() const; + void addToDirtyRegion(const IntRect&); + void scrollBackingStore(int dx, int dy, const IntRect& scrollViewRect, const IntRect& clipRect); + void updateBackingStore(); + + void mouseDidMoveOverElement(const HitTestResult&, unsigned modifierFlags); + + void setToolTip(const HitTestResult&); + + void print(Frame*); + +#if PLATFORM(MAC) + void focusNSView(NSView*); +#endif + + private: + Page* m_page; + ChromeClient* m_client; + }; +} + +#endif // Chrome_h diff --git a/WebCore/page/ChromeClient.h b/WebCore/page/ChromeClient.h new file mode 100644 index 0000000..5bce1af --- /dev/null +++ b/WebCore/page/ChromeClient.h @@ -0,0 +1,113 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006, 2007, 2008 Apple, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef ChromeClient_h +#define ChromeClient_h + +#include "FocusDirection.h" + +namespace WebCore { + + class FloatRect; + class Frame; + class HitTestResult; + class IntRect; + class Page; + class String; + + struct FrameLoadRequest; + struct WindowFeatures; + + class ChromeClient { + public: + virtual void chromeDestroyed() = 0; + + virtual void setWindowRect(const FloatRect&) = 0; + virtual FloatRect windowRect() = 0; + + virtual FloatRect pageRect() = 0; + + virtual float scaleFactor() = 0; + + virtual void focus() = 0; + virtual void unfocus() = 0; + + virtual bool canTakeFocus(FocusDirection) = 0; + virtual void takeFocus(FocusDirection) = 0; + + // The Frame pointer provides the ChromeClient with context about which + // Frame wants to create the new Page. Also, the newly created window + // should not be shown to the user until the ChromeClient of the newly + // created Page has its show method called. + virtual Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&) = 0; + virtual void show() = 0; + + virtual bool canRunModal() = 0; + virtual void runModal() = 0; + + virtual void setToolbarsVisible(bool) = 0; + virtual bool toolbarsVisible() = 0; + + virtual void setStatusbarVisible(bool) = 0; + virtual bool statusbarVisible() = 0; + + virtual void setScrollbarsVisible(bool) = 0; + virtual bool scrollbarsVisible() = 0; + + virtual void setMenubarVisible(bool) = 0; + virtual bool menubarVisible() = 0; + + virtual void setResizable(bool) = 0; + + virtual void addMessageToConsole(const String& message, unsigned int lineNumber, const String& sourceID) = 0; + + virtual bool canRunBeforeUnloadConfirmPanel() = 0; + virtual bool runBeforeUnloadConfirmPanel(const String& message, Frame* frame) = 0; + + virtual void closeWindowSoon() = 0; + + virtual void runJavaScriptAlert(Frame*, const String&) = 0; + virtual bool runJavaScriptConfirm(Frame*, const String&) = 0; + virtual bool runJavaScriptPrompt(Frame*, const String& message, const String& defaultValue, String& result) = 0; + + virtual void setStatusbarText(const String&) = 0; + virtual bool shouldInterruptJavaScript() = 0; + virtual bool tabsToLinks() const = 0; + + virtual IntRect windowResizerRect() const = 0; + virtual void addToDirtyRegion(const IntRect&) = 0; + virtual void scrollBackingStore(int dx, int dy, const IntRect& scrollViewRect, const IntRect& clipRect) = 0; + virtual void updateBackingStore() = 0; + + virtual void mouseDidMoveOverElement(const HitTestResult&, unsigned modifierFlags) = 0; + + virtual void setToolTip(const String&) = 0; + + virtual void print(Frame*) = 0; + + virtual void exceededDatabaseQuota(Frame*, const String& databaseName) = 0; + + protected: + virtual ~ChromeClient() { } + }; + +} + +#endif // ChromeClient_h diff --git a/WebCore/page/Console.cpp b/WebCore/page/Console.cpp new file mode 100644 index 0000000..d45af62 --- /dev/null +++ b/WebCore/page/Console.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Console.h" + +#include "Chrome.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "Page.h" +#include "PlatformString.h" + +namespace WebCore { + +Console::Console(Frame* frame) + : m_frame(frame) +{ +} + +void Console::disconnectFrame() +{ + m_frame = 0; +} + +void Console::error(const String& message) +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, 0, m_frame->loader()->url().prettyURL()); +} + +void Console::info(const String& message) +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->addMessageToConsole(JSMessageSource, LogMessageLevel, message, 0, m_frame->loader()->url().prettyURL()); +} + +void Console::log(const String& message) +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->addMessageToConsole(JSMessageSource, LogMessageLevel, message, 0, m_frame->loader()->url().prettyURL()); +} + +void Console::warn(const String& message) +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->addMessageToConsole(JSMessageSource, WarningMessageLevel, message, 0, m_frame->loader()->url().prettyURL()); +} + +} // namespace WebCore diff --git a/WebCore/page/Console.h b/WebCore/page/Console.h new file mode 100644 index 0000000..062cfdd --- /dev/null +++ b/WebCore/page/Console.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef Console_h +#define Console_h + +#include <wtf/RefCounted.h> +#include "PlatformString.h" + +namespace WebCore { + + class Frame; + + class Console : public RefCounted<Console> { + public: + static PassRefPtr<Console> create(Frame* frame) { return adoptRef(new Console(frame)); } + + void disconnectFrame(); + + void error(const String& message); + void info(const String& message); + void log(const String& message); + void warn(const String& message); + + private: + Console(Frame*); + + Frame* m_frame; + }; + +} // namespace WebCore + +#endif // Console_h diff --git a/WebCore/page/Console.idl b/WebCore/page/Console.idl new file mode 100644 index 0000000..3356c0e --- /dev/null +++ b/WebCore/page/Console.idl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module window { + + interface Console { + void error(in DOMString message); + void info(in DOMString message); + void log(in DOMString message); + void warn(in DOMString message); + }; + +} diff --git a/WebCore/page/ContextMenuClient.h b/WebCore/page/ContextMenuClient.h new file mode 100644 index 0000000..775adc5 --- /dev/null +++ b/WebCore/page/ContextMenuClient.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ContextMenuClient_h +#define ContextMenuClient_h + +#include "PlatformMenuDescription.h" + +namespace WebCore { + class ContextMenu; + class ContextMenuItem; + class Frame; + class HitTestResult; + class KURL; + class String; + + class ContextMenuClient { + public: + virtual ~ContextMenuClient() { } + virtual void contextMenuDestroyed() = 0; + + virtual PlatformMenuDescription getCustomMenuFromDefaultItems(ContextMenu*) = 0; + virtual void contextMenuItemSelected(ContextMenuItem*, const ContextMenu*) = 0; + + virtual void downloadURL(const KURL& url) = 0; + virtual void searchWithGoogle(const Frame*) = 0; + virtual void lookUpInDictionary(Frame*) = 0; + virtual void speak(const String&) = 0; + virtual void stopSpeaking() = 0; + +#if PLATFORM(MAC) + virtual void searchWithSpotlight() = 0; +#endif + }; +} + +#endif diff --git a/WebCore/page/ContextMenuController.cpp b/WebCore/page/ContextMenuController.cpp new file mode 100644 index 0000000..d331b19 --- /dev/null +++ b/WebCore/page/ContextMenuController.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ContextMenuController.h" + +#include "Chrome.h" +#include "ContextMenu.h" +#include "ContextMenuClient.h" +#include "Document.h" +#include "DocumentFragment.h" +#include "DocumentLoader.h" +#include "Editor.h" +#include "EditorClient.h" +#include "Event.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoadRequest.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "InspectorController.h" +#include "MouseEvent.h" +#include "Node.h" +#include "Page.h" +#include "RenderLayer.h" +#include "RenderObject.h" +#include "ReplaceSelectionCommand.h" +#include "ResourceRequest.h" +#include "SelectionController.h" +#include "Settings.h" +#include "TextIterator.h" +#include "WindowFeatures.h" +#include "markup.h" + +namespace WebCore { + +using namespace EventNames; + +ContextMenuController::ContextMenuController(Page* page, ContextMenuClient* client) + : m_page(page) + , m_client(client) + , m_contextMenu(0) +{ + ASSERT_ARG(page, page); + ASSERT_ARG(client, client); +} + +ContextMenuController::~ContextMenuController() +{ + m_client->contextMenuDestroyed(); +} + +void ContextMenuController::clearContextMenu() +{ + m_contextMenu.set(0); +} + +void ContextMenuController::handleContextMenuEvent(Event* event) +{ + ASSERT(event->type() == contextmenuEvent); + if (!event->isMouseEvent()) + return; + MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); + IntPoint point = IntPoint(mouseEvent->pageX(), mouseEvent->pageY()); + HitTestResult result(point); + + if (Frame* frame = event->target()->toNode()->document()->frame()) + result = frame->eventHandler()->hitTestResultAtPoint(point, false); + + if (!result.innerNonSharedNode()) + return; + + m_contextMenu.set(new ContextMenu(result)); + m_contextMenu->populate(); + if (m_page->inspectorController()->enabled()) + m_contextMenu->addInspectElementItem(); + + PlatformMenuDescription customMenu = m_client->getCustomMenuFromDefaultItems(m_contextMenu.get()); + m_contextMenu->setPlatformDescription(customMenu); + + event->setDefaultHandled(); +} + +static void openNewWindow(const KURL& urlToLoad, Frame* frame) +{ + if (Page* oldPage = frame->page()) { + WindowFeatures features; + if (Page* newPage = oldPage->chrome()->createWindow(frame, + FrameLoadRequest(ResourceRequest(urlToLoad, frame->loader()->outgoingReferrer())), features)) + newPage->chrome()->show(); + } +} + +void ContextMenuController::contextMenuItemSelected(ContextMenuItem* item) +{ + ASSERT(item->type() == ActionType || item->type() == CheckableActionType); + + if (item->action() >= ContextMenuItemBaseApplicationTag) { + m_client->contextMenuItemSelected(item, m_contextMenu.get()); + return; + } + + HitTestResult result = m_contextMenu->hitTestResult(); + Frame* frame = result.innerNonSharedNode()->document()->frame(); + if (!frame) + return; + + switch (item->action()) { + case ContextMenuItemTagOpenLinkInNewWindow: + openNewWindow(result.absoluteLinkURL(), frame); + break; + case ContextMenuItemTagDownloadLinkToDisk: + // FIXME: Some day we should be able to do this from within WebCore. + m_client->downloadURL(result.absoluteLinkURL()); + break; + case ContextMenuItemTagCopyLinkToClipboard: + frame->editor()->copyURL(result.absoluteLinkURL(), result.textContent()); + break; + case ContextMenuItemTagOpenImageInNewWindow: + openNewWindow(result.absoluteImageURL(), frame); + break; + case ContextMenuItemTagDownloadImageToDisk: + // FIXME: Some day we should be able to do this from within WebCore. + m_client->downloadURL(result.absoluteImageURL()); + break; + case ContextMenuItemTagCopyImageToClipboard: + // FIXME: The Pasteboard class is not written yet + // For now, call into the client. This is temporary! + frame->editor()->copyImage(result); + break; + case ContextMenuItemTagOpenFrameInNewWindow: { + KURL url = frame->loader()->documentLoader()->unreachableURL(); + if (frame && url.isEmpty()) + url = frame->loader()->documentLoader()->url(); + openNewWindow(url, frame); + break; + } + case ContextMenuItemTagCopy: + frame->editor()->copy(); + break; + case ContextMenuItemTagGoBack: + frame->loader()->goBackOrForward(-1); + break; + case ContextMenuItemTagGoForward: + frame->loader()->goBackOrForward(1); + break; + case ContextMenuItemTagStop: + frame->loader()->stop(); + break; + case ContextMenuItemTagReload: + frame->loader()->reload(); + break; + case ContextMenuItemTagCut: + frame->editor()->cut(); + break; + case ContextMenuItemTagPaste: + frame->editor()->paste(); + break; +#if PLATFORM(GTK) + case ContextMenuItemTagDelete: + frame->editor()->performDelete(); + break; + case ContextMenuItemTagSelectAll: + frame->editor()->command("SelectAll").execute(); + break; +#endif + case ContextMenuItemTagSpellingGuess: + ASSERT(frame->selectedText().length()); + if (frame->editor()->shouldInsertText(item->title(), frame->selectionController()->toRange().get(), + EditorInsertActionPasted)) { + Document* document = frame->document(); + RefPtr<ReplaceSelectionCommand> command = + new ReplaceSelectionCommand(document, createFragmentFromMarkup(document, item->title(), ""), + true, false, true); + applyCommand(command); + frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); + } + break; + case ContextMenuItemTagIgnoreSpelling: + frame->editor()->ignoreSpelling(); + break; + case ContextMenuItemTagLearnSpelling: + frame->editor()->learnSpelling(); + break; + case ContextMenuItemTagSearchWeb: + m_client->searchWithGoogle(frame); + break; + case ContextMenuItemTagLookUpInDictionary: + // FIXME: Some day we may be able to do this from within WebCore. + m_client->lookUpInDictionary(frame); + break; + case ContextMenuItemTagOpenLink: + if (Frame* targetFrame = result.targetFrame()) + targetFrame->loader()->load(FrameLoadRequest(ResourceRequest(result.absoluteLinkURL(), + frame->loader()->outgoingReferrer())), false, true, 0, 0, HashMap<String, String>()); + else + openNewWindow(result.absoluteLinkURL(), frame); + break; + case ContextMenuItemTagBold: + frame->editor()->command("ToggleBold").execute(); + break; + case ContextMenuItemTagItalic: + frame->editor()->command("ToggleItalic").execute(); + break; + case ContextMenuItemTagUnderline: + frame->editor()->toggleUnderline(); + break; + case ContextMenuItemTagOutline: + // We actually never enable this because CSS does not have a way to specify an outline font, + // which may make this difficult to implement. Maybe a special case of text-shadow? + break; + case ContextMenuItemTagStartSpeaking: { + ExceptionCode ec; + RefPtr<Range> selectedRange = frame->selectionController()->toRange(); + if (!selectedRange || selectedRange->collapsed(ec)) { + Document* document = result.innerNonSharedNode()->document(); + selectedRange = document->createRange(); + selectedRange->selectNode(document->documentElement(), ec); + } + m_client->speak(plainText(selectedRange.get())); + break; + } + case ContextMenuItemTagStopSpeaking: + m_client->stopSpeaking(); + break; + case ContextMenuItemTagDefaultDirection: + frame->editor()->setBaseWritingDirection("inherit"); + break; + case ContextMenuItemTagLeftToRight: + frame->editor()->setBaseWritingDirection("ltr"); + break; + case ContextMenuItemTagRightToLeft: + frame->editor()->setBaseWritingDirection("rtl"); + break; +#if PLATFORM(MAC) + case ContextMenuItemTagSearchInSpotlight: + m_client->searchWithSpotlight(); + break; +#endif + case ContextMenuItemTagShowSpellingPanel: + frame->editor()->showSpellingGuessPanel(); + break; + case ContextMenuItemTagCheckSpelling: + frame->editor()->advanceToNextMisspelling(); + break; + case ContextMenuItemTagCheckSpellingWhileTyping: + frame->editor()->toggleContinuousSpellChecking(); + break; +#ifndef BUILDING_ON_TIGER + case ContextMenuItemTagCheckGrammarWithSpelling: + frame->editor()->toggleGrammarChecking(); + break; +#endif +#if PLATFORM(MAC) + case ContextMenuItemTagShowFonts: + frame->editor()->showFontPanel(); + break; + case ContextMenuItemTagStyles: + frame->editor()->showStylesPanel(); + break; + case ContextMenuItemTagShowColors: + frame->editor()->showColorPanel(); + break; +#endif + case ContextMenuItemTagInspectElement: + if (Page* page = frame->page()) + page->inspectorController()->inspect(result.innerNonSharedNode()); + break; + default: + break; + } +} + +} // namespace WebCore diff --git a/WebCore/page/ContextMenuController.h b/WebCore/page/ContextMenuController.h new file mode 100644 index 0000000..cb7e6ee --- /dev/null +++ b/WebCore/page/ContextMenuController.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ContextMenuController_h +#define ContextMenuController_h + +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + + class ContextMenu; + class ContextMenuClient; + class ContextMenuItem; + class Event; + class Page; + + class ContextMenuController : Noncopyable { + public: + ContextMenuController(Page*, ContextMenuClient*); + ~ContextMenuController(); + + ContextMenuClient* client() { return m_client; } + + ContextMenu* contextMenu() const { return m_contextMenu.get(); } + void clearContextMenu(); + + void handleContextMenuEvent(Event*); + void contextMenuItemSelected(ContextMenuItem*); + + private: + Page* m_page; + ContextMenuClient* m_client; + OwnPtr<ContextMenu> m_contextMenu; + }; + +} + +#endif diff --git a/WebCore/page/DOMSelection.cpp b/WebCore/page/DOMSelection.cpp new file mode 100644 index 0000000..66b567a --- /dev/null +++ b/WebCore/page/DOMSelection.cpp @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "config.h" +#include "DOMSelection.h" + +#include "ExceptionCode.h" +#include "Frame.h" +#include "htmlediting.h" +#include "Node.h" +#include "PlatformString.h" +#include "Range.h" +#include "SelectionController.h" +#include "TextIterator.h" + +namespace WebCore { + +DOMSelection::DOMSelection(Frame* frame) + : m_frame(frame) +{ +} + +Frame* DOMSelection::frame() const +{ + return m_frame; +} + +void DOMSelection::disconnectFrame() +{ + m_frame = 0; +} + +Node* DOMSelection::anchorNode() const +{ + if (!m_frame) + return 0; + + const Selection& selection = m_frame->selectionController()->selection(); + Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); + anchor = rangeCompliantEquivalent(anchor); + return anchor.node(); +} + +Node* DOMSelection::baseNode() const +{ + if (!m_frame) + return 0; + return rangeCompliantEquivalent(m_frame->selectionController()->selection().base()).node(); +} + +int DOMSelection::anchorOffset() const +{ + if (!m_frame) + return 0; + + const Selection& selection = m_frame->selectionController()->selection(); + Position anchor = selection.isBaseFirst() ? selection.start() : selection.end(); + anchor = rangeCompliantEquivalent(anchor); + return anchor.offset(); +} + +int DOMSelection::baseOffset() const +{ + if (!m_frame) + return 0; + return rangeCompliantEquivalent(m_frame->selectionController()->selection().base()).offset(); +} + +Node* DOMSelection::focusNode() const +{ + if (!m_frame) + return 0; + + const Selection& selection = m_frame->selectionController()->selection(); + Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); + focus = rangeCompliantEquivalent(focus); + return focus.node(); +} + +Node* DOMSelection::extentNode() const +{ + if (!m_frame) + return 0; + return rangeCompliantEquivalent(m_frame->selectionController()->selection().extent()).node(); +} + +int DOMSelection::focusOffset() const +{ + if (!m_frame) + return 0; + + const Selection& selection = m_frame->selectionController()->selection(); + Position focus = selection.isBaseFirst() ? selection.end() : selection.start(); + focus = rangeCompliantEquivalent(focus); + return focus.offset(); +} + +int DOMSelection::extentOffset() const +{ + if (!m_frame) + return 0; + return rangeCompliantEquivalent(m_frame->selectionController()->selection().extent()).offset(); +} + +bool DOMSelection::isCollapsed() const +{ + if (!m_frame) + return false; + return !m_frame->selectionController()->isRange(); +} + +String DOMSelection::type() const +{ + if (!m_frame) + return String(); + + SelectionController* selectionController = m_frame->selectionController(); + + if (selectionController->isNone()) + return "None"; + if (selectionController->isCaret()) + return "Caret"; + return "Range"; +} + +int DOMSelection::rangeCount() const +{ + if (!m_frame) + return 0; + return m_frame->selectionController()->isNone() ? 0 : 1; +} + +void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (offset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + m_frame->selectionController()->moveTo(VisiblePosition(node, offset, DOWNSTREAM)); +} + +void DOMSelection::collapseToEnd() +{ + if (!m_frame) + return; + + const Selection& selection = m_frame->selectionController()->selection(); + m_frame->selectionController()->moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); +} + +void DOMSelection::collapseToStart() +{ + if (!m_frame) + return; + + const Selection& selection = m_frame->selectionController()->selection(); + m_frame->selectionController()->moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); +} + +void DOMSelection::empty() +{ + if (!m_frame) + return; + m_frame->selectionController()->moveTo(VisiblePosition()); +} + +void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (baseOffset < 0 || extentOffset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + VisiblePosition visibleBase = VisiblePosition(baseNode, baseOffset, DOWNSTREAM); + VisiblePosition visibleExtent = VisiblePosition(extentNode, extentOffset, DOWNSTREAM); + + m_frame->selectionController()->moveTo(visibleBase, visibleExtent); +} + +void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + if (offset < 0) { + ec = INDEX_SIZE_ERR; + return; + } + m_frame->selectionController()->moveTo(VisiblePosition(node, offset, DOWNSTREAM)); +} + +void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString) +{ + if (!m_frame) + return; + + String alterStringLower = alterString.lower(); + SelectionController::EAlteration alter; + if (alterStringLower == "extend") + alter = SelectionController::EXTEND; + else if (alterStringLower == "move") + alter = SelectionController::MOVE; + else + return; + + String directionStringLower = directionString.lower(); + SelectionController::EDirection direction; + if (directionStringLower == "forward") + direction = SelectionController::FORWARD; + else if (directionStringLower == "backward") + direction = SelectionController::BACKWARD; + else if (directionStringLower == "left") + direction = SelectionController::LEFT; + else if (directionStringLower == "right") + direction = SelectionController::RIGHT; + else + return; + + String granularityStringLower = granularityString.lower(); + TextGranularity granularity; + if (granularityStringLower == "character") + granularity = CharacterGranularity; + else if (granularityStringLower == "word") + granularity = WordGranularity; + else if (granularityStringLower == "sentence") + granularity = SentenceGranularity; + else if (granularityStringLower == "line") + granularity = LineGranularity; + else if (granularityStringLower == "paragraph") + granularity = ParagraphGranularity; + else if (granularityStringLower == "lineboundary") + granularity = LineBoundary; + else if (granularityStringLower == "sentenceboundary") + granularity = SentenceBoundary; + else if (granularityStringLower == "paragraphboundary") + granularity = ParagraphBoundary; + else if (granularityStringLower == "documentboundary") + granularity = DocumentBoundary; + else + return; + + m_frame->selectionController()->modify(alter, direction, granularity, false); +} + +void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec) +{ + if (!m_frame) + return; + + if (!node) { + ec = TYPE_MISMATCH_ERR; + return; + } + if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) { + ec = INDEX_SIZE_ERR; + return; + } + + SelectionController* selectionController = m_frame->selectionController(); + selectionController->expandUsingGranularity(CharacterGranularity); + selectionController->setExtent(VisiblePosition(node, offset, DOWNSTREAM)); +} + +PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec) +{ + if (!m_frame) + return 0; + + if (index < 0 || index >= rangeCount()) { + ec = INDEX_SIZE_ERR; + return 0; + } + + const Selection& selection = m_frame->selectionController()->selection(); + return selection.toRange(); +} + +void DOMSelection::removeAllRanges() +{ + if (!m_frame) + return; + m_frame->selectionController()->clear(); +} + +void DOMSelection::addRange(Range* r) +{ + if (!m_frame) + return; + if (!r) + return; + + SelectionController* selectionController = m_frame->selectionController(); + + if (selectionController->isNone()) { + selectionController->setSelection(Selection(r)); + return; + } + + RefPtr<Range> range = selectionController->selection().toRange(); + ExceptionCode ec = 0; + if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), ec) == -1) { + // We don't support discontiguous selection. We don't do anything if r and range don't intersect. + if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) > -1) { + if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) + // The original range and r intersect. + selectionController->setSelection(Selection(r->startPosition(), range->endPosition(), DOWNSTREAM)); + else + // r contains the original range. + selectionController->setSelection(Selection(r)); + } + } else { + // We don't support discontiguous selection. We don't do anything if r and range don't intersect. + if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), ec) < 1) { + if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), ec) == -1) + // The original range contains r. + selectionController->setSelection(Selection(range.get())); + else + // The original range and r intersect. + selectionController->setSelection(Selection(range->startPosition(), r->endPosition(), DOWNSTREAM)); + } + } +} + +void DOMSelection::deleteFromDocument() +{ + if (!m_frame) + return; + + SelectionController* selectionController = m_frame->selectionController(); + + if (selectionController->isNone()) + return; + + if (isCollapsed()) + selectionController->modify(SelectionController::EXTEND, SelectionController::BACKWARD, CharacterGranularity); + + RefPtr<Range> selectedRange = selectionController->selection().toRange(); + + ExceptionCode ec = 0; + selectedRange->deleteContents(ec); + ASSERT(!ec); + + setBaseAndExtent(selectedRange->startContainer(ec), selectedRange->startOffset(ec), selectedRange->startContainer(ec), selectedRange->startOffset(ec), ec); + ASSERT(!ec); +} + +bool DOMSelection::containsNode(const Node* n, bool allowPartial) const +{ + if (!m_frame) + return false; + + SelectionController* selectionController = m_frame->selectionController(); + + if (!n || selectionController->isNone()) + return false; + + Node* parentNode = n->parentNode(); + unsigned nodeIndex = n->nodeIndex(); + RefPtr<Range> selectedRange = selectionController->selection().toRange(); + + if (!parentNode) + return false; + + ExceptionCode ec = 0; + bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) >= 0 + && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) <= 0; + ASSERT(!ec); + if (nodeFullySelected) + return true; + + bool nodeFullyUnselected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(ec), selectedRange->endOffset(ec)) > 0 + || Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(ec), selectedRange->startOffset(ec)) < 0; + ASSERT(!ec); + if (nodeFullyUnselected) + return false; + + return allowPartial || n->isTextNode(); +} + +void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec) +{ + if (!n) + return; + + // This doesn't (and shouldn't) select text node characters. + setBaseAndExtent(n, 0, n, n->childNodeCount(), ec); +} + +String DOMSelection::toString() +{ + if (!m_frame) + return String(); + + return plainText(m_frame->selectionController()->selection().toRange().get()); +} + +} // namespace WebCore diff --git a/WebCore/page/DOMSelection.h b/WebCore/page/DOMSelection.h new file mode 100644 index 0000000..fd8d1fc --- /dev/null +++ b/WebCore/page/DOMSelection.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef DOMSelection_h +#define DOMSelection_h + +#include <wtf/RefCounted.h> +#include <wtf/Forward.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + + class Frame; + class Range; + class Node; + class String; + + typedef int ExceptionCode; + + class DOMSelection : public RefCounted<DOMSelection> { + public: + static PassRefPtr<DOMSelection> create(Frame* frame) { return adoptRef(new DOMSelection(frame)); } + + Frame* frame() const; + void disconnectFrame(); + + // Safari Selection Object API + // These methods return the valid equivalents of internal editing positions. + Node* baseNode() const; + Node* extentNode() const; + int baseOffset() const; + int extentOffset() const; + String type() const; + void setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode&); + void setPosition(Node*, int offset, ExceptionCode&); + void modify(const String& alter, const String& direction, const String& granularity); + + // Mozilla Selection Object API + // In Firefox, anchor/focus are the equal to the start/end of the selection, + // but reflect the direction in which the selection was made by the user. That does + // not mean that they are base/extent, since the base/extent don't reflect + // expansion. + // These methods return the valid equivalents of internal editing positions. + Node* anchorNode() const; + int anchorOffset() const; + Node* focusNode() const; + int focusOffset() const; + bool isCollapsed() const; + int rangeCount() const; + void collapse(Node*, int offset, ExceptionCode&); + void collapseToEnd(); + void collapseToStart(); + void extend(Node*, int offset, ExceptionCode&); + PassRefPtr<Range> getRangeAt(int, ExceptionCode&); + void removeAllRanges(); + void addRange(Range*); + void deleteFromDocument(); + bool containsNode(const Node*, bool partlyContained) const; + void selectAllChildren(Node*, ExceptionCode&); + + String toString(); + + // Microsoft Selection Object API + void empty(); + //void clear(); + //TextRange *createRange(); + + private: + DOMSelection(Frame*); + + Frame* m_frame; + }; + +} // namespace WebCore + +#endif // DOMSelection_h diff --git a/WebCore/page/DOMSelection.idl b/WebCore/page/DOMSelection.idl new file mode 100644 index 0000000..85d23bf --- /dev/null +++ b/WebCore/page/DOMSelection.idl @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module window { + + interface DOMSelection { + readonly attribute Node anchorNode; + readonly attribute long anchorOffset; + readonly attribute Node focusNode; + readonly attribute long focusOffset; + readonly attribute Node baseNode; + readonly attribute long baseOffset; + readonly attribute Node extentNode; + readonly attribute long extentOffset; + readonly attribute boolean isCollapsed; + readonly attribute DOMString type; + readonly attribute long rangeCount; + + void collapse(in Node node, in long index) + raises(DOMException); + void collapseToEnd(); + void collapseToStart(); + void deleteFromDocument(); + boolean containsNode(in Node node, in boolean allowPartial); + void selectAllChildren(in Node node) + raises(DOMException); + void empty(); + void setBaseAndExtent(in Node baseNode, in long baseOffset, in Node extentNode, in long extentOffset) + raises(DOMException); + void setPosition(in Node node, in long offset) + raises(DOMException); + void modify(in DOMString alter, in DOMString direction, in DOMString granularity); + void extend(in Node node, in long offset) + raises(DOMException); + Range getRangeAt(in long index) + raises(DOMException); + void removeAllRanges(); + void addRange(in Range range); + +#if defined(LANGUAGE_JAVASCRIPT) + [DontEnum] DOMString toString(); +#endif + }; + +} diff --git a/WebCore/page/DOMWindow.cpp b/WebCore/page/DOMWindow.cpp new file mode 100644 index 0000000..22f7489 --- /dev/null +++ b/WebCore/page/DOMWindow.cpp @@ -0,0 +1,740 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DOMWindow.h" + +#include "BarInfo.h" +#include "CSSComputedStyleDeclaration.h" +#include "CSSRuleList.h" +#include "CSSStyleSelector.h" +#include "Chrome.h" +#include "Console.h" +#include "DOMSelection.h" +#include "Document.h" +#include "Element.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "History.h" +#include "MessageEvent.h" +#include "Page.h" +#include "PlatformScreen.h" +#include "PlatformString.h" +#include "Screen.h" +#include <algorithm> +#include <wtf/MathExtras.h> + +#if ENABLE(DATABASE) +#include "Database.h" +#endif + +using std::min; +using std::max; + +namespace WebCore { + +// This function: +// 1) Validates the pending changes are not changing to NaN +// 2) Constrains the window rect to no smaller than 100 in each dimension and no +// bigger than the the float rect's dimensions. +// 3) Constrain window rect to within the top and left boundaries of the screen rect +// 4) Constraint the window rect to within the bottom and right boundaries of the +// screen rect. +// 5) Translate the window rect coordinates to be within the coordinate space of +// the screen rect. +void DOMWindow::adjustWindowRect(const FloatRect& screen, FloatRect& window, const FloatRect& pendingChanges) +{ + // Make sure we're in a valid state before adjusting dimensions. + ASSERT(isfinite(screen.x())); + ASSERT(isfinite(screen.y())); + ASSERT(isfinite(screen.width())); + ASSERT(isfinite(screen.height())); + ASSERT(isfinite(window.x())); + ASSERT(isfinite(window.y())); + ASSERT(isfinite(window.width())); + ASSERT(isfinite(window.height())); + + // Update window values if new requested values are not NaN. + if (!isnan(pendingChanges.x())) + window.setX(pendingChanges.x()); + if (!isnan(pendingChanges.y())) + window.setY(pendingChanges.y()); + if (!isnan(pendingChanges.width())) + window.setWidth(pendingChanges.width()); + if (!isnan(pendingChanges.height())) + window.setHeight(pendingChanges.height()); + + // Resize the window to between 100 and the screen width and height. + window.setWidth(min(max(100.0f, window.width()), screen.width())); + window.setHeight(min(max(100.0f, window.height()), screen.height())); + + // Constrain the window position to the screen. + window.setX(max(screen.x(), min(window.x(), screen.right() - window.width()))); + window.setY(max(screen.y(), min(window.y(), screen.bottom() - window.height()))); +} + +DOMWindow::DOMWindow(Frame* frame) + : m_frame(frame) +{ +} + +DOMWindow::~DOMWindow() +{ +} + +void DOMWindow::disconnectFrame() +{ + m_frame = 0; + clear(); +} + +void DOMWindow::clear() +{ + if (m_screen) + m_screen->disconnectFrame(); + m_screen = 0; + + if (m_selection) + m_selection->disconnectFrame(); + m_selection = 0; + + if (m_history) + m_history->disconnectFrame(); + m_history = 0; + + if (m_locationbar) + m_locationbar->disconnectFrame(); + m_locationbar = 0; + + if (m_menubar) + m_menubar->disconnectFrame(); + m_menubar = 0; + + if (m_personalbar) + m_personalbar->disconnectFrame(); + m_personalbar = 0; + + if (m_scrollbars) + m_scrollbars->disconnectFrame(); + m_scrollbars = 0; + + if (m_statusbar) + m_statusbar->disconnectFrame(); + m_statusbar = 0; + + if (m_toolbar) + m_toolbar->disconnectFrame(); + m_toolbar = 0; + + if (m_console) + m_console->disconnectFrame(); + m_console = 0; +} + +Screen* DOMWindow::screen() const +{ + if (!m_screen) + m_screen = Screen::create(m_frame); + return m_screen.get(); +} + +History* DOMWindow::history() const +{ + if (!m_history) + m_history = History::create(m_frame); + return m_history.get(); +} + +BarInfo* DOMWindow::locationbar() const +{ + if (!m_locationbar) + m_locationbar = BarInfo::create(m_frame, BarInfo::Locationbar); + return m_locationbar.get(); +} + +BarInfo* DOMWindow::menubar() const +{ + if (!m_menubar) + m_menubar = BarInfo::create(m_frame, BarInfo::Menubar); + return m_menubar.get(); +} + +BarInfo* DOMWindow::personalbar() const +{ + if (!m_personalbar) + m_personalbar = BarInfo::create(m_frame, BarInfo::Personalbar); + return m_personalbar.get(); +} + +BarInfo* DOMWindow::scrollbars() const +{ + if (!m_scrollbars) + m_scrollbars = BarInfo::create(m_frame, BarInfo::Scrollbars); + return m_scrollbars.get(); +} + +BarInfo* DOMWindow::statusbar() const +{ + if (!m_statusbar) + m_statusbar = BarInfo::create(m_frame, BarInfo::Statusbar); + return m_statusbar.get(); +} + +BarInfo* DOMWindow::toolbar() const +{ + if (!m_toolbar) + m_toolbar = BarInfo::create(m_frame, BarInfo::Toolbar); + return m_toolbar.get(); +} + +Console* DOMWindow::console() const +{ + if (!m_console) + m_console = Console::create(m_frame); + return m_console.get(); +} + +#if ENABLE(CROSS_DOCUMENT_MESSAGING) +void DOMWindow::postMessage(const String& message, const String& domain, const String& uri, DOMWindow* source) const +{ + ExceptionCode ec; + document()->dispatchEvent(new MessageEvent(message, domain, uri, source), ec, true); +} +#endif + +DOMSelection* DOMWindow::getSelection() +{ + if (!m_selection) + m_selection = DOMSelection::create(m_frame); + return m_selection.get(); +} + +Element* DOMWindow::frameElement() const +{ + if (!m_frame) + return 0; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (!doc) + return 0; + + // FIXME: could this use m_frame->ownerElement() instead of going through the Document. + return doc->ownerElement(); +} + +void DOMWindow::focus() +{ + if (!m_frame) + return; + + m_frame->focusWindow(); +} + +void DOMWindow::blur() +{ + if (!m_frame) + return; + + m_frame->unfocusWindow(); +} + +void DOMWindow::close() +{ + if (!m_frame) + return; + + if (m_frame->loader()->openedByDOM() || m_frame->loader()->getHistoryLength() <= 1) + m_frame->scheduleClose(); +} + +void DOMWindow::print() +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->print(m_frame); +} + +void DOMWindow::stop() +{ + if (!m_frame) + return; + + // We must check whether the load is complete asynchronously, because we might still be parsing + // the document until the callstack unwinds. + m_frame->loader()->stopForUserCancel(true); +} + +void DOMWindow::alert(const String& message) +{ + if (!m_frame) + return; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateRendering(); + + Page* page = m_frame->page(); + if (!page) + return; + + page->chrome()->runJavaScriptAlert(m_frame, message); +} + +bool DOMWindow::confirm(const String& message) +{ + if (!m_frame) + return false; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateRendering(); + + Page* page = m_frame->page(); + if (!page) + return false; + + return page->chrome()->runJavaScriptConfirm(m_frame, message); +} + +String DOMWindow::prompt(const String& message, const String& defaultValue) +{ + if (!m_frame) + return String(); + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateRendering(); + + Page* page = m_frame->page(); + if (!page) + return String(); + + String returnValue; + if (page->chrome()->runJavaScriptPrompt(m_frame, message, defaultValue, returnValue)) + return returnValue; + + return String(); +} + +bool DOMWindow::find(const String& string, bool caseSensitive, bool backwards, bool wrap, bool wholeWord, bool searchInFrames, bool showDialog) const +{ + if (!m_frame) + return false; + + // FIXME (13016): Support wholeWord, searchInFrames and showDialog + return m_frame->findString(string, !backwards, caseSensitive, wrap, false); +} + +bool DOMWindow::offscreenBuffering() const +{ + return true; +} + +int DOMWindow::outerHeight() const +{ + if (!m_frame) + return 0; + + Page* page = m_frame->page(); + if (!page) + return 0; + + return static_cast<int>(page->chrome()->windowRect().height()); +} + +int DOMWindow::outerWidth() const +{ + if (!m_frame) + return 0; + + Page* page = m_frame->page(); + if (!page) + return 0; + + return static_cast<int>(page->chrome()->windowRect().width()); +} + +int DOMWindow::innerHeight() const +{ + if (!m_frame) + return 0; + + FrameView* view = m_frame->view(); + if (!view) + return 0; + + return view->height(); +} + +int DOMWindow::innerWidth() const +{ + if (!m_frame) + return 0; + + FrameView* view = m_frame->view(); + if (!view) + return 0; + + return view->width(); +} + +int DOMWindow::screenX() const +{ + if (!m_frame) + return 0; + + Page* page = m_frame->page(); + if (!page) + return 0; + + return static_cast<int>(page->chrome()->windowRect().x()); +} + +int DOMWindow::screenY() const +{ + if (!m_frame) + return 0; + + Page* page = m_frame->page(); + if (!page) + return 0; + + return static_cast<int>(page->chrome()->windowRect().y()); +} + +int DOMWindow::scrollX() const +{ + if (!m_frame) + return 0; + + FrameView* view = m_frame->view(); + if (!view) + return 0; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateLayoutIgnorePendingStylesheets(); + + return view->contentsX(); +} + +int DOMWindow::scrollY() const +{ + if (!m_frame) + return 0; + + FrameView* view = m_frame->view(); + if (!view) + return 0; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateLayoutIgnorePendingStylesheets(); + + return view->contentsY(); +} + +bool DOMWindow::closed() const +{ + return !m_frame; +} + +unsigned DOMWindow::length() const +{ + if (!m_frame) + return 0; + + return m_frame->tree()->childCount(); +} + +String DOMWindow::name() const +{ + if (!m_frame) + return String(); + + return m_frame->tree()->name(); +} + +void DOMWindow::setName(const String& string) +{ + if (!m_frame) + return; + + m_frame->tree()->setName(string); +} + +String DOMWindow::status() const +{ + if (!m_frame) + return String(); + + return m_frame->jsStatusBarText(); +} + +void DOMWindow::setStatus(const String& string) +{ + if (!m_frame) + return; + + m_frame->setJSStatusBarText(string); +} + +String DOMWindow::defaultStatus() const +{ + if (!m_frame) + return String(); + + return m_frame->jsDefaultStatusBarText(); +} + +void DOMWindow::setDefaultStatus(const String& string) +{ + if (!m_frame) + return; + + m_frame->setJSDefaultStatusBarText(string); +} + +DOMWindow* DOMWindow::self() const +{ + if (!m_frame) + return 0; + + return m_frame->domWindow(); +} + +DOMWindow* DOMWindow::opener() const +{ + if (!m_frame) + return 0; + + Frame* opener = m_frame->loader()->opener(); + if (!opener) + return 0; + + return opener->domWindow(); +} + +DOMWindow* DOMWindow::parent() const +{ + if (!m_frame) + return 0; + + Frame* parent = m_frame->tree()->parent(); + if (parent) + return parent->domWindow(); + + return m_frame->domWindow(); +} + +DOMWindow* DOMWindow::top() const +{ + if (!m_frame) + return 0; + + Page* page = m_frame->page(); + if (!page) + return 0; + + return page->mainFrame()->domWindow(); +} + +Document* DOMWindow::document() const +{ + if (!m_frame) + return 0; + + ASSERT(m_frame->document()); + return m_frame->document(); +} + +PassRefPtr<CSSStyleDeclaration> DOMWindow::getComputedStyle(Element* elt, const String&) const +{ + if (!elt) + return 0; + + // FIXME: This needs to work with pseudo elements. + return new CSSComputedStyleDeclaration(elt); +} + +PassRefPtr<CSSRuleList> DOMWindow::getMatchedCSSRules(Element* elt, const String& pseudoElt, bool authorOnly) const +{ + if (!m_frame) + return 0; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (!doc) + return 0; + + if (!pseudoElt.isEmpty()) + return doc->styleSelector()->pseudoStyleRulesForElement(elt, pseudoElt.impl(), authorOnly); + return doc->styleSelector()->styleRulesForElement(elt, authorOnly); +} + +double DOMWindow::devicePixelRatio() const +{ + if (!m_frame) + return 0.0; + + Page* page = m_frame->page(); + if (!page) + return 0.0; + + return page->chrome()->scaleFactor(); +} + +#if ENABLE(DATABASE) +PassRefPtr<Database> DOMWindow::openDatabase(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, ExceptionCode& ec) +{ + if (!m_frame) + return 0; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (!doc) + return 0; + + return Database::openDatabase(doc, name, version, displayName, estimatedSize, ec); +} +#endif + +void DOMWindow::scrollBy(int x, int y) const +{ + if (!m_frame) + return; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateLayoutIgnorePendingStylesheets(); + + FrameView* view = m_frame->view(); + if (!view) + return; + + view->scrollBy(x, y); +} + +void DOMWindow::scrollTo(int x, int y) const +{ + if (!m_frame) + return; + + Document* doc = m_frame->document(); + ASSERT(doc); + if (doc) + doc->updateLayoutIgnorePendingStylesheets(); + + FrameView* view = m_frame->view(); + if (!view) + return; + + view->setContentsPos(x, y); +} + +void DOMWindow::moveBy(float x, float y) const +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + FloatRect fr = page->chrome()->windowRect(); + FloatRect update = fr; + update.move(x, y); + // Security check (the spec talks about UniversalBrowserWrite to disable this check...) + adjustWindowRect(screenAvailableRect(page->mainFrame()->view()), fr, update); + page->chrome()->setWindowRect(fr); +} + +void DOMWindow::moveTo(float x, float y) const +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + FloatRect fr = page->chrome()->windowRect(); + FloatRect sr = screenAvailableRect(page->mainFrame()->view()); + fr.setLocation(sr.location()); + FloatRect update = fr; + update.move(x, y); + // Security check (the spec talks about UniversalBrowserWrite to disable this check...) + adjustWindowRect(sr, fr, update); + page->chrome()->setWindowRect(fr); +} + +void DOMWindow::resizeBy(float x, float y) const +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + FloatRect fr = page->chrome()->windowRect(); + FloatSize dest = fr.size() + FloatSize(x, y); + FloatRect update(fr.location(), dest); + adjustWindowRect(screenAvailableRect(page->mainFrame()->view()), fr, update); + page->chrome()->setWindowRect(fr); +} + +void DOMWindow::resizeTo(float width, float height) const +{ + if (!m_frame) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + FloatRect fr = page->chrome()->windowRect(); + FloatSize dest = FloatSize(width, height); + FloatRect update(fr.location(), dest); + adjustWindowRect(screenAvailableRect(page->mainFrame()->view()), fr, update); + page->chrome()->setWindowRect(fr); +} + +} // namespace WebCore diff --git a/WebCore/page/DOMWindow.h b/WebCore/page/DOMWindow.h new file mode 100644 index 0000000..adb4731 --- /dev/null +++ b/WebCore/page/DOMWindow.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DOMWindow_h +#define DOMWindow_h + +#include "PlatformString.h" +#include <wtf/RefCounted.h> +#include <wtf/Forward.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class BarInfo; + class CSSRuleList; + class CSSStyleDeclaration; + class Console; + class DOMSelection; + class Database; + class Document; + class Element; + class FloatRect; + class Frame; + class History; + class Screen; + + typedef int ExceptionCode; + + class DOMWindow : public RefCounted<DOMWindow> { + public: + static PassRefPtr<DOMWindow> create(Frame* frame) { return adoptRef(new DOMWindow(frame)); } + virtual ~DOMWindow(); + + Frame* frame() { return m_frame; } + void disconnectFrame(); + + void clear(); + + static void adjustWindowRect(const FloatRect& screen, FloatRect& window, const FloatRect& pendingChanges); + + // DOM Level 0 + Screen* screen() const; + History* history() const; + BarInfo* locationbar() const; + BarInfo* menubar() const; + BarInfo* personalbar() const; + BarInfo* scrollbars() const; + BarInfo* statusbar() const; + BarInfo* toolbar() const; + + DOMSelection* getSelection(); + + Element* frameElement() const; + + void focus(); + void blur(); + void close(); + void print(); + void stop(); + + void alert(const String& message); + bool confirm(const String& message); + String prompt(const String& message, const String& defaultValue); + + bool find(const String&, bool caseSensitive, bool backwards, bool wrap, bool wholeWord, bool searchInFrames, bool showDialog) const; + + bool offscreenBuffering() const; + + int outerHeight() const; + int outerWidth() const; + int innerHeight() const; + int innerWidth() const; + int screenX() const; + int screenY() const; + int screenLeft() const { return screenX(); } + int screenTop() const { return screenY(); } + int scrollX() const; + int scrollY() const; + int pageXOffset() const { return scrollX(); } + int pageYOffset() const { return scrollY(); } + + bool closed() const; + + unsigned length() const; + + String name() const; + void setName(const String&); + + String status() const; + void setStatus(const String&); + String defaultStatus() const; + void setDefaultStatus(const String&); + // This attribute is an alias of defaultStatus and is necessary for legacy uses. + String defaultstatus() const { return defaultStatus(); } + void setDefaultstatus(const String& status) { setDefaultStatus(status); } + + // Self referential attributes + DOMWindow* self() const; + DOMWindow* window() const { return self(); } + DOMWindow* frames() const { return self(); } + + DOMWindow* opener() const; + DOMWindow* parent() const; + DOMWindow* top() const; + + // DOM Level 2 AbstractView Interface + Document* document() const; + + // DOM Level 2 Style Interface + PassRefPtr<CSSStyleDeclaration> getComputedStyle(Element*, const String& pseudoElt) const; + + // WebKit extensions + PassRefPtr<CSSRuleList> getMatchedCSSRules(Element*, const String& pseudoElt, bool authorOnly = true) const; + double devicePixelRatio() const; + +#if ENABLE(DATABASE) + // HTML 5 client-side database + PassRefPtr<Database> openDatabase(const String& name, const String& version, const String& displayName, unsigned long estimatedSize, ExceptionCode&); +#endif + + Console* console() const; + +#if ENABLE(CROSS_DOCUMENT_MESSAGING) + void postMessage(const String& message, const String& domain, const String& uri, DOMWindow* source) const; +#endif + + void scrollBy(int x, int y) const; + void scrollTo(int x, int y) const; + void scroll(int x, int y) const { scrollTo(x, y); } + + void moveBy(float x, float y) const; + void moveTo(float x, float y) const; + + void resizeBy(float x, float y) const; + void resizeTo(float width, float height) const; + + private: + DOMWindow(Frame*); + + Frame* m_frame; + mutable RefPtr<Screen> m_screen; + mutable RefPtr<DOMSelection> m_selection; + mutable RefPtr<History> m_history; + mutable RefPtr<BarInfo> m_locationbar; + mutable RefPtr<BarInfo> m_menubar; + mutable RefPtr<BarInfo> m_personalbar; + mutable RefPtr<BarInfo> m_scrollbars; + mutable RefPtr<BarInfo> m_statusbar; + mutable RefPtr<BarInfo> m_toolbar; + mutable RefPtr<Console> m_console; + }; + +} // namespace WebCore + +#endif diff --git a/WebCore/page/DOMWindow.idl b/WebCore/page/DOMWindow.idl new file mode 100644 index 0000000..655d6ab --- /dev/null +++ b/WebCore/page/DOMWindow.idl @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module window { + + interface [ + LegacyParent=KJS::Window, + DoNotCache, + CheckDomainSecurity, + GenerateNativeConverter, + CustomGetOwnPropertySlot, + CustomPutFunction, + CustomDeleteProperty, + CustomGetPropertyNames + ] DOMWindow { + // DOM Level 0 + readonly attribute Screen screen; + readonly attribute [DoNotCheckDomainSecurity] History history; + attribute [Replaceable] BarInfo locationbar; + attribute [Replaceable] BarInfo menubar; + attribute [Replaceable] BarInfo personalbar; + attribute [Replaceable] BarInfo scrollbars; + attribute [Replaceable] BarInfo statusbar; + attribute [Replaceable] BarInfo toolbar; + + DOMSelection getSelection(); + + readonly attribute [CheckNodeSecurity] Element frameElement; + + [DoNotCheckDomainSecurity] void focus(); + [DoNotCheckDomainSecurity] void blur(); + [DoNotCheckDomainSecurity] void close(); + + void print(); + void stop(); + + void alert(in DOMString message); + boolean confirm(in DOMString message); + [ConvertNullStringTo=Null] DOMString prompt(in DOMString message, + in [ConvertUndefinedOrNullToNullString] DOMString defaultValue); + + boolean find(in DOMString string, + in boolean caseSensitive, + in boolean backwards, + in boolean wrap, + in boolean wholeWord, + in boolean searchInFrames, + in boolean showDialog); + + attribute [Replaceable] boolean offscreenBuffering; + + attribute [Replaceable] long outerHeight; + attribute [Replaceable] long outerWidth; + attribute [Replaceable] long innerHeight; + attribute [Replaceable] long innerWidth; + attribute [Replaceable] long screenX; + attribute [Replaceable] long screenY; + attribute [Replaceable] long screenLeft; + attribute [Replaceable] long screenTop; + attribute [Replaceable] long scrollX; + attribute [Replaceable] long scrollY; + readonly attribute long pageXOffset; + readonly attribute long pageYOffset; + + [RequiresAllArguments] void scrollBy(in long x, in long y); + [RequiresAllArguments] void scrollTo(in long x, in long y); + [RequiresAllArguments] void scroll(in long x, in long y); + [RequiresAllArguments] void moveBy(in float x, in float y); // FIXME: this should take longs not floats. + [RequiresAllArguments] void moveTo(in float x, in float y); // FIXME: this should take longs not floats. + [RequiresAllArguments] void resizeBy(in float x, in float y); // FIXME: this should take longs not floats. + [RequiresAllArguments] void resizeTo(in float width, in float height); // FIXME: this should take longs not floats. + + readonly attribute [DoNotCheckDomainSecurity] boolean closed; + + attribute [Replaceable, DoNotCheckDomainSecurityOnGet] unsigned long length; + + attribute DOMString name; + + attribute DOMString status; + attribute DOMString defaultStatus; +#if defined(LANGUAGE_JAVASCRIPT) + // This attribute is an alias of defaultStatus and is necessary for legacy uses. + attribute DOMString defaultstatus; +#endif + + // Self referential attributes + attribute [Replaceable, DoNotCheckDomainSecurityOnGet] DOMWindow self; + readonly attribute [DoNotCheckDomainSecurity] DOMWindow window; + attribute [Replaceable, DoNotCheckDomainSecurityOnGet] DOMWindow frames; + + attribute [Replaceable, DoNotCheckDomainSecurityOnGet] DOMWindow opener; + attribute [Replaceable, DoNotCheckDomainSecurity] DOMWindow parent; + attribute [Replaceable, DoNotCheckDomainSecurity] DOMWindow top; + + // DOM Level 2 AbstractView Interface + readonly attribute Document document; + + // DOM Level 2 Style Interface + CSSStyleDeclaration getComputedStyle(in Element element, + in DOMString pseudoElement); + + // WebKit extensions + CSSRuleList getMatchedCSSRules(in Element element, + in DOMString pseudoElement, + in [Optional] boolean authorOnly); + attribute [Replaceable] double devicePixelRatio; + +#if defined(ENABLE_DATABASE) + Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize) + raises(DOMException); +#endif + + attribute [Replaceable] Console console; + +#if defined(ENABLE_CROSS_DOCUMENT_MESSAGING) + // cross-document messaging + [DoNotCheckDomainSecurity, Custom] void postMessage(in DOMString message); +#endif + +#if defined(LANGUAGE_JAVASCRIPT) + // Global constructors + attribute StyleSheetConstructor StyleSheet; + attribute CSSStyleSheetConstructor CSSStyleSheet; + + attribute CSSValueConstructor CSSValue; + attribute CSSPrimitiveValueConstructor CSSPrimitiveValue; + attribute CSSValueListConstructor CSSValueList; + + attribute CSSRuleConstructor CSSRule; + attribute CSSCharsetRuleConstructor CSSCharsetRule; + attribute CSSFontFaceRuleConstructor CSSFontFaceRule; + attribute CSSImportRuleConstructor CSSImportRule; + attribute CSSMediaRuleConstructor CSSMediaRule; + attribute CSSPageRuleConstructor CSSPageRule; + attribute CSSStyleRuleConstructor CSSStyleRule; + + attribute CSSStyleDeclarationConstructor CSSStyleDeclaration; + attribute MediaListConstructor MediaList; + attribute CounterConstructor Counter; + attribute CSSRuleListConstructor CSSRuleList; + attribute RectConstructor Rect; + attribute StyleSheetListConstructor StyleSheetList; + + // FIXME: Implement the commented-out global constructors for interfaces listed in DOM Level 3 Core specification. + attribute DOMCoreExceptionConstructor DOMException; +// attribute DOMStringListConstructor DOMStringList; +// attribute NameListConstructor NameList; +// attribute DOMImplementationListConstructor DOMImplementationList; +// attribute DOMImplementationSourceConstructor DOMImplementationSource; + attribute DOMImplementationConstructor DOMImplementation; + attribute DocumentFragmentConstructor DocumentFragment; + attribute DocumentConstructor Document; + attribute NodeConstructor Node; + attribute NodeListConstructor NodeList; + attribute NamedNodeMapConstructor NamedNodeMap; + attribute CharacterDataConstructor CharacterData; + attribute AttrConstructor Attr; + attribute ElementConstructor Element; + attribute TextConstructor Text; + attribute CommentConstructor Comment; +// attribute TypeInfoConstructor TypeInfo; +// attribute UserDataHandlerConstructor UserDataHandler; +// attribute DOMErrorConstructor DOMError; +// attribute DOMErrorHandlerConstructor DOMErrorHandler +// attribute DOMLocatorConstructor DOMLocator; +// attribute DOMConfigurationConstructor DOMConfiguration; + attribute CDATASectionConstructor CDATASection; + attribute DocumentTypeConstructor DocumentType; + attribute NotationConstructor Notation; + attribute EntityConstructor Entity; + attribute EntityReferenceConstructor EntityReference; + attribute ProcessingInstructionConstructor ProcessingInstruction; + + attribute HTMLDocumentConstructor HTMLDocument; + + attribute HTMLElementConstructor HTMLElement; + attribute HTMLAnchorElementConstructor HTMLAnchorElement; + attribute HTMLAppletElementConstructor HTMLAppletElement; + attribute HTMLAreaElementConstructor HTMLAreaElement; + attribute HTMLBRElementConstructor HTMLBRElement; + attribute HTMLBaseElementConstructor HTMLBaseElement; + attribute HTMLBaseFontElementConstructor HTMLBaseFontElement; + attribute HTMLBlockquoteElementConstructor HTMLBlockquoteElement; + attribute HTMLBodyElementConstructor HTMLBodyElement; + attribute HTMLButtonElementConstructor HTMLButtonElement; + attribute HTMLCanvasElementConstructor HTMLCanvasElement; + attribute HTMLDListElementConstructor HTMLDListElement; + attribute HTMLDirectoryElementConstructor HTMLDirectoryElement; + attribute HTMLDivElementConstructor HTMLDivElement; + attribute HTMLEmbedElementConstructor HTMLEmbedElement; + attribute HTMLFieldSetElementConstructor HTMLFieldSetElement; + attribute HTMLFontElementConstructor HTMLFontElement; + attribute HTMLFormElementConstructor HTMLFormElement; + attribute HTMLFrameElementConstructor HTMLFrameElement; + attribute HTMLFrameSetElementConstructor HTMLFrameSetElement; + attribute HTMLHRElementConstructor HTMLHRElement; + attribute HTMLHeadElementConstructor HTMLHeadElement; + attribute HTMLHeadingElementConstructor HTMLHeadingElement; + attribute HTMLHtmlElementConstructor HTMLHtmlElement; + attribute HTMLIFrameElementConstructor HTMLIFrameElement; + attribute HTMLImageElementConstructor HTMLImageElement; + attribute HTMLInputElementConstructor HTMLInputElement; + attribute HTMLIsIndexElementConstructor HTMLIsIndexElement; + attribute HTMLLIElementConstructor HTMLLIElement; + attribute HTMLLabelElementConstructor HTMLLabelElement; + attribute HTMLLegendElementConstructor HTMLLegendElement; + attribute HTMLLinkElementConstructor HTMLLinkElement; + attribute HTMLMapElementConstructor HTMLMapElement; + attribute HTMLMarqueeElementConstructor HTMLMarqueeElement; + attribute HTMLMenuElementConstructor HTMLMenuElement; + attribute HTMLMetaElementConstructor HTMLMetaElement; + attribute HTMLModElementConstructor HTMLModElement; + attribute HTMLOListElementConstructor HTMLOListElement; + attribute HTMLObjectElementConstructor HTMLObjectElement; + attribute HTMLOptGroupElementConstructor HTMLOptGroupElement; + attribute HTMLOptionElementConstructor HTMLOptionElement; + attribute HTMLParagraphElementConstructor HTMLParagraphElement; + attribute HTMLParamElementConstructor HTMLParamElement; + attribute HTMLPreElementConstructor HTMLPreElement; + attribute HTMLQuoteElementConstructor HTMLQuoteElement; + attribute HTMLScriptElementConstructor HTMLScriptElement; + attribute HTMLSelectElementConstructor HTMLSelectElement; + attribute HTMLStyleElementConstructor HTMLStyleElement; + attribute HTMLTableCaptionElementConstructor HTMLTableCaptionElement; + attribute HTMLTableCellElementConstructor HTMLTableCellElement; + attribute HTMLTableColElementConstructor HTMLTableColElement; + attribute HTMLTableElementConstructor HTMLTableElement; + attribute HTMLTableRowElementConstructor HTMLTableRowElement; + attribute HTMLTableSectionElementConstructor HTMLTableSectionElement; + attribute HTMLTextAreaElementConstructor HTMLTextAreaElement; + attribute HTMLTitleElementConstructor HTMLTitleElement; + attribute HTMLUListElementConstructor HTMLUListElement; + + attribute EventConstructor Event; + attribute KeyboardEventConstructor KeyboardEvent; + attribute MouseEventConstructor MouseEvent; + attribute MutationEventConstructor MutationEvent; + attribute OverflowEventConstructor OverflowEvent; + attribute ProgressEventConstructor ProgressEvent; + attribute TextEventConstructor TextEvent; + attribute UIEventConstructor UIEvent; + attribute WheelEventConstructor WheelEvent; + attribute EventExceptionConstructor EventException; + + attribute NodeFilterConstructor NodeFilter; + attribute RangeConstructor Range; + attribute RangeExceptionConstructor RangeException; + + // Mozilla has a separate XMLDocument object for XML documents. + // We just use Document for this. + attribute DocumentConstructor XMLDocument; + + attribute DOMParserConstructor DOMParser; + attribute XMLSerializerConstructor XMLSerializer; + + attribute XMLHttpRequestExceptionConstructor XMLHttpRequestException; + +#if defined(ENABLE_CROSS_DOCUMENT_MESSAGING) + attribute MessageEventConstructor MessageEvent; +#endif + +#if defined(ENABLE_VIDEO) + attribute HTMLAudioElementConstructor HTMLAudioElement; + attribute HTMLMediaElementConstructor HTMLMediaElement; + attribute HTMLVideoElementConstructor HTMLVideoElement; + attribute MediaErrorConstructor MediaError; +#endif + +#if defined(ENABLE_XPATH) + attribute XPathEvaluatorConstructor XPathEvaluator; + attribute XPathResultConstructor XPathResult; + attribute XPathExceptionConstructor XPathException; +#endif + +#if defined(ENABLE_SVG) + attribute SVGAngleConstructor SVGAngle; + attribute SVGColorConstructor SVGColor; +// attribute SVGCSSRuleConstructor SVGCSSRule; + attribute SVGExceptionConstructor SVGException; + attribute SVGGradientElementConstructor SVGGradientElement; + attribute SVGLengthConstructor SVGLength; + attribute SVGMarkerElementConstructor SVGMarkerElement; + attribute SVGPaintConstructor SVGPaint; + attribute SVGPathSegConstructor SVGPathSeg; + attribute SVGPreserveAspectRatioConstructor SVGPreserveAspectRatio; + attribute SVGRenderingIntentConstructor SVGRenderingIntent; + attribute SVGTextContentElementConstructor SVGTextContentElement; + attribute SVGTextPathElementConstructor SVGTextPathElement; + attribute SVGTransformConstructor SVGTransform; + attribute SVGUnitTypesConstructor SVGUnitTypes; +// attribute SVGZoomAndPanConstructor SVGZoomAndPan; +#endif + +#if defined(ENABLED_SVG_FILTERS) + attribute SVGComponentTransferFunctionElementConstructor SVGComponentTransferFunctionElement; + attribute SVGFEBlendElementConstructor SVGFEBlendElement; + attribute SVGFEColorMatrixElementConstructor SVGFEColorMatrixElement; + attribute SVGFECompositeElementConstructor SVGFECompositeElement; +// attribute SVGFEConvolveMatrixElementConstructor SVGFEConvolveMatrixElement; + attribute SVGFEDisplacementMapElementConstructor SVGFEDisplacementMapElement; +// attribute SVGFEMorphologyElementConstructor SVGFEMorphologyElement; + attribute SVGFETurbulenceElementConstructor SVGFETurbulenceElement; +#endif + +#endif // defined(LANGUAGE_JAVASCRIPT) + }; + +} diff --git a/WebCore/page/DragActions.h b/WebCore/page/DragActions.h new file mode 100644 index 0000000..37b783b --- /dev/null +++ b/WebCore/page/DragActions.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DragActions_h +#define DragActions_h + +#include <limits.h> + +namespace WebCore { + + // WebCoreDragDestinationAction should be kept in sync with WebDragDestinationAction + typedef enum { + DragDestinationActionNone = 0, + DragDestinationActionDHTML = 1, + DragDestinationActionEdit = 2, + DragDestinationActionLoad = 4, + DragDestinationActionAny = UINT_MAX + } DragDestinationAction; + + // WebCoreDragSourceAction should be kept in sync with WebDragSourceAction + typedef enum { + DragSourceActionNone = 0, + DragSourceActionDHTML = 1, + DragSourceActionImage = 2, + DragSourceActionLink = 4, + DragSourceActionSelection = 8, + DragSourceActionAny = UINT_MAX + } DragSourceAction; + + //matches NSDragOperation + typedef enum { + DragOperationNone = 0, + DragOperationCopy = 1, + DragOperationLink = 2, + DragOperationGeneric = 4, + DragOperationPrivate = 8, + DragOperationMove = 16, + DragOperationDelete = 32, + DragOperationEvery = UINT_MAX + } DragOperation; + +} + +#endif // !DragActions_h diff --git a/WebCore/page/DragClient.h b/WebCore/page/DragClient.h new file mode 100644 index 0000000..4f343a0 --- /dev/null +++ b/WebCore/page/DragClient.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef DragClient_h +#define DragClient_h + +#include "DragActions.h" +#include "DragImage.h" +#include "IntPoint.h" + +#if PLATFORM(MAC) +#ifdef __OBJC__ +@class DOMElement; +@class NSURL; +@class NSString; +@class NSPasteboard; +#else +class DOMElement; +class NSURL; +class NSString; +class NSPasteboard; +#endif +#endif + +namespace WebCore { + + class Clipboard; + class DragData; + class Frame; + class Image; + class HTMLImageElement; + + class DragClient { + public: + virtual void willPerformDragDestinationAction(DragDestinationAction, DragData*) = 0; + virtual void willPerformDragSourceAction(DragSourceAction, const IntPoint&, Clipboard*) = 0; + virtual DragDestinationAction actionMaskForDrag(DragData*) = 0; + //We work in window rather than view coordinates here + virtual DragSourceAction dragSourceActionMaskForPoint(const IntPoint& windowPoint) = 0; + + virtual void startDrag(DragImageRef dragImage, const IntPoint& dragImageOrigin, const IntPoint& eventPos, Clipboard*, Frame*, bool linkDrag = false) = 0; + virtual DragImageRef createDragImageForLink(KURL&, const String& label, Frame*) = 0; + + virtual void dragControllerDestroyed() = 0; +#if PLATFORM(MAC) + //Mac specific helper functions to allow access to functionality in webkit -- such as + //web archives and NSPasteboard extras + //not abstract as that would require another #if PLATFORM(MAC) for the SVGImage client empty impl + virtual void declareAndWriteDragImage(NSPasteboard*, DOMElement*, NSURL*, NSString*, Frame*) {}; +#endif + + virtual ~DragClient() {}; + }; + +} + +#endif // !DragClient_h + diff --git a/WebCore/page/DragController.cpp b/WebCore/page/DragController.cpp new file mode 100644 index 0000000..8eec6e0 --- /dev/null +++ b/WebCore/page/DragController.cpp @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragController.h" + +#include "CSSStyleDeclaration.h" +#include "Clipboard.h" +#include "ClipboardAccessPolicy.h" +#include "DocLoader.h" +#include "Document.h" +#include "DocumentFragment.h" +#include "DragActions.h" +#include "DragClient.h" +#include "DragData.h" +#include "Editor.h" +#include "EditorClient.h" +#include "Element.h" +#include "EventHandler.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "HTMLAnchorElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HitTestResult.h" +#include "Image.h" +#include "MoveSelectionCommand.h" +#include "Node.h" +#include "Page.h" +#include "PluginInfoStore.h" +#include "RenderFileUploadControl.h" +#include "RenderImage.h" +#include "ReplaceSelectionCommand.h" +#include "ResourceRequest.h" +#include "SelectionController.h" +#include "Settings.h" +#include "SystemTime.h" +#include "Text.h" +#include "markup.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +static PlatformMouseEvent createMouseEvent(DragData* dragData) +{ + // FIXME: We should fake modifier keys here. + return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(), + LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); + +} + +DragController::DragController(Page* page, DragClient* client) + : m_page(page) + , m_client(client) + , m_document(0) + , m_dragInitiator(0) + , m_dragDestinationAction(DragDestinationActionNone) + , m_dragSourceAction(DragSourceActionNone) + , m_didInitiateDrag(false) + , m_isHandlingDrag(false) + , m_dragOperation(DragOperationNone) +{ +} + +DragController::~DragController() +{ + m_client->dragControllerDestroyed(); +} + +static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, RefPtr<Range> context, + bool allowPlainText, bool& chosePlainText) +{ + ASSERT(dragData); + chosePlainText = false; + + Document* document = context->ownerDocument(); + ASSERT(document); + if (document && dragData->containsCompatibleContent()) { + if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(document)) + return fragment; + + if (dragData->containsURL()) { + String title; + String url = dragData->asURL(&title); + if (!url.isEmpty()) { + ExceptionCode ec; + RefPtr<HTMLAnchorElement> anchor = static_cast<HTMLAnchorElement*>(document->createElement("a", ec).get()); + anchor->setHref(url); + RefPtr<Node> anchorText = document->createTextNode(title); + anchor->appendChild(anchorText, ec); + RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); + fragment->appendChild(anchor, ec); + return fragment.get(); + } + } + } + if (allowPlainText && dragData->containsPlainText()) { + chosePlainText = true; + return createFragmentFromText(context.get(), dragData->asPlainText()).get(); + } + + return 0; +} + +bool DragController::dragIsMove(SelectionController* selectionController, DragData* dragData) +{ + return m_document == m_dragInitiator + && selectionController->isContentEditable() + && !isCopyKeyDown(); +} + +void DragController::cancelDrag() +{ + m_page->dragCaretController()->clear(); +} + +void DragController::dragEnded() +{ + m_dragInitiator = 0; + m_didInitiateDrag = false; + m_page->dragCaretController()->clear(); +} + +DragOperation DragController::dragEntered(DragData* dragData) +{ + return dragEnteredOrUpdated(dragData); +} + +void DragController::dragExited(DragData* dragData) +{ + ASSERT(dragData); + Frame* mainFrame = m_page->mainFrame(); + + if (RefPtr<FrameView> v = mainFrame->view()) { + ClipboardAccessPolicy policy = mainFrame->loader()->baseURL().isLocalFile() ? ClipboardReadable : ClipboardTypesReadable; + RefPtr<Clipboard> clipboard = dragData->createClipboard(policy); + clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); + mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get()); + clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security + } + + cancelDrag(); + m_document = 0; +} + +DragOperation DragController::dragUpdated(DragData* dragData) +{ + return dragEnteredOrUpdated(dragData); +} + +bool DragController::performDrag(DragData* dragData) +{ + ASSERT(dragData); + m_document = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); + if (m_isHandlingDrag) { + ASSERT(m_dragDestinationAction & DragDestinationActionDHTML); + m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData); + RefPtr<Frame> mainFrame = m_page->mainFrame(); + if (mainFrame->view()) { + // Sending an event can result in the destruction of the view and part. + RefPtr<Clipboard> clipboard = dragData->createClipboard(ClipboardReadable); + clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); + mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get()); + clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security + } + m_document = 0; + return true; + } + + if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeDrag(dragData, m_dragDestinationAction)) { + m_document = 0; + return true; + } + + m_document = 0; + + if (operationForLoad(dragData) == DragOperationNone) + return false; + + m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData); + m_page->mainFrame()->loader()->load(ResourceRequest(dragData->asURL())); + return true; +} + +DragOperation DragController::dragEnteredOrUpdated(DragData* dragData) +{ + ASSERT(dragData); + IntPoint windowPoint = dragData->clientPosition(); + + Document* newDraggingDoc = 0; + if (Frame* frame = m_page->mainFrame()) + newDraggingDoc = frame->documentAtPoint(windowPoint); + if (m_document != newDraggingDoc) { + if (m_document) + cancelDrag(); + m_document = newDraggingDoc; + } + + m_dragDestinationAction = m_client->actionMaskForDrag(dragData); + + DragOperation operation = DragOperationNone; + + if (m_dragDestinationAction == DragDestinationActionNone) + cancelDrag(); + else { + operation = tryDocumentDrag(dragData, m_dragDestinationAction); + if (operation == DragOperationNone && (m_dragDestinationAction & DragDestinationActionLoad)) + return operationForLoad(dragData); + } + + return operation; +} + +static HTMLInputElement* asFileInput(Node* node) +{ + ASSERT(node); + + // The button for a FILE input is a sub element with no set input type + // In order to get around this problem we assume any non-FILE input element + // is this internal button, and try querying the shadow parent node. + if (node->hasTagName(HTMLNames::inputTag) && node->isShadowNode() && static_cast<HTMLInputElement*>(node)->inputType() != HTMLInputElement::FILE) + node = node->shadowParentNode(); + + if (!node || !node->hasTagName(HTMLNames::inputTag)) + return 0; + + HTMLInputElement* inputElem = static_cast<HTMLInputElement*>(node); + if (inputElem->inputType() == HTMLInputElement::FILE) + return inputElem; + + return 0; +} + +DragOperation DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask) +{ + ASSERT(dragData); + + if (!m_document) + return DragOperationNone; + + DragOperation operation = DragOperationNone; + if (actionMask & DragDestinationActionDHTML) + operation = tryDHTMLDrag(dragData); + m_isHandlingDrag = operation != DragOperationNone; + + RefPtr<FrameView> frameView = m_document->view(); + if (!frameView) + return operation; + + if ((actionMask & DragDestinationActionEdit) && !m_isHandlingDrag && canProcessDrag(dragData)) { + if (dragData->containsColor()) + return DragOperationGeneric; + + IntPoint dragPos = dragData->clientPosition(); + IntPoint point = frameView->windowToContents(dragPos); + Element* element = m_document->elementFromPoint(point.x(), point.y()); + ASSERT(element); + Frame* innerFrame = element->document()->frame(); + ASSERT(innerFrame); + if (!asFileInput(element)) { + Selection dragCaret; + if (Frame* frame = m_document->frame()) + dragCaret = frame->visiblePositionForPoint(point); + m_page->dragCaretController()->setSelection(dragCaret); + } + + return dragIsMove(innerFrame->selectionController(), dragData) ? DragOperationMove : DragOperationCopy; + } + + m_page->dragCaretController()->clear(); + return operation; +} + +DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint) +{ + m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint); + return m_dragSourceAction; +} + +DragOperation DragController::operationForLoad(DragData* dragData) +{ + ASSERT(dragData); + Document* doc = 0; + doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); + if (doc && (m_didInitiateDrag || doc->isPluginDocument() || (doc->frame() && doc->frame()->editor()->clientIsEditable()))) + return DragOperationNone; + return dragOperation(dragData); +} + +static bool setSelectionToDragCaret(Frame* frame, Selection& dragCaret, RefPtr<Range>& range, const IntPoint& point) +{ + frame->selectionController()->setSelection(dragCaret); + if (frame->selectionController()->isNone()) { + dragCaret = frame->visiblePositionForPoint(point); + frame->selectionController()->setSelection(dragCaret); + range = dragCaret.toRange(); + } + return !frame->selectionController()->isNone() && frame->selectionController()->isContentEditable(); +} + +bool DragController::concludeDrag(DragData* dragData, DragDestinationAction actionMask) +{ + ASSERT(dragData); + ASSERT(!m_isHandlingDrag); + ASSERT(actionMask & DragDestinationActionEdit); + + if (!m_document) + return false; + + IntPoint point = m_document->view()->windowToContents(dragData->clientPosition()); + Element* element = m_document->elementFromPoint(point.x(), point.y()); + ASSERT(element); + Frame* innerFrame = element->ownerDocument()->frame(); + ASSERT(innerFrame); + + if (dragData->containsColor()) { + Color color = dragData->asColor(); + if (!color.isValid()) + return false; + if (!innerFrame) + return false; + RefPtr<Range> innerRange = innerFrame->selectionController()->toRange(); + RefPtr<CSSStyleDeclaration> style = m_document->createCSSStyleDeclaration(); + ExceptionCode ec; + style->setProperty("color", color.name(), ec); + if (!innerFrame->editor()->shouldApplyStyle(style.get(), innerRange.get())) + return false; + m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); + innerFrame->editor()->applyStyle(style.get(), EditActionSetColor); + return true; + } + + if (!m_page->dragController()->canProcessDrag(dragData)) { + m_page->dragCaretController()->clear(); + return false; + } + + if (HTMLInputElement* fileInput = asFileInput(element)) { + if (!dragData->containsFiles()) + return false; + + Vector<String> filenames; + dragData->asFilenames(filenames); + if (filenames.isEmpty()) + return false; + + // Ugly. For security none of the API's available to us to set the input value + // on file inputs. Even forcing a change in HTMLInputElement doesn't work as + // RenderFileUploadControl clears the file when doing updateFromElement() + RenderFileUploadControl* renderer = static_cast<RenderFileUploadControl*>(fileInput->renderer()); + + if (!renderer) + return false; + + // Only take the first filename as <input type="file" /> can only accept one + renderer->receiveDroppedFile(filenames[0]); + return true; + } + + Selection dragCaret(m_page->dragCaretController()->selection()); + m_page->dragCaretController()->clear(); + RefPtr<Range> range = dragCaret.toRange(); + + // For range to be null a WebKit client must have done something bad while + // manually controlling drag behaviour + if (!range) + return false; + DocLoader* loader = range->ownerDocument()->docLoader(); + loader->setAllowStaleResources(true); + if (dragIsMove(innerFrame->selectionController(), dragData) || dragCaret.isContentRichlyEditable()) { + bool chosePlainText = false; + RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, range, true, chosePlainText); + if (!fragment || !innerFrame->editor()->shouldInsertFragment(fragment, range, EditorInsertActionDropped)) { + loader->setAllowStaleResources(false); + return false; + } + + m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); + if (dragIsMove(innerFrame->selectionController(), dragData)) { + bool smartMove = innerFrame->selectionGranularity() == WordGranularity + && innerFrame->editor()->smartInsertDeleteEnabled() + && dragData->canSmartReplace(); + applyCommand(new MoveSelectionCommand(fragment, dragCaret.base(), smartMove)); + } else { + if (setSelectionToDragCaret(innerFrame, dragCaret, range, point)) + applyCommand(new ReplaceSelectionCommand(m_document, fragment, true, dragData->canSmartReplace(), chosePlainText)); + } + } else { + String text = dragData->asPlainText(); + if (text.isEmpty() || !innerFrame->editor()->shouldInsertText(text, range.get(), EditorInsertActionDropped)) { + loader->setAllowStaleResources(false); + return false; + } + + m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); + if (setSelectionToDragCaret(innerFrame, dragCaret, range, point)) + applyCommand(new ReplaceSelectionCommand(m_document, createFragmentFromText(range.get(), text), true, false, true)); + } + loader->setAllowStaleResources(false); + + return true; +} + + +bool DragController::canProcessDrag(DragData* dragData) +{ + ASSERT(dragData); + + if (!dragData->containsCompatibleContent()) + return false; + + IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition()); + HitTestResult result = HitTestResult(point); + if (!m_page->mainFrame()->renderer()) + return false; + + result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, true); + + if (!result.innerNonSharedNode()) + return false; + + if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode())) + return true; + + if (!result.innerNonSharedNode()->isContentEditable()) + return false; + + if (m_didInitiateDrag && m_document == m_dragInitiator && result.isSelected()) + return false; + + return true; +} + +DragOperation DragController::tryDHTMLDrag(DragData* dragData) +{ + ASSERT(dragData); + ASSERT(m_document); + DragOperation op = DragOperationNone; + RefPtr<Frame> frame = m_page->mainFrame(); + RefPtr<FrameView> viewProtector = frame->view(); + if (!viewProtector) + return DragOperationNone; + + ClipboardAccessPolicy policy = frame->loader()->baseURL().isLocalFile() ? ClipboardReadable : ClipboardTypesReadable; + RefPtr<Clipboard> clipboard = dragData->createClipboard(policy); + DragOperation srcOp = dragData->draggingSourceOperationMask(); + clipboard->setSourceOperation(srcOp); + + PlatformMouseEvent event = createMouseEvent(dragData); + if (frame->eventHandler()->updateDragAndDrop(event, clipboard.get())) { + // *op unchanged if no source op was set + if (!clipboard->destinationOperation(op)) { + // The element accepted but they didn't pick an operation, so we pick one for them + // (as does WinIE). + if (srcOp & DragOperationCopy) + op = DragOperationCopy; + else if (srcOp & DragOperationMove || srcOp & DragOperationGeneric) + op = DragOperationMove; + else if (srcOp & DragOperationLink) + op = DragOperationLink; + else + op = DragOperationGeneric; + } else if (!(op & srcOp)) { + op = DragOperationNone; + } + + clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security + return op; + } + return op; +} + +bool DragController::mayStartDragAtEventLocation(const Frame* frame, const IntPoint& framePos) +{ + ASSERT(frame); + ASSERT(frame->settings()); + + if (!frame->view() || !frame->renderer()) + return false; + + HitTestResult mouseDownTarget = HitTestResult(framePos); + + mouseDownTarget = frame->eventHandler()->hitTestResultAtPoint(framePos, true); + + if (mouseDownTarget.image() + && !mouseDownTarget.absoluteImageURL().isEmpty() + && frame->settings()->loadsImagesAutomatically() + && m_dragSourceAction & DragSourceActionImage) + return true; + + if (!mouseDownTarget.absoluteLinkURL().isEmpty() + && m_dragSourceAction & DragSourceActionLink + && mouseDownTarget.isLiveLink()) + return true; + + if (mouseDownTarget.isSelected() + && m_dragSourceAction & DragSourceActionSelection) + return true; + + return false; + +} + +static CachedImage* getCachedImage(Element* element) +{ + ASSERT(element); + RenderObject* renderer = element->renderer(); + if (!renderer || !renderer->isImage()) + return 0; + RenderImage* image = static_cast<RenderImage*>(renderer); + return image->cachedImage(); +} + +static Image* getImage(Element* element) +{ + ASSERT(element); + RenderObject* renderer = element->renderer(); + if (!renderer || !renderer->isImage()) + return 0; + + RenderImage* image = static_cast<RenderImage*>(renderer); + if (image->cachedImage() && !image->cachedImage()->errorOccurred()) + return image->cachedImage()->image(); + return 0; +} + +static void prepareClipboardForImageDrag(Frame* src, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label) +{ + RefPtr<Range> range = src->document()->createRange(); + ExceptionCode ec = 0; + range->selectNode(node, ec); + ASSERT(ec == 0); + src->selectionController()->setSelection(Selection(range.get(), DOWNSTREAM)); + clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, src); +} + +static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage) +{ + // dragImageOffset is the cursor position relative to the lower-left corner of the image. +#if PLATFORM(MAC) + // We add in the Y dimension because we are a flipped view, so adding moves the image down. + const int yOffset = dragImageOffset.y(); +#else + const int yOffset = -dragImageOffset.y(); +#endif + + if (isLinkImage) + return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset); + + return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset); +} + +static IntPoint dragLocForSelectionDrag(Frame* src) +{ + IntRect draggingRect = enclosingIntRect(src->selectionRect()); + int xpos = draggingRect.right(); + xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos; + int ypos = draggingRect.bottom(); +#if PLATFORM(MAC) + // Deal with flipped coordinates on Mac + ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos; +#else + ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos; +#endif + return IntPoint(xpos, ypos); +} + +bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag) +{ + ASSERT(src); + ASSERT(clipboard); + + if (!src->view() || !src->renderer()) + return false; + + HitTestResult dragSource = HitTestResult(dragOrigin); + dragSource = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true); + KURL linkURL = dragSource.absoluteLinkURL(); + KURL imageURL = dragSource.absoluteImageURL(); + bool isSelected = dragSource.isSelected(); + + IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.pos()); + + m_draggingImageURL = KURL(); + m_dragOperation = srcOp; + + DragImageRef dragImage = 0; + IntPoint dragLoc(0, 0); + IntPoint dragImageOffset(0, 0); + + if (isDHTMLDrag) + dragImage = clipboard->createDragImage(dragImageOffset); + + // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. + // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. + if (dragImage) { + dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty()); + m_dragOffset = dragImageOffset; + } + + bool startedDrag = true; // optimism - we almost always manage to start the drag + + Node* node = dragSource.innerNonSharedNode(); + + if (!imageURL.isEmpty() && node && node->isElementNode() + && getImage(static_cast<Element*>(node)) + && (m_dragSourceAction & DragSourceActionImage)) { + Element* element = static_cast<Element*>(node); + if (!clipboard->hasData()) { + m_draggingImageURL = imageURL; + prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, dragSource.altDisplayString()); + } + + m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard); + + if (!dragImage) { + IntRect imageRect = dragSource.imageRect(); + imageRect.setLocation(m_page->mainFrame()->view()->windowToContents(src->view()->contentsToWindow(imageRect.location()))); + doImageDrag(element, dragOrigin, dragSource.imageRect(), clipboard, src, m_dragOffset); + } else + // DHTML defined drag image + doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); + + } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) { + if (!clipboard->hasData()) + // Simplify whitespace so the title put on the clipboard resembles what the user sees + // on the web page. This includes replacing newlines with spaces. + clipboard->writeURL(linkURL, dragSource.textContent().simplifyWhiteSpace(), src); + + m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard); + if (!dragImage) { + dragImage = m_client->createDragImageForLink(linkURL, dragSource.textContent(), src); + IntSize size = dragImageSize(dragImage); + m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset); + dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y()); + } + doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true); + } else if (isSelected && (m_dragSourceAction & DragSourceActionSelection)) { + RefPtr<Range> selectionRange = src->selectionController()->toRange(); + ASSERT(selectionRange); + if (!clipboard->hasData()) + clipboard->writeRange(selectionRange.get(), src); + m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard); + if (!dragImage) { + dragImage = createDragImageForSelection(src); + dragLoc = dragLocForSelectionDrag(src); + m_dragOffset = IntPoint((int)(dragOrigin.x() - dragLoc.x()), (int)(dragOrigin.y() - dragLoc.y())); + } + doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); + } else if (isDHTMLDrag) { + ASSERT(m_dragSourceAction & DragSourceActionDHTML); + m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard); + doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); + } else { + // Only way I know to get here is if to get here is if the original element clicked on in the mousedown is no longer + // under the mousedown point, so linkURL, imageURL and isSelected are all false/empty. + startedDrag = false; + } + + if (dragImage) + deleteDragImage(dragImage); + return startedDrag; +} + +void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset) +{ + IntPoint mouseDownPoint = dragOrigin; + DragImageRef dragImage; + IntPoint origin; + + Image* image = getImage(element); + if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea + && (dragImage = createDragImageFromImage(image))) { + IntSize originalSize = rect.size(); + origin = rect.location(); + + dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize()); + dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha); + IntSize newSize = dragImageSize(dragImage); + + // Properly orient the drag image and orient it differently if it's smaller than the original + float scale = newSize.width() / (float)originalSize.width(); + float dx = origin.x() - mouseDownPoint.x(); + dx *= scale; + origin.setX((int)(dx + 0.5)); +#if PLATFORM(MAC) + //Compensate for accursed flipped coordinates in cocoa + origin.setY(origin.y() + originalSize.height()); +#endif + float dy = origin.y() - mouseDownPoint.y(); + dy *= scale; + origin.setY((int)(dy + 0.5)); + } else { + dragImage = createDragImageIconForCachedImage(getCachedImage(element)); + if (dragImage) + origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset); + } + + dragImageOffset.setX(mouseDownPoint.x() + origin.x()); + dragImageOffset.setY(mouseDownPoint.y() + origin.y()); + doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false); + + deleteDragImage(dragImage); +} + +void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink) +{ + m_didInitiateDrag = true; + m_dragInitiator = frame->document(); + // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame + RefPtr<Frame> frameProtector = m_page->mainFrame(); + RefPtr<FrameView> viewProtector = frameProtector->view(); + m_client->startDrag(image, viewProtector->windowToContents(frame->view()->contentsToWindow(dragLoc)), + viewProtector->windowToContents(frame->view()->contentsToWindow(eventPos)), clipboard, frameProtector.get(), forLink); + + // Drag has ended, dragEnded *should* have been called, however it is possible + // for the UIDelegate to take over the drag, and fail to send the appropriate + // drag termination event. As dragEnded just resets drag variables, we just + // call it anyway to be on the safe side + dragEnded(); +} + +// Manual drag caret manipulation +void DragController::placeDragCaret(const IntPoint& windowPoint) +{ + Frame* mainFrame = m_page->mainFrame(); + Document* newDraggingDoc = mainFrame->documentAtPoint(windowPoint); + if (m_document != newDraggingDoc) { + if (m_document) + cancelDrag(); + m_document = newDraggingDoc; + } + if (!m_document) + return; + Frame* frame = m_document->frame(); + ASSERT(frame); + FrameView* frameView = frame->view(); + if (!frameView) + return; + IntPoint framePoint = frameView->windowToContents(windowPoint); + Selection dragCaret(frame->visiblePositionForPoint(framePoint)); + m_page->dragCaretController()->setSelection(dragCaret); +} + +} diff --git a/WebCore/page/DragController.h b/WebCore/page/DragController.h new file mode 100644 index 0000000..efa8292 --- /dev/null +++ b/WebCore/page/DragController.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DragController_h +#define DragController_h + +#include "DragActions.h" +#include "DragImage.h" +#include "IntPoint.h" +#include "IntRect.h" +#include "KURL.h" + +namespace WebCore { + + class Clipboard; + class Document; + class DragClient; + class DragData; + class Element; + class Frame; + class Image; + class Node; + class Page; + class PlatformMouseEvent; + class Range; + class SelectionController; + + class DragController { + public: + DragController(Page*, DragClient*); + ~DragController(); + DragClient* client() const { return m_client; } + + DragOperation dragEntered(DragData*); + void dragExited(DragData*); + DragOperation dragUpdated(DragData*); + bool performDrag(DragData*); + + //FIXME: It should be possible to remove a number of these accessors once all + //drag logic is in WebCore + void setDidInitiateDrag(bool initiated) { m_didInitiateDrag = initiated; } + bool didInitiateDrag() const { return m_didInitiateDrag; } + void setIsHandlingDrag(bool handling) { m_isHandlingDrag = handling; } + bool isHandlingDrag() const { return m_isHandlingDrag; } + void setDragOperation(DragOperation dragOp) { m_dragOperation = dragOp; } + DragOperation dragOperation() const { return m_dragOperation; } + void setDraggingImageURL(const KURL& url) { m_draggingImageURL = url; } + const KURL& draggingImageURL() const { return m_draggingImageURL; } + void setDragInitiator(Document* initiator) { m_dragInitiator = initiator; m_didInitiateDrag = true; } + Document* dragInitiator() const { return m_dragInitiator; } + void setDragOffset(const IntPoint& offset) { m_dragOffset = offset; } + const IntPoint& dragOffset() const { return m_dragOffset; } + DragSourceAction dragSourceAction() const { return m_dragSourceAction; } + + Document* document() const { return m_document; } + DragDestinationAction dragDestinationAction() const { return m_dragDestinationAction; } + DragSourceAction delegateDragSourceAction(const IntPoint& pagePoint); + + bool mayStartDragAtEventLocation(const Frame*, const IntPoint& framePos); + void dragEnded(); + + void placeDragCaret(const IntPoint&); + + bool startDrag(Frame* src, Clipboard*, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag); + static const IntSize& maxDragImageSize(); + + static const int LinkDragBorderInset; + static const int MaxOriginalImageArea; + static const int DragIconRightInset; + static const int DragIconBottomInset; + static const float DragImageAlpha; + private: + bool canProcessDrag(DragData*); + bool concludeDrag(DragData*, DragDestinationAction); + DragOperation dragEnteredOrUpdated(DragData*); + DragOperation operationForLoad(DragData*); + DragOperation tryDocumentDrag(DragData*, DragDestinationAction); + DragOperation tryDHTMLDrag(DragData*); + DragOperation dragOperation(DragData*); + void cancelDrag(); + bool dragIsMove(SelectionController*, DragData*); + bool isCopyKeyDown(); + + IntRect selectionDraggingRect(Frame*); + bool doDrag(Frame* src, Clipboard* clipboard, DragImageRef dragImage, const KURL& linkURL, const KURL& imageURL, Node* node, IntPoint& dragLoc, IntPoint& dragImageOffset); + void doImageDrag(Element*, const IntPoint&, const IntRect&, Clipboard*, Frame*, IntPoint&); + void doSystemDrag(DragImageRef, const IntPoint&, const IntPoint&, Clipboard*, Frame*, bool forLink); + Page* m_page; + DragClient* m_client; + + //The Document the mouse was last dragged over + Document* m_document; + + //The Document (if any) that initiated the drag + Document* m_dragInitiator; + + DragDestinationAction m_dragDestinationAction; + DragSourceAction m_dragSourceAction; + bool m_didInitiateDrag; + bool m_isHandlingDrag; + DragOperation m_dragOperation; + IntPoint m_dragOffset; + KURL m_draggingImageURL; + + }; + +} + +#endif diff --git a/WebCore/page/EditorClient.h b/WebCore/page/EditorClient.h new file mode 100644 index 0000000..802aa4f --- /dev/null +++ b/WebCore/page/EditorClient.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Trolltech ASA + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EditorClient_h +#define EditorClient_h + +#include "EditorInsertAction.h" +#include "PlatformString.h" +#include "TextAffinity.h" +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +#if PLATFORM(MAC) +class NSArray; +class NSData; +class NSString; +class NSURL; +#endif + +namespace WebCore { + +class CSSStyleDeclaration; +class EditCommand; +class Element; +class Frame; +class HTMLElement; +class KeyboardEvent; +class Node; +class Range; +class Selection; +class String; +class VisiblePosition; + +struct GrammarDetail { + int location; + int length; + Vector<String> guesses; + String userDescription; +}; + +class EditorClient { +public: + virtual ~EditorClient() { } + virtual void pageDestroyed() = 0; + + virtual bool shouldDeleteRange(Range*) = 0; + virtual bool shouldShowDeleteInterface(HTMLElement*) = 0; + virtual bool smartInsertDeleteEnabled() = 0; + virtual bool isContinuousSpellCheckingEnabled() = 0; + virtual void toggleContinuousSpellChecking() = 0; + virtual bool isGrammarCheckingEnabled() = 0; + virtual void toggleGrammarChecking() = 0; + virtual int spellCheckerDocumentTag() = 0; + + virtual bool isEditable() = 0; + + virtual bool shouldBeginEditing(Range*) = 0; + virtual bool shouldEndEditing(Range*) = 0; + virtual bool shouldInsertNode(Node*, Range*, EditorInsertAction) = 0; + virtual bool shouldInsertText(String, Range*, EditorInsertAction) = 0; + virtual bool shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity, bool stillSelecting) = 0; + + virtual bool shouldApplyStyle(CSSStyleDeclaration*, Range*) = 0; +// virtual bool shouldChangeTypingStyle(CSSStyleDeclaration* fromStyle, CSSStyleDeclaration* toStyle) = 0; +// virtual bool doCommandBySelector(SEL selector) = 0; + virtual bool shouldMoveRangeAfterDelete(Range*, Range*) = 0; + + virtual void didBeginEditing() = 0; + virtual void respondToChangedContents() = 0; + virtual void respondToChangedSelection() = 0; + virtual void didEndEditing() = 0; + virtual void didWriteSelectionToPasteboard() = 0; + virtual void didSetSelectionTypesForPasteboard() = 0; +// virtual void didChangeTypingStyle:(NSNotification *)notification = 0; +// virtual void didChangeSelection:(NSNotification *)notification = 0; +// virtual NSUndoManager* undoManager:(WebView *)webView = 0; + + virtual void registerCommandForUndo(PassRefPtr<EditCommand>) = 0; + virtual void registerCommandForRedo(PassRefPtr<EditCommand>) = 0; + virtual void clearUndoRedoOperations() = 0; + + virtual bool canUndo() const = 0; + virtual bool canRedo() const = 0; + + virtual void undo() = 0; + virtual void redo() = 0; + + virtual void handleKeyboardEvent(KeyboardEvent*) = 0; + virtual void handleInputMethodKeydown(KeyboardEvent*) = 0; + + virtual void textFieldDidBeginEditing(Element*) = 0; + virtual void textFieldDidEndEditing(Element*) = 0; + virtual void textDidChangeInTextField(Element*) = 0; + virtual bool doTextFieldCommandFromEvent(Element*, KeyboardEvent*) = 0; + virtual void textWillBeDeletedInTextField(Element*) = 0; + virtual void textDidChangeInTextArea(Element*) = 0; + +#if PLATFORM(MAC) + // FIXME: This should become SelectionController::toWebArchive() + virtual NSData* dataForArchivedSelection(Frame*) = 0; + + virtual NSString* userVisibleString(NSURL*) = 0; +#ifdef BUILDING_ON_TIGER + virtual NSArray* pasteboardTypesForSelection(Frame*) = 0; +#endif +#endif + + virtual void ignoreWordInSpellDocument(const String&) = 0; + virtual void learnWord(const String&) = 0; + virtual void checkSpellingOfString(const UChar*, int length, int* misspellingLocation, int* misspellingLength) = 0; + virtual void checkGrammarOfString(const UChar*, int length, Vector<GrammarDetail>&, int* badGrammarLocation, int* badGrammarLength) = 0; + virtual void updateSpellingUIWithGrammarString(const String&, const GrammarDetail& detail) = 0; + virtual void updateSpellingUIWithMisspelledWord(const String&) = 0; + virtual void showSpellingUI(bool show) = 0; + virtual bool spellingUIIsShowing() = 0; + virtual void getGuessesForWord(const String&, Vector<String>& guesses) = 0; + virtual void setInputMethodState(bool enabled) = 0; +}; + +} + +#endif // EditorClient_h diff --git a/WebCore/page/EventHandler.cpp b/WebCore/page/EventHandler.cpp new file mode 100644 index 0000000..adb4086 --- /dev/null +++ b/WebCore/page/EventHandler.cpp @@ -0,0 +1,1866 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventHandler.h" + +#include "CachedImage.h" +#include "ChromeClient.h" +#include "Cursor.h" +#include "Document.h" +#include "DragController.h" +#include "Editor.h" +#include "EventNames.h" +#include "FloatPoint.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "HTMLFrameSetElement.h" +#include "HTMLFrameElementBase.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "Image.h" +#include "KeyboardEvent.h" +#include "MouseEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformScrollBar.h" +#include "PlatformWheelEvent.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include "Settings.h" +#include "TextEvent.h" + +#if ENABLE(SVG) +#include "SVGDocument.h" +#include "SVGNames.h" +#endif + +namespace WebCore { + +using namespace EventNames; +using namespace HTMLNames; + +// The link drag hysteresis is much larger than the others because there +// needs to be enough space to cancel the link press without starting a link drag, +// and because dragging links is rare. +const int LinkDragHysteresis = 40; +const int ImageDragHysteresis = 5; +const int TextDragHysteresis = 3; +const int GeneralDragHysteresis = 3; +const double TextDragDelay = 0.15; + +// Match key code of composition keydown event on windows. +// IE sends VK_PROCESSKEY which has value 229; +const int CompositionEventKeyCode = 229; + +#if ENABLE(SVG) +using namespace SVGNames; +#endif + +const double autoscrollInterval = 0.1; + +static Frame* subframeForTargetNode(Node* node); + +EventHandler::EventHandler(Frame* frame) + : m_frame(frame) + , m_mousePressed(false) + , m_mouseDownMayStartSelect(false) + , m_mouseDownMayStartDrag(false) + , m_mouseDownWasSingleClickInSelection(false) + , m_beganSelectingText(false) + , m_hoverTimer(this, &EventHandler::hoverTimerFired) + , m_autoscrollTimer(this, &EventHandler::autoscrollTimerFired) + , m_autoscrollRenderer(0) + , m_mouseDownMayStartAutoscroll(false) + , m_mouseDownWasInSubframe(false) +#if ENABLE(SVG) + , m_svgPan(false) +#endif + , m_resizeLayer(0) + , m_capturingMouseEventsNode(0) + , m_clickCount(0) + , m_mouseDownTimestamp(0) +#if PLATFORM(MAC) + , m_mouseDownView(nil) + , m_sendingEventToSubview(false) + , m_activationEventNumber(0) +#endif +{ +} + +EventHandler::~EventHandler() +{ +} + +EventHandler::EventHandlerDragState& EventHandler::dragState() +{ + static EventHandlerDragState state; + return state; +} + +void EventHandler::clear() +{ + m_hoverTimer.stop(); + m_resizeLayer = 0; + m_nodeUnderMouse = 0; + m_lastNodeUnderMouse = 0; + m_lastMouseMoveEventSubframe = 0; + m_lastScrollbarUnderMouse = 0; + m_clickCount = 0; + m_clickNode = 0; + m_frameSetBeingResized = 0; + m_dragTarget = 0; + m_currentMousePosition = IntPoint(); + m_mousePressNode = 0; + m_mousePressed = false; + m_capturingMouseEventsNode = 0; +} + +void EventHandler::selectClosestWordFromMouseEvent(const MouseEventWithHitTestResults& result) +{ + Node* innerNode = result.targetNode(); + Selection newSelection; + + if (innerNode && innerNode->renderer() && m_mouseDownMayStartSelect) { + VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint())); + if (pos.isNotNull()) { + newSelection = Selection(pos); + newSelection.expandUsingGranularity(WordGranularity); + } + + if (newSelection.isRange()) { + m_frame->setSelectionGranularity(WordGranularity); + m_beganSelectingText = true; + } + + if (m_frame->shouldChangeSelection(newSelection)) + m_frame->selectionController()->setSelection(newSelection); + } +} + +void EventHandler::selectClosestWordOrLinkFromMouseEvent(const MouseEventWithHitTestResults& result) +{ + if (!result.hitTestResult().isLiveLink()) + return selectClosestWordFromMouseEvent(result); + + Node* innerNode = result.targetNode(); + + if (innerNode && innerNode->renderer() && m_mouseDownMayStartSelect) { + Selection newSelection; + Element* URLElement = result.hitTestResult().URLElement(); + VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint())); + if (pos.isNotNull() && pos.deepEquivalent().node()->isDescendantOf(URLElement)) + newSelection = Selection::selectionFromContentsOfNode(URLElement); + + if (newSelection.isRange()) { + m_frame->setSelectionGranularity(WordGranularity); + m_beganSelectingText = true; + } + + if (m_frame->shouldChangeSelection(newSelection)) + m_frame->selectionController()->setSelection(newSelection); + } +} + +bool EventHandler::handleMousePressEventDoubleClick(const MouseEventWithHitTestResults& event) +{ + if (event.event().button() != LeftButton) + return false; + + if (m_frame->selectionController()->isRange()) + // A double-click when range is already selected + // should not change the selection. So, do not call + // selectClosestWordFromMouseEvent, but do set + // m_beganSelectingText to prevent handleMouseReleaseEvent + // from setting caret selection. + m_beganSelectingText = true; + else + selectClosestWordFromMouseEvent(event); + + return true; +} + +bool EventHandler::handleMousePressEventTripleClick(const MouseEventWithHitTestResults& event) +{ + if (event.event().button() != LeftButton) + return false; + + Node* innerNode = event.targetNode(); + if (!(innerNode && innerNode->renderer() && m_mouseDownMayStartSelect)) + return false; + + Selection newSelection; + VisiblePosition pos(innerNode->renderer()->positionForPoint(event.localPoint())); + if (pos.isNotNull()) { + newSelection = Selection(pos); + newSelection.expandUsingGranularity(ParagraphGranularity); + } + if (newSelection.isRange()) { + m_frame->setSelectionGranularity(ParagraphGranularity); + m_beganSelectingText = true; + } + + if (m_frame->shouldChangeSelection(newSelection)) + m_frame->selectionController()->setSelection(newSelection); + + return true; +} + +bool EventHandler::handleMousePressEventSingleClick(const MouseEventWithHitTestResults& event) +{ + if (event.event().button() != LeftButton) + return false; + + Node* innerNode = event.targetNode(); + if (!(innerNode && innerNode->renderer() && m_mouseDownMayStartSelect)) + return false; + + // Extend the selection if the Shift key is down, unless the click is in a link. + bool extendSelection = event.event().shiftKey() && !event.isOverLink(); + + // Don't restart the selection when the mouse is pressed on an + // existing selection so we can allow for text dragging. + IntPoint vPoint = m_frame->view()->windowToContents(event.event().pos()); + if (!extendSelection && m_frame->selectionController()->contains(vPoint)) { + m_mouseDownWasSingleClickInSelection = true; + return false; + } + + VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(event.localPoint())); + if (visiblePos.isNull()) + visiblePos = VisiblePosition(innerNode, 0, DOWNSTREAM); + Position pos = visiblePos.deepEquivalent(); + + Selection newSelection = m_frame->selectionController()->selection(); + if (extendSelection && newSelection.isCaretOrRange()) { + m_frame->selectionController()->setLastChangeWasHorizontalExtension(false); + + // See <rdar://problem/3668157> REGRESSION (Mail): shift-click deselects when selection + // was created right-to-left + Position start = newSelection.start(); + Position end = newSelection.end(); + short before = Range::compareBoundaryPoints(pos.node(), pos.offset(), start.node(), start.offset()); + if (before <= 0) + newSelection = Selection(pos, end); + else + newSelection = Selection(start, pos); + + if (m_frame->selectionGranularity() != CharacterGranularity) + newSelection.expandUsingGranularity(m_frame->selectionGranularity()); + m_beganSelectingText = true; + } else { + newSelection = Selection(visiblePos); + m_frame->setSelectionGranularity(CharacterGranularity); + } + + if (m_frame->shouldChangeSelection(newSelection)) + m_frame->selectionController()->setSelection(newSelection); + + return true; +} + +bool EventHandler::handleMousePressEvent(const MouseEventWithHitTestResults& event) +{ + // Reset drag state. + dragState().m_dragSrc = 0; + + bool singleClick = event.event().clickCount() <= 1; + + // If we got the event back, that must mean it wasn't prevented, + // so it's allowed to start a drag or selection. + m_mouseDownMayStartSelect = canMouseDownStartSelect(event.targetNode()); + + // Careful that the drag starting logic stays in sync with eventMayStartDrag() + m_mouseDownMayStartDrag = singleClick; + + m_mouseDownWasSingleClickInSelection = false; + + if (passWidgetMouseDownEventToWidget(event)) + return true; + +#if ENABLE(SVG) + if (m_frame->document()->isSVGDocument() && + static_cast<SVGDocument*>(m_frame->document())->zoomAndPanEnabled()) { + if (event.event().shiftKey() && singleClick) { + m_svgPan = true; + static_cast<SVGDocument*>(m_frame->document())->startPan(event.event().pos()); + return true; + } + } +#endif + + // We don't do this at the start of mouse down handling, + // because we don't want to do it until we know we didn't hit a widget. + if (singleClick) + focusDocumentView(); + + Node* innerNode = event.targetNode(); + + m_mousePressNode = innerNode; + m_dragStartPos = event.event().pos(); + + bool swallowEvent = false; + if (event.event().button() == LeftButton || event.event().button() == MiddleButton) { + m_frame->selectionController()->setCaretBlinkingSuspended(true); + m_mousePressed = true; + m_beganSelectingText = false; + + if (event.event().clickCount() == 2) + swallowEvent = handleMousePressEventDoubleClick(event); + else if (event.event().clickCount() >= 3) + swallowEvent = handleMousePressEventTripleClick(event); + else + swallowEvent = handleMousePressEventSingleClick(event); + } + + m_mouseDownMayStartAutoscroll = m_mouseDownMayStartSelect || + (m_mousePressNode && m_mousePressNode->renderer() && m_mousePressNode->renderer()->shouldAutoscroll()); + + return swallowEvent; +} + +bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& event) +{ + if (handleDrag(event)) + return true; + + if (!m_mousePressed) + return false; + + Node* targetNode = event.targetNode(); + if (event.event().button() != LeftButton || !targetNode || !targetNode->renderer()) + return false; + +#if PLATFORM(MAC) // FIXME: Why does this assertion fire on other platforms? + ASSERT(m_mouseDownMayStartSelect || m_mouseDownMayStartAutoscroll); +#endif + + m_mouseDownMayStartDrag = false; + + if (m_mouseDownMayStartAutoscroll) { + // If the selection is contained in a layer that can scroll, that layer should handle the autoscroll + // Otherwise, let the bridge handle it so the view can scroll itself. + RenderObject* renderer = targetNode->renderer(); + while (renderer && !renderer->shouldAutoscroll()) + renderer = renderer->parent(); + if (renderer) + handleAutoscroll(renderer); + } + + updateSelectionForMouseDrag(targetNode, event.localPoint()); + return true; +} + +bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const +{ + // This is a pre-flight check of whether the event might lead to a drag being started. Be careful + // that its logic needs to stay in sync with handleMouseMoveEvent() and the way we setMouseDownMayStartDrag + // in handleMousePressEvent + + if (!m_frame->renderer() || !m_frame->renderer()->hasLayer() + || event.button() != LeftButton || event.clickCount() != 1) + return false; + + bool DHTMLFlag; + bool UAFlag; + allowDHTMLDrag(DHTMLFlag, UAFlag); + if (!DHTMLFlag && !UAFlag) + return false; + + HitTestRequest request(true, false); + HitTestResult result(m_frame->view()->windowToContents(event.pos())); + m_frame->renderer()->layer()->hitTest(request, result); + bool srcIsDHTML; + return result.innerNode() && result.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, result.point().x(), result.point().y(), srcIsDHTML); +} + +void EventHandler::updateSelectionForMouseDrag() +{ + FrameView* view = m_frame->view(); + if (!view) + return; + RenderObject* renderer = m_frame->renderer(); + if (!renderer) + return; + RenderLayer* layer = renderer->layer(); + if (!layer) + return; + + HitTestResult result(view->windowToContents(m_currentMousePosition)); + layer->hitTest(HitTestRequest(true, true, true), result); + updateSelectionForMouseDrag(result.innerNode(), result.localPoint()); +} + +void EventHandler::updateSelectionForMouseDrag(Node* targetNode, const IntPoint& localPoint) +{ + if (!m_mouseDownMayStartSelect) + return; + + if (!targetNode) + return; + + RenderObject* targetRenderer = targetNode->renderer(); + if (!targetRenderer) + return; + + if (!canMouseDragExtendSelect(targetNode)) + return; + + VisiblePosition targetPosition(targetRenderer->positionForPoint(localPoint)); + + // Don't modify the selection if we're not on a node. + if (targetPosition.isNull()) + return; + + // Restart the selection if this is the first mouse move. This work is usually + // done in handleMousePressEvent, but not if the mouse press was on an existing selection. + Selection newSelection = m_frame->selectionController()->selection(); + +#if ENABLE(SVG) + // Special case to limit selection to the containing block for SVG text. + // FIXME: Isn't there a better non-SVG-specific way to do this? + if (Node* selectionBaseNode = newSelection.base().node()) + if (RenderObject* selectionBaseRenderer = selectionBaseNode->renderer()) + if (selectionBaseRenderer->isSVGText()) + if (targetNode->renderer()->containingBlock() != selectionBaseRenderer->containingBlock()) + return; +#endif + + if (!m_beganSelectingText) { + m_beganSelectingText = true; + newSelection = Selection(targetPosition); + } + + newSelection.setExtent(targetPosition); + if (m_frame->selectionGranularity() != CharacterGranularity) + newSelection.expandUsingGranularity(m_frame->selectionGranularity()); + + if (m_frame->shouldChangeSelection(newSelection)) { + m_frame->selectionController()->setLastChangeWasHorizontalExtension(false); + m_frame->selectionController()->setSelection(newSelection); + } +} + +bool EventHandler::handleMouseUp(const MouseEventWithHitTestResults& event) +{ + if (eventLoopHandleMouseUp(event)) + return true; + + // If this was the first click in the window, we don't even want to clear the selection. + // This case occurs when the user clicks on a draggable element, since we have to process + // the mouse down and drag events to see if we might start a drag. For other first clicks + // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets + // ignored upstream of this layer. + return eventActivatedView(event.event()); +} + +bool EventHandler::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event) +{ + stopAutoscrollTimer(); + + if (handleMouseUp(event)) + return true; + + // Used to prevent mouseMoveEvent from initiating a drag before + // the mouse is pressed again. + m_frame->selectionController()->setCaretBlinkingSuspended(false); + m_mousePressed = false; + m_mouseDownMayStartDrag = false; + m_mouseDownMayStartSelect = false; + m_mouseDownMayStartAutoscroll = false; + m_mouseDownWasInSubframe = false; + + bool handled = false; + + // Clear the selection if the mouse didn't move after the last mouse press. + // We do this so when clicking on the selection, the selection goes away. + // However, if we are editing, place the caret. + if (m_mouseDownWasSingleClickInSelection && !m_beganSelectingText + && m_dragStartPos == event.event().pos() + && m_frame->selectionController()->isRange()) { + Selection newSelection; + Node *node = event.targetNode(); + if (node && node->isContentEditable() && node->renderer()) { + VisiblePosition pos = node->renderer()->positionForPoint(event.localPoint()); + newSelection = Selection(pos); + } + if (m_frame->shouldChangeSelection(newSelection)) + m_frame->selectionController()->setSelection(newSelection); + + handled = true; + } + + m_frame->notifyRendererOfSelectionChange(true); + + m_frame->selectionController()->selectFrameElementInParentIfFullySelected(); + + return handled; +} + +void EventHandler::handleAutoscroll(RenderObject* renderer) +{ + if (m_autoscrollTimer.isActive()) + return; + setAutoscrollRenderer(renderer); + startAutoscrollTimer(); +} + +void EventHandler::autoscrollTimerFired(Timer<EventHandler>*) +{ + if (!m_mousePressed) { + stopAutoscrollTimer(); + return; + } + if (RenderObject* r = autoscrollRenderer()) + r->autoscroll(); +} + +RenderObject* EventHandler::autoscrollRenderer() const +{ + return m_autoscrollRenderer; +} + +void EventHandler::setAutoscrollRenderer(RenderObject* renderer) +{ + m_autoscrollRenderer = renderer; +} + +void EventHandler::allowDHTMLDrag(bool& flagDHTML, bool& flagUA) const +{ + if (!m_frame || !m_frame->document()) { + flagDHTML = false; + flagUA = false; + } + + unsigned mask = m_frame->page()->dragController()->delegateDragSourceAction(m_frame->view()->contentsToWindow(m_mouseDownPos)); + flagDHTML = (mask & DragSourceActionDHTML) != DragSourceActionNone; + flagUA = ((mask & DragSourceActionImage) || (mask & DragSourceActionLink) || (mask & DragSourceActionSelection)); +} + +HitTestResult EventHandler::hitTestResultAtPoint(const IntPoint& point, bool allowShadowContent) +{ + HitTestResult result(point); + if (!m_frame->renderer()) + return result; + m_frame->renderer()->layer()->hitTest(HitTestRequest(true, true), result); + + while (true) { + Node* n = result.innerNode(); + if (!n || !n->renderer() || !n->renderer()->isWidget()) + break; + Widget* widget = static_cast<RenderWidget*>(n->renderer())->widget(); + if (!widget || !widget->isFrameView()) + break; + Frame* frame = static_cast<HTMLFrameElementBase*>(n)->contentFrame(); + if (!frame || !frame->renderer()) + break; + FrameView* view = static_cast<FrameView*>(widget); + IntPoint widgetPoint(result.localPoint().x() + view->contentsX() - n->renderer()->borderLeft() - n->renderer()->paddingLeft(), + result.localPoint().y() + view->contentsY() - n->renderer()->borderTop() - n->renderer()->paddingTop()); + HitTestResult widgetHitTestResult(widgetPoint); + frame->renderer()->layer()->hitTest(HitTestRequest(true, true), widgetHitTestResult); + result = widgetHitTestResult; + } + + if (!allowShadowContent) + result.setToNonShadowAncestor(); + + return result; +} + + +void EventHandler::startAutoscrollTimer() +{ + m_autoscrollTimer.startRepeating(autoscrollInterval); +} + +void EventHandler::stopAutoscrollTimer(bool rendererIsBeingDestroyed) +{ + if (m_mouseDownWasInSubframe) { + if (Frame* subframe = subframeForTargetNode(m_mousePressNode.get())) + subframe->eventHandler()->stopAutoscrollTimer(rendererIsBeingDestroyed); + return; + } + + if (!rendererIsBeingDestroyed && autoscrollRenderer()) + autoscrollRenderer()->stopAutoscroll(); + setAutoscrollRenderer(0); + m_autoscrollTimer.stop(); +} + +Node* EventHandler::mousePressNode() const +{ + return m_mousePressNode.get(); +} + +void EventHandler::setMousePressNode(PassRefPtr<Node> node) +{ + m_mousePressNode = node; +} + +bool EventHandler::scrollOverflow(ScrollDirection direction, ScrollGranularity granularity) +{ + if (!m_frame->document()) + return false; + + Node* node = m_frame->document()->focusedNode(); + if (!node) + node = m_mousePressNode.get(); + + if (node) { + RenderObject *r = node->renderer(); + if (r && !r->isListBox()) + return r->scroll(direction, granularity); + } + + return false; +} + +IntPoint EventHandler::currentMousePosition() const +{ + return m_currentMousePosition; +} + +Frame* subframeForTargetNode(Node* node) +{ + if (!node) + return 0; + + RenderObject* renderer = node->renderer(); + if (!renderer || !renderer->isWidget()) + return 0; + + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (!widget || !widget->isFrameView()) + return 0; + + return static_cast<FrameView*>(widget)->frame(); +} + +static bool isSubmitImage(Node* node) +{ + return node && node->hasTagName(inputTag) + && static_cast<HTMLInputElement*>(node)->inputType() == HTMLInputElement::IMAGE; +} + +// Returns true if the node's editable block is not current focused for editing +static bool nodeIsNotBeingEdited(Node* node, Frame* frame) +{ + return frame->selectionController()->rootEditableElement() != node->rootEditableElement(); +} + +Cursor EventHandler::selectCursor(const MouseEventWithHitTestResults& event, PlatformScrollbar* scrollbar) +{ + // During selection, use an I-beam no matter what we're over. + // If you're capturing mouse events for a particular node, don't treat this as a selection. + if (m_mousePressed && m_mouseDownMayStartSelect && m_frame->selectionController()->isCaretOrRange() && !m_capturingMouseEventsNode) + return iBeamCursor(); + + Node* node = event.targetNode(); + RenderObject* renderer = node ? node->renderer() : 0; + RenderStyle* style = renderer ? renderer->style() : 0; + + if (style && style->cursors()) { + const CursorList* cursors = style->cursors(); + for (unsigned i = 0; i < cursors->size(); ++i) { + CachedImage* cimage = (*cursors)[i].cursorImage; + IntPoint hotSpot = (*cursors)[i].hotSpot; + if (!cimage) + continue; + if (cimage->image()->isNull()) + break; + if (!cimage->errorOccurred()) + return Cursor(cimage->image(), hotSpot); + } + } + + switch (style ? style->cursor() : CURSOR_AUTO) { + case CURSOR_AUTO: { + bool editable = (node && node->isContentEditable()); + bool editableLinkEnabled = false; + + // If the link is editable, then we need to check the settings to see whether or not the link should be followed + if (editable) { + ASSERT(m_frame->settings()); + switch(m_frame->settings()->editableLinkBehavior()) { + default: + case EditableLinkDefaultBehavior: + case EditableLinkAlwaysLive: + editableLinkEnabled = true; + break; + + case EditableLinkNeverLive: + editableLinkEnabled = false; + break; + + case EditableLinkLiveWhenNotFocused: + editableLinkEnabled = nodeIsNotBeingEdited(node, m_frame) || event.event().shiftKey(); + break; + + case EditableLinkOnlyLiveWithShiftKey: + editableLinkEnabled = event.event().shiftKey(); + break; + } + } + + if ((event.isOverLink() || isSubmitImage(node)) && (!editable || editableLinkEnabled)) + return handCursor(); + RenderLayer* layer = renderer ? renderer->enclosingLayer() : 0; + bool inResizer = false; + if (m_frame->view() && layer && layer->isPointInResizeControl(m_frame->view()->windowToContents(event.event().pos()))) + inResizer = true; + if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !inResizer && !scrollbar) + return iBeamCursor(); + return pointerCursor(); + } + case CURSOR_CROSS: + return crossCursor(); + case CURSOR_POINTER: + return handCursor(); + case CURSOR_MOVE: + return moveCursor(); + case CURSOR_ALL_SCROLL: + return moveCursor(); + case CURSOR_E_RESIZE: + return eastResizeCursor(); + case CURSOR_W_RESIZE: + return westResizeCursor(); + case CURSOR_N_RESIZE: + return northResizeCursor(); + case CURSOR_S_RESIZE: + return southResizeCursor(); + case CURSOR_NE_RESIZE: + return northEastResizeCursor(); + case CURSOR_SW_RESIZE: + return southWestResizeCursor(); + case CURSOR_NW_RESIZE: + return northWestResizeCursor(); + case CURSOR_SE_RESIZE: + return southEastResizeCursor(); + case CURSOR_NS_RESIZE: + return northSouthResizeCursor(); + case CURSOR_EW_RESIZE: + return eastWestResizeCursor(); + case CURSOR_NESW_RESIZE: + return northEastSouthWestResizeCursor(); + case CURSOR_NWSE_RESIZE: + return northWestSouthEastResizeCursor(); + case CURSOR_COL_RESIZE: + return columnResizeCursor(); + case CURSOR_ROW_RESIZE: + return rowResizeCursor(); + case CURSOR_TEXT: + return iBeamCursor(); + case CURSOR_WAIT: + return waitCursor(); + case CURSOR_HELP: + return helpCursor(); + case CURSOR_VERTICAL_TEXT: + return verticalTextCursor(); + case CURSOR_CELL: + return cellCursor(); + case CURSOR_CONTEXT_MENU: + return contextMenuCursor(); + case CURSOR_PROGRESS: + return progressCursor(); + case CURSOR_NO_DROP: + return noDropCursor(); + case CURSOR_ALIAS: + return aliasCursor(); + case CURSOR_COPY: + return copyCursor(); + case CURSOR_NONE: + return noneCursor(); + case CURSOR_NOT_ALLOWED: + return notAllowedCursor(); + case CURSOR_DEFAULT: + return pointerCursor(); + case CURSOR_WEBKIT_ZOOM_IN: + return zoomInCursor(); + case CURSOR_WEBKIT_ZOOM_OUT: + return zoomOutCursor(); + } + return pointerCursor(); +} + +bool EventHandler::handleMousePressEvent(const PlatformMouseEvent& mouseEvent) +{ + if (!m_frame->document()) + return false; + + RefPtr<FrameView> protector(m_frame->view()); + + m_mousePressed = true; + m_currentMousePosition = mouseEvent.pos(); + m_mouseDownTimestamp = mouseEvent.timestamp(); + m_mouseDownMayStartDrag = false; + m_mouseDownMayStartSelect = false; + m_mouseDownMayStartAutoscroll = false; + m_mouseDownPos = m_frame->view()->windowToContents(mouseEvent.pos()); + m_mouseDownWasInSubframe = false; + + MouseEventWithHitTestResults mev = prepareMouseEvent(HitTestRequest(false, true), mouseEvent); + + if (!mev.targetNode()) { + invalidateClick(); + return false; + } + + m_mousePressNode = mev.targetNode(); + + Frame* subframe = subframeForTargetNode(mev.targetNode()); + if (subframe && passMousePressEventToSubframe(mev, subframe)) { + // Start capturing future events for this frame. We only do this if we didn't clear + // the m_mousePressed flag, which may happen if an AppKit widget entered a modal event loop. + if (m_mousePressed) + m_capturingMouseEventsNode = mev.targetNode(); + invalidateClick(); + return true; + } + + m_clickCount = mouseEvent.clickCount(); + m_clickNode = mev.targetNode(); + + RenderLayer* layer = m_clickNode->renderer() ? m_clickNode->renderer()->enclosingLayer() : 0; + IntPoint p = m_frame->view()->windowToContents(mouseEvent.pos()); + if (layer && layer->isPointInResizeControl(p)) { + layer->setInResizeMode(true); + m_resizeLayer = layer; + m_offsetFromResizeCorner = layer->offsetFromResizeCorner(p); + invalidateClick(); + return true; + } + + bool swallowEvent = dispatchMouseEvent(mousedownEvent, mev.targetNode(), true, m_clickCount, mouseEvent, true); + + // If the hit testing originally determined the event was in a scrollbar, refetch the MouseEventWithHitTestResults + // in case the scrollbar widget was destroyed when the mouse event was handled. + if (mev.scrollbar()) { + const bool wasLastScrollBar = mev.scrollbar() == m_lastScrollbarUnderMouse.get(); + mev = prepareMouseEvent(HitTestRequest(true, true), mouseEvent); + + if (wasLastScrollBar && mev.scrollbar() != m_lastScrollbarUnderMouse.get()) + m_lastScrollbarUnderMouse = 0; + } + + if (swallowEvent) { + // scrollbars should get events anyway, even disabled controls might be scrollable + if (mev.scrollbar()) + passMousePressEventToScrollbar(mev, mev.scrollbar()); + } else { + // Refetch the event target node if it currently is the shadow node inside an <input> element. + // If a mouse event handler changes the input element type to one that has a widget associated, + // we'd like to EventHandler::handleMousePressEvent to pass the event to the widget and thus the + // event target node can't still be the shadow node. + if (mev.targetNode()->isShadowNode() && mev.targetNode()->shadowParentNode()->hasTagName(inputTag)) + mev = prepareMouseEvent(HitTestRequest(true, true), mouseEvent); + + PlatformScrollbar* scrollbar = m_frame->view()->scrollbarUnderMouse(mouseEvent); + if (!scrollbar) + scrollbar = mev.scrollbar(); + if (scrollbar && passMousePressEventToScrollbar(mev, scrollbar)) + swallowEvent = true; + else + swallowEvent = handleMousePressEvent(mev); + } + + return swallowEvent; +} + +// This method only exists for platforms that don't know how to deliver +bool EventHandler::handleMouseDoubleClickEvent(const PlatformMouseEvent& mouseEvent) +{ + if (!m_frame->document()) + return false; + + RefPtr<FrameView> protector(m_frame->view()); + + // We get this instead of a second mouse-up + m_mousePressed = false; + m_currentMousePosition = mouseEvent.pos(); + + MouseEventWithHitTestResults mev = prepareMouseEvent(HitTestRequest(false, true), mouseEvent); + Frame* subframe = subframeForTargetNode(mev.targetNode()); + if (subframe && passMousePressEventToSubframe(mev, subframe)) { + m_capturingMouseEventsNode = 0; + return true; + } + + m_clickCount = mouseEvent.clickCount(); + bool swallowMouseUpEvent = dispatchMouseEvent(mouseupEvent, mev.targetNode(), true, m_clickCount, mouseEvent, false); + + bool swallowClickEvent = false; + // Don't ever dispatch click events for right clicks + if (mouseEvent.button() != RightButton && mev.targetNode() == m_clickNode) + swallowClickEvent = dispatchMouseEvent(clickEvent, mev.targetNode(), true, m_clickCount, mouseEvent, true); + + bool swallowMouseReleaseEvent = false; + if (!swallowMouseUpEvent) + swallowMouseReleaseEvent = handleMouseReleaseEvent(mev); + + invalidateClick(); + + return swallowMouseUpEvent || swallowClickEvent || swallowMouseReleaseEvent; +} + +bool EventHandler::mouseMoved(const PlatformMouseEvent& event) +{ + HitTestResult hoveredNode = HitTestResult(IntPoint()); + bool result = handleMouseMoveEvent(event, &hoveredNode); + + Page* page = m_frame->page(); + if (!page) + return result; + + hoveredNode.setToNonShadowAncestor(); + page->chrome()->mouseDidMoveOverElement(hoveredNode, event.modifierFlags()); + page->chrome()->setToolTip(hoveredNode); + return result; +} + +bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, HitTestResult* hoveredNode) +{ + // in Radar 3703768 we saw frequent crashes apparently due to the + // part being null here, which seems impossible, so check for nil + // but also assert so that we can try to figure this out in debug + // builds, if it happens. + ASSERT(m_frame); + if (!m_frame || !m_frame->document()) + return false; + + RefPtr<FrameView> protector(m_frame->view()); + m_currentMousePosition = mouseEvent.pos(); + + if (m_hoverTimer.isActive()) + m_hoverTimer.stop(); + +#if ENABLE(SVG) + if (m_svgPan) { + static_cast<SVGDocument*>(m_frame->document())->updatePan(m_currentMousePosition); + return true; + } +#endif + + if (m_frameSetBeingResized) + return dispatchMouseEvent(mousemoveEvent, m_frameSetBeingResized.get(), false, 0, mouseEvent, false); + + // Send events right to a scrollbar if the mouse is pressed. + if (m_lastScrollbarUnderMouse && m_mousePressed) + return m_lastScrollbarUnderMouse->handleMouseMoveEvent(mouseEvent); + + // Treat mouse move events while the mouse is pressed as "read-only" in prepareMouseEvent + // if we are allowed to select. + // This means that :hover and :active freeze in the state they were in when the mouse + // was pressed, rather than updating for nodes the mouse moves over as you hold the mouse down. + HitTestRequest request(m_mousePressed && m_mouseDownMayStartSelect, m_mousePressed, true); + MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseEvent); + if (hoveredNode) + *hoveredNode = mev.hitTestResult(); + + PlatformScrollbar* scrollbar = 0; + + if (m_resizeLayer && m_resizeLayer->inResizeMode()) + m_resizeLayer->resize(mouseEvent, m_offsetFromResizeCorner); + else { + if (m_frame->view()) + scrollbar = m_frame->view()->scrollbarUnderMouse(mouseEvent); + + if (!scrollbar) + scrollbar = mev.scrollbar(); + + if (m_lastScrollbarUnderMouse != scrollbar) { + // Send mouse exited to the old scrollbar. + if (m_lastScrollbarUnderMouse) + m_lastScrollbarUnderMouse->handleMouseOutEvent(mouseEvent); + m_lastScrollbarUnderMouse = m_mousePressed ? 0 : scrollbar; + } + } + + bool swallowEvent = false; + Node* targetNode = m_capturingMouseEventsNode ? m_capturingMouseEventsNode.get() : mev.targetNode(); + RefPtr<Frame> newSubframe = subframeForTargetNode(targetNode); + + // We want mouseouts to happen first, from the inside out. First send a move event to the last subframe so that it will fire mouseouts. + if (m_lastMouseMoveEventSubframe && m_lastMouseMoveEventSubframe->tree()->isDescendantOf(m_frame) && m_lastMouseMoveEventSubframe != newSubframe) + passMouseMoveEventToSubframe(mev, m_lastMouseMoveEventSubframe.get()); + + if (newSubframe) { + // Update over/out state before passing the event to the subframe. + updateMouseEventTargetNode(mev.targetNode(), mouseEvent, true); + swallowEvent |= passMouseMoveEventToSubframe(mev, newSubframe.get(), hoveredNode); + } else { + if (scrollbar && !m_mousePressed) + scrollbar->handleMouseMoveEvent(mouseEvent); // Handle hover effects on platforms that support visual feedback on scrollbar hovering. + if ((!m_resizeLayer || !m_resizeLayer->inResizeMode()) && m_frame->view()) + m_frame->view()->setCursor(selectCursor(mev, scrollbar)); + } + + m_lastMouseMoveEventSubframe = newSubframe; + + if (swallowEvent) + return true; + + swallowEvent = dispatchMouseEvent(mousemoveEvent, mev.targetNode(), false, 0, mouseEvent, true); + if (!swallowEvent) + swallowEvent = handleMouseDraggedEvent(mev); + + return swallowEvent; +} + +void EventHandler::invalidateClick() +{ + m_clickCount = 0; + m_clickNode = 0; +} + +bool EventHandler::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent) +{ + if (!m_frame->document()) + return false; + + RefPtr<FrameView> protector(m_frame->view()); + + m_mousePressed = false; + m_currentMousePosition = mouseEvent.pos(); + +#if ENABLE(SVG) + if (m_svgPan) { + m_svgPan = false; + static_cast<SVGDocument*>(m_frame->document())->updatePan(m_currentMousePosition); + return true; + } +#endif + + if (m_frameSetBeingResized) + return dispatchMouseEvent(mouseupEvent, m_frameSetBeingResized.get(), true, m_clickCount, mouseEvent, false); + + if (m_lastScrollbarUnderMouse) { + invalidateClick(); + return m_lastScrollbarUnderMouse->handleMouseReleaseEvent(mouseEvent); + } + + MouseEventWithHitTestResults mev = prepareMouseEvent(HitTestRequest(false, false, false, true), mouseEvent); + Node* targetNode = m_capturingMouseEventsNode.get() ? m_capturingMouseEventsNode.get() : mev.targetNode(); + Frame* subframe = subframeForTargetNode(targetNode); + if (subframe && passMouseReleaseEventToSubframe(mev, subframe)) { + m_capturingMouseEventsNode = 0; + return true; + } + + bool swallowMouseUpEvent = dispatchMouseEvent(mouseupEvent, mev.targetNode(), true, m_clickCount, mouseEvent, false); + + // Don't ever dispatch click events for right clicks + bool swallowClickEvent = false; + if (m_clickCount > 0 && mouseEvent.button() != RightButton && mev.targetNode() == m_clickNode) + swallowClickEvent = dispatchMouseEvent(clickEvent, mev.targetNode(), true, m_clickCount, mouseEvent, true); + + if (m_resizeLayer) { + m_resizeLayer->setInResizeMode(false); + m_resizeLayer = 0; + } + + bool swallowMouseReleaseEvent = false; + if (!swallowMouseUpEvent) + swallowMouseReleaseEvent = handleMouseReleaseEvent(mev); + + invalidateClick(); + + return swallowMouseUpEvent || swallowClickEvent || swallowMouseReleaseEvent; +} + +bool EventHandler::dispatchDragEvent(const AtomicString& eventType, Node* dragTarget, const PlatformMouseEvent& event, Clipboard* clipboard) +{ + IntPoint contentsPos = m_frame->view()->windowToContents(event.pos()); + + RefPtr<MouseEvent> me = new MouseEvent(eventType, + true, true, m_frame->document()->defaultView(), + 0, event.globalX(), event.globalY(), contentsPos.x(), contentsPos.y(), + event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(), + 0, 0, clipboard); + + ExceptionCode ec = 0; + EventTargetNodeCast(dragTarget)->dispatchEvent(me.get(), ec, true); + return me->defaultPrevented(); +} + +bool EventHandler::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard) +{ + bool accept = false; + + if (!m_frame->document()) + return false; + + if (!m_frame->view()) + return false; + + MouseEventWithHitTestResults mev = prepareMouseEvent(HitTestRequest(true, false), event); + + // Drag events should never go to text nodes (following IE, and proper mouseover/out dispatch) + Node* newTarget = mev.targetNode(); + if (newTarget && newTarget->isTextNode()) + newTarget = newTarget->parentNode(); + if (newTarget) + newTarget = newTarget->shadowAncestorNode(); + + if (m_dragTarget != newTarget) { + // FIXME: this ordering was explicitly chosen to match WinIE. However, + // it is sometimes incorrect when dragging within subframes, as seen with + // LayoutTests/fast/events/drag-in-frames.html. + if (newTarget) + if (newTarget->hasTagName(frameTag) || newTarget->hasTagName(iframeTag)) + accept = static_cast<HTMLFrameElementBase*>(newTarget)->contentFrame()->eventHandler()->updateDragAndDrop(event, clipboard); + else + accept = dispatchDragEvent(dragenterEvent, newTarget, event, clipboard); + + if (m_dragTarget) { + Frame* frame = (m_dragTarget->hasTagName(frameTag) || m_dragTarget->hasTagName(iframeTag)) + ? static_cast<HTMLFrameElementBase*>(m_dragTarget.get())->contentFrame() : 0; + if (frame) + accept = frame->eventHandler()->updateDragAndDrop(event, clipboard); + else + dispatchDragEvent(dragleaveEvent, m_dragTarget.get(), event, clipboard); + } + } else { + if (newTarget) + if (newTarget->hasTagName(frameTag) || newTarget->hasTagName(iframeTag)) + accept = static_cast<HTMLFrameElementBase*>(newTarget)->contentFrame()->eventHandler()->updateDragAndDrop(event, clipboard); + else + accept = dispatchDragEvent(dragoverEvent, newTarget, event, clipboard); + } + m_dragTarget = newTarget; + + return accept; +} + +void EventHandler::cancelDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard) +{ + if (m_dragTarget) { + Frame* frame = (m_dragTarget->hasTagName(frameTag) || m_dragTarget->hasTagName(iframeTag)) + ? static_cast<HTMLFrameElementBase*>(m_dragTarget.get())->contentFrame() : 0; + if (frame) + frame->eventHandler()->cancelDragAndDrop(event, clipboard); + else + dispatchDragEvent(dragleaveEvent, m_dragTarget.get(), event, clipboard); + } + clearDragState(); +} + +bool EventHandler::performDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard) +{ + bool accept = false; + if (m_dragTarget) { + Frame* frame = (m_dragTarget->hasTagName(frameTag) || m_dragTarget->hasTagName(iframeTag)) + ? static_cast<HTMLFrameElementBase*>(m_dragTarget.get())->contentFrame() : 0; + if (frame) + accept = frame->eventHandler()->performDragAndDrop(event, clipboard); + else + accept = dispatchDragEvent(dropEvent, m_dragTarget.get(), event, clipboard); + } + clearDragState(); + return accept; +} + +void EventHandler::clearDragState() +{ + m_dragTarget = 0; + m_capturingMouseEventsNode = 0; +#if PLATFORM(MAC) + m_sendingEventToSubview = false; +#endif +} + +Node* EventHandler::nodeUnderMouse() const +{ + return m_nodeUnderMouse.get(); +} + +void EventHandler::setCapturingMouseEventsNode(PassRefPtr<Node> n) +{ + m_capturingMouseEventsNode = n; +} + +MouseEventWithHitTestResults EventHandler::prepareMouseEvent(const HitTestRequest& request, const PlatformMouseEvent& mev) +{ + ASSERT(m_frame); + ASSERT(m_frame->document()); + + IntPoint documentPoint = m_frame->view()->windowToContents(mev.pos()); + return m_frame->document()->prepareMouseEvent(request, documentPoint, mev); +} + +void EventHandler::updateMouseEventTargetNode(Node* targetNode, const PlatformMouseEvent& mouseEvent, bool fireMouseOverOut) +{ + Node* result = targetNode; + + // If we're capturing, we always go right to that node. + if (m_capturingMouseEventsNode) + result = m_capturingMouseEventsNode.get(); + else { + // If the target node is a text node, dispatch on the parent node - rdar://4196646 + if (result && result->isTextNode()) + result = result->parentNode(); + if (result) + result = result->shadowAncestorNode(); + } + m_nodeUnderMouse = result; + + // Fire mouseout/mouseover if the mouse has shifted to a different node. + if (fireMouseOverOut) { + if (m_lastNodeUnderMouse && m_lastNodeUnderMouse->document() != m_frame->document()) { + m_lastNodeUnderMouse = 0; + m_lastScrollbarUnderMouse = 0; + } + + if (m_lastNodeUnderMouse != m_nodeUnderMouse) { + // send mouseout event to the old node + if (m_lastNodeUnderMouse) + EventTargetNodeCast(m_lastNodeUnderMouse.get())->dispatchMouseEvent(mouseEvent, mouseoutEvent, 0, m_nodeUnderMouse.get()); + // send mouseover event to the new node + if (m_nodeUnderMouse) + EventTargetNodeCast(m_nodeUnderMouse.get())->dispatchMouseEvent(mouseEvent, mouseoverEvent, 0, m_lastNodeUnderMouse.get()); + } + m_lastNodeUnderMouse = m_nodeUnderMouse; + } +} + +bool EventHandler::dispatchMouseEvent(const AtomicString& eventType, Node* targetNode, bool cancelable, int clickCount, const PlatformMouseEvent& mouseEvent, bool setUnder) +{ + updateMouseEventTargetNode(targetNode, mouseEvent, setUnder); + + bool swallowEvent = false; + + if (m_nodeUnderMouse) + swallowEvent = EventTargetNodeCast(m_nodeUnderMouse.get())->dispatchMouseEvent(mouseEvent, eventType, clickCount); + + if (!swallowEvent && eventType == mousedownEvent) { + // Blur current focus node when a link/button is clicked; this + // is expected by some sites that rely on onChange handlers running + // from form fields before the button click is processed. + Node* node = m_nodeUnderMouse.get(); + RenderObject* renderer = node ? node->renderer() : 0; + + // Walk up the render tree to search for a node to focus. + // Walking up the DOM tree wouldn't work for shadow trees, like those behind the engine-based text fields. + while (renderer) { + node = renderer->element(); + if (node && node->isFocusable()) { + // To fix <rdar://problem/4895428> Can't drag selected ToDo, we don't focus a + // node on mouse down if it's selected and inside a focused node. It will be + // focused if the user does a mouseup over it, however, because the mouseup + // will set a selection inside it, which will call setFocuseNodeIfNeeded. + ExceptionCode ec = 0; + Node* n = node->isShadowNode() ? node->shadowParentNode() : node; + if (m_frame->selectionController()->isRange() && + m_frame->selectionController()->toRange()->compareNode(n, ec) == Range::NODE_INSIDE && + n->isDescendantOf(m_frame->document()->focusedNode())) + return false; + + break; + } + + renderer = renderer->parent(); + } + // If focus shift is blocked, we eat the event. Note we should never clear swallowEvent + // if the page already set it (e.g., by canceling default behavior). + if (node && node->isMouseFocusable()) { + if (!m_frame->page()->focusController()->setFocusedNode(node, m_frame)) + swallowEvent = true; + } else if (!node || !node->focused()) { + if (!m_frame->page()->focusController()->setFocusedNode(0, m_frame)) + swallowEvent = true; + } + } + + return swallowEvent; +} + +bool EventHandler::handleWheelEvent(PlatformWheelEvent& e) +{ + Document* doc = m_frame->document(); + if (!doc) + return false; + + RenderObject* docRenderer = doc->renderer(); + if (!docRenderer) + return false; + + IntPoint vPoint = m_frame->view()->windowToContents(e.pos()); + + HitTestRequest request(true, false); + HitTestResult result(vPoint); + doc->renderer()->layer()->hitTest(request, result); + Node* node = result.innerNode(); + + if (node) { + // Figure out which view to send the event to. + RenderObject* target = node->renderer(); + + if (target && target->isWidget()) { + Widget* widget = static_cast<RenderWidget*>(target)->widget(); + + if (widget && passWheelEventToWidget(e, widget)) { + e.accept(); + return true; + } + } + + node = node->shadowAncestorNode(); + EventTargetNodeCast(node)->dispatchWheelEvent(e); + if (e.isAccepted()) + return true; + + if (node->renderer()) { + // Just break up into two scrolls if we need to. Diagonal movement on + // a MacBook pro is an example of a 2-dimensional mouse wheel event (where both deltaX and deltaY can be set). + float deltaX = e.isContinuous() ? e.continuousDeltaX() : e.deltaX(); + float deltaY = e.isContinuous() ? e.continuousDeltaY() : e.deltaY(); + if (deltaX && node->renderer()->scroll(deltaX < 0 ? ScrollRight : ScrollLeft, e.isContinuous() ? ScrollByPixel : ScrollByLine, + deltaX < 0 ? -deltaX : deltaX)) + e.accept(); + if (deltaY && node->renderer()->scroll(deltaY < 0 ? ScrollDown : ScrollUp, e.isContinuous() ? ScrollByPixel : ScrollByLine, + deltaY < 0 ? -deltaY : deltaY)) + e.accept(); + } + } + + if (!e.isAccepted()) + m_frame->view()->wheelEvent(e); + + return e.isAccepted(); +} + +bool EventHandler::sendContextMenuEvent(const PlatformMouseEvent& event) +{ + Document* doc = m_frame->document(); + FrameView* v = m_frame->view(); + if (!doc || !v) + return false; + + bool swallowEvent; + IntPoint viewportPos = v->windowToContents(event.pos()); + MouseEventWithHitTestResults mev = doc->prepareMouseEvent(HitTestRequest(false, true), viewportPos, event); + + if (!m_frame->selectionController()->contains(viewportPos) && + // FIXME: In the editable case, word selection sometimes selects content that isn't underneath the mouse. + // If the selection is non-editable, we do word selection to make it easier to use the contextual menu items + // available for text selections. But only if we're above text. + (m_frame->selectionController()->isContentEditable() || mev.targetNode() && mev.targetNode()->isTextNode())) { + m_mouseDownMayStartSelect = true; // context menu events are always allowed to perform a selection + selectClosestWordOrLinkFromMouseEvent(mev); + } + + swallowEvent = dispatchMouseEvent(contextmenuEvent, mev.targetNode(), true, 0, event, true); + + return swallowEvent; +} + +void EventHandler::scheduleHoverStateUpdate() +{ + if (!m_hoverTimer.isActive()) + m_hoverTimer.startOneShot(0); +} + +// Whether or not a mouse down can begin the creation of a selection. Fires the selectStart event. +bool EventHandler::canMouseDownStartSelect(Node* node) +{ + if (!node || !node->renderer()) + return true; + + // Some controls and images can't start a select on a mouse down. + if (!node->canStartSelection()) + return false; + + for (RenderObject* curr = node->renderer(); curr; curr = curr->parent()) + if (Node* node = curr->element()) + return EventTargetNodeCast(node)->dispatchHTMLEvent(selectstartEvent, true, true); + + return true; +} + +bool EventHandler::canMouseDragExtendSelect(Node* node) +{ + if (!node || !node->renderer()) + return true; + + for (RenderObject* curr = node->renderer(); curr; curr = curr->parent()) + if (Node* node = curr->element()) + return EventTargetNodeCast(node)->dispatchHTMLEvent(selectstartEvent, true, true); + + return true; +} + +void EventHandler::setResizingFrameSet(HTMLFrameSetElement* frameSet) +{ + m_frameSetBeingResized = frameSet; +} + +void EventHandler::resizeLayerDestroyed() +{ + ASSERT(m_resizeLayer); + m_resizeLayer = 0; +} + +void EventHandler::hoverTimerFired(Timer<EventHandler>*) +{ + m_hoverTimer.stop(); + + ASSERT(m_frame); + ASSERT(m_frame->document()); + + if (RenderObject* renderer = m_frame->renderer()) { + HitTestResult result(m_frame->view()->windowToContents(m_currentMousePosition)); + renderer->layer()->hitTest(HitTestRequest(false, false, true), result); + m_frame->document()->updateRendering(); + } +} + +static EventTargetNode* eventTargetNodeForDocument(Document* doc) +{ + if (!doc) + return 0; + Node* node = doc->focusedNode(); + if (!node) { + if (doc->isHTMLDocument()) + node = doc->body(); + else + node = doc->documentElement(); + if (!node) + return 0; + } + return EventTargetNodeCast(node); +} + +bool EventHandler::handleAccessKey(const PlatformKeyboardEvent& evt) +{ +#if PLATFORM(MAC) || PLATFORM(QT) + if (evt.ctrlKey()) +#else + if (evt.altKey()) +#endif + { + String key = evt.unmodifiedText(); + Element* elem = m_frame->document()->getElementByAccessKey(key.lower()); + if (elem) { + elem->accessKeyAction(false); + return true; + } + } + + return false; +} + +#if !PLATFORM(MAC) +bool EventHandler::needsKeyboardEventDisambiguationQuirks() const +{ + return false; +} +#endif + +bool EventHandler::keyEvent(const PlatformKeyboardEvent& initialKeyEvent) +{ + // Check for cases where we are too early for events -- possible unmatched key up + // from pressing return in the location bar. + RefPtr<EventTargetNode> node = eventTargetNodeForDocument(m_frame->document()); + if (!node) + return false; + + // FIXME: what is this doing here, in keyboard event handler? + m_frame->loader()->resetMultipleFormSubmissionProtection(); + + // In IE, access keys are special, they are handled after default keydown processing, but cannot be canceled - this is hard to match. + // On Mac OS X, we process them before dispatching keydown, as the default keydown handler implements Emacs key bindings, which may conflict + // with access keys. Then we dispatch keydown, but suppress its default handling. + // On Windows, WebKit explicitly calls handleAccessKey() instead of dispatching a keypress event for WM_SYSCHAR messages. + // Other platforms currently match either Mac or Windows behavior, depending on whether they send combined KeyDown events. + bool matchedAnAccessKey = false; + if (initialKeyEvent.type() == PlatformKeyboardEvent::KeyDown) + matchedAnAccessKey = handleAccessKey(initialKeyEvent); + + // FIXME: it would be fair to let an input method handle KeyUp events before DOM dispatch. + if (initialKeyEvent.type() == PlatformKeyboardEvent::KeyUp || initialKeyEvent.type() == PlatformKeyboardEvent::Char) + return !node->dispatchKeyEvent(initialKeyEvent); + + bool backwardCompatibilityMode = needsKeyboardEventDisambiguationQuirks(); + + ExceptionCode ec; + PlatformKeyboardEvent keyDownEvent = initialKeyEvent; + if (keyDownEvent.type() != PlatformKeyboardEvent::RawKeyDown) + keyDownEvent.disambiguateKeyDownEvent(PlatformKeyboardEvent::RawKeyDown, backwardCompatibilityMode); + RefPtr<KeyboardEvent> keydown = new KeyboardEvent(keyDownEvent, m_frame->document()->defaultView()); + if (matchedAnAccessKey) + keydown->setDefaultPrevented(true); + keydown->setTarget(node); + + if (initialKeyEvent.type() == PlatformKeyboardEvent::RawKeyDown) { + node->dispatchEvent(keydown, ec, true); + return keydown->defaultHandled() || keydown->defaultPrevented(); + } + + // Run input method in advance of DOM event handling. This may result in the IM + // modifying the page prior the keydown event, but this behaviour is necessary + // in order to match IE: + // 1. preventing default handling of keydown and keypress events has no effect on IM input; + // 2. if an input method handles the event, its keyCode is set to 229 in keydown event. + m_frame->editor()->handleInputMethodKeydown(keydown.get()); + + bool handledByInputMethod = keydown->defaultHandled(); + + if (handledByInputMethod) { + keyDownEvent.setWindowsVirtualKeyCode(CompositionEventKeyCode); + keydown = new KeyboardEvent(keyDownEvent, m_frame->document()->defaultView()); + keydown->setTarget(node); + keydown->setDefaultHandled(); + } + + node->dispatchEvent(keydown, ec, true); + bool keydownResult = keydown->defaultHandled() || keydown->defaultPrevented(); + if (handledByInputMethod || (keydownResult && !backwardCompatibilityMode)) + return keydownResult; + + // Focus may have changed during keydown handling, so refetch node. + // But if we are dispatching a fake backward compatibility keypress, then we pretend that the keypress happened on the original node. + if (!keydownResult) { + node = eventTargetNodeForDocument(m_frame->document()); + if (!node) + return false; + } + + PlatformKeyboardEvent keyPressEvent = initialKeyEvent; + keyPressEvent.disambiguateKeyDownEvent(PlatformKeyboardEvent::Char, backwardCompatibilityMode); + if (keyPressEvent.text().isEmpty()) + return keydownResult; + RefPtr<KeyboardEvent> keypress = new KeyboardEvent(keyPressEvent, m_frame->document()->defaultView()); + keypress->setTarget(node); + if (keydownResult) + keypress->setDefaultPrevented(true); +#if PLATFORM(MAC) + keypress->keypressCommands() = keydown->keypressCommands(); +#endif + node->dispatchEvent(keypress, ec, true); + + return keydownResult || keypress->defaultPrevented() || keypress->defaultHandled(); +} + +void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event) +{ + if (event->type() == keydownEvent) { + m_frame->editor()->handleKeyboardEvent(event); + if (event->defaultHandled()) + return; + if (event->keyIdentifier() == "U+0009") + defaultTabEventHandler(event); + } + if (event->type() == keypressEvent) { + m_frame->editor()->handleKeyboardEvent(event); + if (event->defaultHandled()) + return; + } +} + +bool EventHandler::dragHysteresisExceeded(const FloatPoint& floatDragViewportLocation) const +{ + IntPoint dragViewportLocation((int)floatDragViewportLocation.x(), (int)floatDragViewportLocation.y()); + return dragHysteresisExceeded(dragViewportLocation); +} + +bool EventHandler::dragHysteresisExceeded(const IntPoint& dragViewportLocation) const +{ + IntPoint dragLocation = m_frame->view()->windowToContents(dragViewportLocation); + IntSize delta = dragLocation - m_mouseDownPos; + + int threshold = GeneralDragHysteresis; + if (dragState().m_dragSrcIsImage) + threshold = ImageDragHysteresis; + else if (dragState().m_dragSrcIsLink) + threshold = LinkDragHysteresis; + else if (dragState().m_dragSrcInSelection) + threshold = TextDragHysteresis; + + return abs(delta.width()) >= threshold || abs(delta.height()) >= threshold; +} + +void EventHandler::freeClipboard() +{ + if (dragState().m_dragClipboard) + dragState().m_dragClipboard->setAccessPolicy(ClipboardNumb); +} + +bool EventHandler::shouldDragAutoNode(Node* node, const IntPoint& point) const +{ + ASSERT(node); + if (node->hasChildNodes() || !m_frame->view()) + return false; + return m_frame->page() && m_frame->page()->dragController()->mayStartDragAtEventLocation(m_frame, point); +} + +void EventHandler::dragSourceMovedTo(const PlatformMouseEvent& event) +{ + if (dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) + // for now we don't care if event handler cancels default behavior, since there is none + dispatchDragSrcEvent(dragEvent, event); +} + +void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperation operation) +{ + if (dragState().m_dragSrc && dragState().m_dragSrcMayBeDHTML) { + dragState().m_dragClipboard->setDestinationOperation(operation); + // for now we don't care if event handler cancels default behavior, since there is none + dispatchDragSrcEvent(dragendEvent, event); + } + freeClipboard(); + dragState().m_dragSrc = 0; +} + +// returns if we should continue "default processing", i.e., whether eventhandler canceled +bool EventHandler::dispatchDragSrcEvent(const AtomicString& eventType, const PlatformMouseEvent& event) +{ + return !dispatchDragEvent(eventType, dragState().m_dragSrc.get(), event, dragState().m_dragClipboard.get()); +} + +bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event) +{ + if (event.event().button() != LeftButton || event.event().eventType() != MouseEventMoved) { + // If we allowed the other side of the bridge to handle a drag + // last time, then m_mousePressed might still be set. So we + // clear it now to make sure the next move after a drag + // doesn't look like a drag. + m_mousePressed = false; + return false; + } + + if (eventLoopHandleMouseDragged(event)) + return true; + + // Careful that the drag starting logic stays in sync with eventMayStartDrag() + + if (m_mouseDownMayStartDrag && !dragState().m_dragSrc) { + allowDHTMLDrag(dragState().m_dragSrcMayBeDHTML, dragState().m_dragSrcMayBeUA); + if (!dragState().m_dragSrcMayBeDHTML && !dragState().m_dragSrcMayBeUA) + m_mouseDownMayStartDrag = false; // no element is draggable + } + + if (m_mouseDownMayStartDrag && !dragState().m_dragSrc) { + // try to find an element that wants to be dragged + HitTestRequest request(true, false); + HitTestResult result(m_mouseDownPos); + m_frame->renderer()->layer()->hitTest(request, result); + Node* node = result.innerNode(); + if (node && node->renderer()) + dragState().m_dragSrc = node->renderer()->draggableNode(dragState().m_dragSrcMayBeDHTML, dragState().m_dragSrcMayBeUA, + m_mouseDownPos.x(), m_mouseDownPos.y(), dragState().m_dragSrcIsDHTML); + else + dragState().m_dragSrc = 0; + + if (!dragState().m_dragSrc) + m_mouseDownMayStartDrag = false; // no element is draggable + else { + // remember some facts about this source, while we have a HitTestResult handy + node = result.URLElement(); + dragState().m_dragSrcIsLink = node && node->isLink(); + + node = result.innerNonSharedNode(); + dragState().m_dragSrcIsImage = node && node->renderer() && node->renderer()->isImage(); + + dragState().m_dragSrcInSelection = m_frame->selectionController()->contains(m_mouseDownPos); + } + } + + // For drags starting in the selection, the user must wait between the mousedown and mousedrag, + // or else we bail on the dragging stuff and allow selection to occur + if (m_mouseDownMayStartDrag && !dragState().m_dragSrcIsImage && dragState().m_dragSrcInSelection && event.event().timestamp() - m_mouseDownTimestamp < TextDragDelay) { + m_mouseDownMayStartDrag = false; + dragState().m_dragSrc = 0; + // ...but if this was the first click in the window, we don't even want to start selection + if (eventActivatedView(event.event())) + m_mouseDownMayStartSelect = false; + } + + if (!m_mouseDownMayStartDrag) + return !mouseDownMayStartSelect() && !m_mouseDownMayStartAutoscroll; + + // We are starting a text/image/url drag, so the cursor should be an arrow + m_frame->view()->setCursor(pointerCursor()); + + if (!dragHysteresisExceeded(event.event().pos())) + return true; + + // Once we're past the hysteresis point, we don't want to treat this gesture as a click + invalidateClick(); + + DragOperation srcOp = DragOperationNone; + + freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just + // to make sure it gets numbified + dragState().m_dragClipboard = createDraggingClipboard(); + + if (dragState().m_dragSrcMayBeDHTML) { + // Check to see if the is a DOM based drag, if it is get the DOM specified drag + // image and offset + if (dragState().m_dragSrcIsDHTML) { + int srcX, srcY; + dragState().m_dragSrc->renderer()->absolutePosition(srcX, srcY); + IntSize delta = m_mouseDownPos - IntPoint(srcX, srcY); + dragState().m_dragClipboard->setDragImageElement(dragState().m_dragSrc.get(), IntPoint() + delta); + } + + m_mouseDownMayStartDrag = dispatchDragSrcEvent(dragstartEvent, m_mouseDown) + && !m_frame->selectionController()->isInPasswordField(); + + // Invalidate clipboard here against anymore pasteboard writing for security. The drag + // image can still be changed as we drag, but not the pasteboard data. + dragState().m_dragClipboard->setAccessPolicy(ClipboardImageWritable); + + if (m_mouseDownMayStartDrag) { + // gather values from DHTML element, if it set any + dragState().m_dragClipboard->sourceOperation(srcOp); + + // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with + // dragImage! Because of that dumb reentrancy, we may think we've not started the + // drag when that happens. So we have to assume it's started before we kick it off. + dragState().m_dragClipboard->setDragHasStarted(); + } + } + + if (m_mouseDownMayStartDrag) { + DragController* dragController = m_frame->page() ? m_frame->page()->dragController() : 0; + bool startedDrag = dragController && dragController->startDrag(m_frame, dragState().m_dragClipboard.get(), srcOp, event.event(), m_mouseDownPos, dragState().m_dragSrcIsDHTML); + if (!startedDrag && dragState().m_dragSrcMayBeDHTML) { + // Drag was canned at the last minute - we owe m_dragSrc a DRAGEND event + dispatchDragSrcEvent(dragendEvent, event.event()); + m_mouseDownMayStartDrag = false; + } + } + + if (!m_mouseDownMayStartDrag) { + // something failed to start the drag, cleanup + freeClipboard(); + dragState().m_dragSrc = 0; + } + + // No more default handling (like selection), whether we're past the hysteresis bounds or not + return true; +} + +bool EventHandler::handleTextInputEvent(const String& text, Event* underlyingEvent, + bool isLineBreak, bool isBackTab) +{ + if (!m_frame) + return false; +#ifndef NDEBUG + // Platforms should differentiate real commands like selectAll from text input in disguise (like insertNewline), + // and avoid dispatching text input events from keydown default handlers. + if (underlyingEvent && underlyingEvent->isKeyboardEvent()) + ASSERT(static_cast<KeyboardEvent*>(underlyingEvent)->type() == keypressEvent); +#endif + EventTarget* target; + if (underlyingEvent) + target = underlyingEvent->target(); + else + target = eventTargetNodeForDocument(m_frame->document()); + if (!target) + return false; + RefPtr<TextEvent> event = new TextEvent(m_frame->domWindow(), text); + event->setUnderlyingEvent(underlyingEvent); + event->setIsLineBreak(isLineBreak); + event->setIsBackTab(isBackTab); + ExceptionCode ec; + return target->dispatchEvent(event.release(), ec, true); +} + + +#if !PLATFORM(MAC) && !PLATFORM(QT) +bool EventHandler::invertSenseOfTabsToLinks(KeyboardEvent*) const +{ + return false; +} +#endif + +bool EventHandler::tabsToLinks(KeyboardEvent* event) const +{ + Page* page = m_frame->page(); + if (!page) + return false; + + if (page->chrome()->client()->tabsToLinks()) + return !invertSenseOfTabsToLinks(event); + + return invertSenseOfTabsToLinks(event); +} + +void EventHandler::defaultTextInputEventHandler(TextEvent* event) +{ + String data = event->data(); + if (data == "\n") { + if (event->isLineBreak()) { + if (m_frame->editor()->insertLineBreak()) + event->setDefaultHandled(); + } else { + if (m_frame->editor()->insertParagraphSeparator()) + event->setDefaultHandled(); + } + } else { + if (m_frame->editor()->insertTextWithoutSendingTextEvent(data, false, event)) + event->setDefaultHandled(); + } +} + +void EventHandler::defaultTabEventHandler(KeyboardEvent* event) +{ + // We should only advance focus on tabs if no special modifier keys are held down. + if (event->ctrlKey() || event->metaKey() || event->altGraphKey()) + return; + + Page* page = m_frame->page(); + if (!page) + return; + if (!page->tabKeyCyclesThroughElements()) + return; + + FocusDirection focusDirection = event->shiftKey() ? FocusDirectionBackward : FocusDirectionForward; + + // Tabs can be used in design mode editing. + if (m_frame->document()->inDesignMode()) + return; + + if (page->focusController()->advanceFocus(focusDirection, event)) + event->setDefaultHandled(); +} + +void EventHandler::capsLockStateMayHaveChanged() +{ + if (Document* d = m_frame->document()) + if (Node* node = d->focusedNode()) + if (RenderObject* r = node->renderer()) + r->capsLockStateMayHaveChanged(); +} + +} diff --git a/WebCore/page/EventHandler.h b/WebCore/page/EventHandler.h new file mode 100644 index 0000000..95a976d --- /dev/null +++ b/WebCore/page/EventHandler.h @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EventHandler_h +#define EventHandler_h + +#include "DragActions.h" +#include "FocusDirection.h" +#include "PlatformMouseEvent.h" +#include "ScrollTypes.h" +#include "Timer.h" +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/Platform.h> +#include <wtf/RefPtr.h> + +#if PLATFORM(MAC) +#include "WebCoreKeyboardUIMode.h" +#ifndef __OBJC__ +class NSEvent; +class NSView; +#endif +#endif + +namespace WebCore { + +class AtomicString; +class Clipboard; +class Cursor; +class EventTargetNode; +class Event; +class FloatPoint; +class FloatRect; +class Frame; +class HitTestResult; +class HTMLFrameSetElement; +class KeyboardEvent; +class MouseEventWithHitTestResults; +class Node; +class PlatformKeyboardEvent; +class PlatformScrollbar; +class PlatformWheelEvent; +class RenderLayer; +class RenderObject; +class RenderWidget; +class String; +class TextEvent; +class VisiblePosition; +class Widget; + +struct HitTestRequest; + +extern const int LinkDragHysteresis; +extern const int ImageDragHysteresis; +extern const int TextDragHysteresis; +extern const int GeneralDragHysteresis; +extern const double TextDragDelay; + +class EventHandler : Noncopyable { +public: + EventHandler(Frame*); + ~EventHandler(); + + void clear(); + + void updateSelectionForMouseDrag(); + + Node* mousePressNode() const; + void setMousePressNode(PassRefPtr<Node>); + + void stopAutoscrollTimer(bool rendererIsBeingDestroyed = false); + RenderObject* autoscrollRenderer() const; + + HitTestResult hitTestResultAtPoint(const IntPoint&, bool allowShadowContent); + + bool mousePressed() const { return m_mousePressed; } + void setMousePressed(bool pressed) { m_mousePressed = pressed; } + + void setCapturingMouseEventsNode(PassRefPtr<Node>); + + bool updateDragAndDrop(const PlatformMouseEvent&, Clipboard*); + void cancelDragAndDrop(const PlatformMouseEvent&, Clipboard*); + bool performDragAndDrop(const PlatformMouseEvent&, Clipboard*); + + void scheduleHoverStateUpdate(); + + void setResizingFrameSet(HTMLFrameSetElement*); + + void resizeLayerDestroyed(); + + IntPoint currentMousePosition() const; + + void setIgnoreWheelEvents(bool); + + bool scrollOverflow(ScrollDirection, ScrollGranularity); + + bool shouldDragAutoNode(Node*, const IntPoint&) const; // -webkit-user-drag == auto + + bool tabsToLinks(KeyboardEvent*) const; + bool tabsToAllControls(KeyboardEvent*) const; + + bool mouseDownMayStartSelect() const { return m_mouseDownMayStartSelect; } + + bool mouseMoved(const PlatformMouseEvent&); + + bool handleMousePressEvent(const PlatformMouseEvent&); + bool handleMouseMoveEvent(const PlatformMouseEvent&, HitTestResult* hoveredNode = 0); + bool handleMouseReleaseEvent(const PlatformMouseEvent&); + bool handleWheelEvent(PlatformWheelEvent&); + + bool sendContextMenuEvent(const PlatformMouseEvent&); + + void setMouseDownMayStartAutoscroll() { m_mouseDownMayStartAutoscroll = true; } + + bool needsKeyboardEventDisambiguationQuirks() const; + + bool handleAccessKey(const PlatformKeyboardEvent&); + bool keyEvent(const PlatformKeyboardEvent&); + void defaultKeyboardEventHandler(KeyboardEvent*); + + bool handleTextInputEvent(const String& text, Event* underlyingEvent = 0, + bool isLineBreak = false, bool isBackTab = false); + void defaultTextInputEventHandler(TextEvent*); + + bool eventMayStartDrag(const PlatformMouseEvent&) const; + + void dragSourceMovedTo(const PlatformMouseEvent&); + void dragSourceEndedAt(const PlatformMouseEvent&, DragOperation); + + void focusDocumentView(); + + void capsLockStateMayHaveChanged(); + +#if PLATFORM(MAC) + PassRefPtr<KeyboardEvent> currentKeyboardEvent() const; + + void mouseDown(NSEvent*); + void mouseDragged(NSEvent*); + void mouseUp(NSEvent*); + void mouseMoved(NSEvent*); + bool keyEvent(NSEvent*); + bool wheelEvent(NSEvent*); + + void sendFakeEventsAfterWidgetTracking(NSEvent* initiatingEvent); + + void setActivationEventNumber(int num) { m_activationEventNumber = num; } + + NSEvent *currentNSEvent(); +#endif + +private: + struct EventHandlerDragState { + RefPtr<Node> m_dragSrc; // element that may be a drag source, for the current mouse gesture + bool m_dragSrcIsLink; + bool m_dragSrcIsImage; + bool m_dragSrcInSelection; + bool m_dragSrcMayBeDHTML; + bool m_dragSrcMayBeUA; // Are DHTML and/or the UserAgent allowed to drag out? + bool m_dragSrcIsDHTML; + RefPtr<Clipboard> m_dragClipboard; // used on only the source side of dragging + }; + static EventHandlerDragState& dragState(); + + Clipboard* createDraggingClipboard() const; + + bool eventActivatedView(const PlatformMouseEvent&) const; + void selectClosestWordFromMouseEvent(const MouseEventWithHitTestResults& event); + void selectClosestWordOrLinkFromMouseEvent(const MouseEventWithHitTestResults& event); + + bool handleMouseDoubleClickEvent(const PlatformMouseEvent&); + + bool handleMousePressEvent(const MouseEventWithHitTestResults&); + bool handleMousePressEventSingleClick(const MouseEventWithHitTestResults&); + bool handleMousePressEventDoubleClick(const MouseEventWithHitTestResults&); + bool handleMousePressEventTripleClick(const MouseEventWithHitTestResults&); + bool handleMouseDraggedEvent(const MouseEventWithHitTestResults&); + bool handleMouseReleaseEvent(const MouseEventWithHitTestResults&); + + Cursor selectCursor(const MouseEventWithHitTestResults&, PlatformScrollbar*); + + void hoverTimerFired(Timer<EventHandler>*); + + static bool canMouseDownStartSelect(Node*); + static bool canMouseDragExtendSelect(Node*); + + void handleAutoscroll(RenderObject*); + void startAutoscrollTimer(); + void setAutoscrollRenderer(RenderObject*); + + void autoscrollTimerFired(Timer<EventHandler>*); + + void invalidateClick(); + + Node* nodeUnderMouse() const; + + void updateMouseEventTargetNode(Node*, const PlatformMouseEvent&, bool fireMouseOverOut); + void fireMouseOverOut(bool fireMouseOver = true, bool fireMouseOut = true, bool updateLastNodeUnderMouse = true); + + MouseEventWithHitTestResults prepareMouseEvent(const HitTestRequest&, const PlatformMouseEvent&); + + bool dispatchMouseEvent(const AtomicString& eventType, Node* target, bool cancelable, int clickCount, const PlatformMouseEvent&, bool setUnder); + bool dispatchDragEvent(const AtomicString& eventType, Node* target, const PlatformMouseEvent&, Clipboard*); + + void freeClipboard(); + + bool handleDrag(const MouseEventWithHitTestResults&); + bool handleMouseUp(const MouseEventWithHitTestResults&); + void clearDragState(); + + bool dispatchDragSrcEvent(const AtomicString& eventType, const PlatformMouseEvent&); + + bool dragHysteresisExceeded(const FloatPoint&) const; + bool dragHysteresisExceeded(const IntPoint&) const; + + bool passMousePressEventToSubframe(MouseEventWithHitTestResults&, Frame* subframe); + bool passMouseMoveEventToSubframe(MouseEventWithHitTestResults&, Frame* subframe, HitTestResult* hoveredNode = 0); + bool passMouseReleaseEventToSubframe(MouseEventWithHitTestResults&, Frame* subframe); + + bool passSubframeEventToSubframe(MouseEventWithHitTestResults&, Frame* subframe, HitTestResult* hoveredNode = 0); + + bool passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar*); + + bool passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults&); + bool passWidgetMouseDownEventToWidget(RenderWidget*); + + bool passMouseDownEventToWidget(Widget*); + bool passWheelEventToWidget(PlatformWheelEvent&, Widget*); + + void defaultTabEventHandler(KeyboardEvent*); + + void allowDHTMLDrag(bool& flagDHTML, bool& flagUA) const; + + // The following are called at the beginning of handleMouseUp and handleDrag. + // If they return true it indicates that they have consumed the event. +#if PLATFORM(MAC) + bool eventLoopHandleMouseUp(const MouseEventWithHitTestResults&); + bool eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&); + NSView *mouseDownViewIfStillGood(); +#else + bool eventLoopHandleMouseUp(const MouseEventWithHitTestResults&) { return false; } + bool eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&) { return false; } +#endif + + bool invertSenseOfTabsToLinks(KeyboardEvent*) const; + + void updateSelectionForMouseDrag(Node* targetNode, const IntPoint& localPoint); + + Frame* m_frame; + + bool m_mousePressed; + RefPtr<Node> m_mousePressNode; + + bool m_mouseDownMayStartSelect; + bool m_mouseDownMayStartDrag; + bool m_mouseDownWasSingleClickInSelection; + bool m_beganSelectingText; + + IntPoint m_dragStartPos; + + Timer<EventHandler> m_hoverTimer; + + Timer<EventHandler> m_autoscrollTimer; + RenderObject* m_autoscrollRenderer; + bool m_mouseDownMayStartAutoscroll; + bool m_mouseDownWasInSubframe; +#if ENABLE(SVG) + bool m_svgPan; +#endif + + RenderLayer* m_resizeLayer; + + RefPtr<Node> m_capturingMouseEventsNode; + + RefPtr<Node> m_nodeUnderMouse; + RefPtr<Node> m_lastNodeUnderMouse; + RefPtr<Frame> m_lastMouseMoveEventSubframe; + RefPtr<PlatformScrollbar> m_lastScrollbarUnderMouse; + + int m_clickCount; + RefPtr<Node> m_clickNode; + + RefPtr<Node> m_dragTarget; + + RefPtr<HTMLFrameSetElement> m_frameSetBeingResized; + + IntSize m_offsetFromResizeCorner; + + IntPoint m_currentMousePosition; + IntPoint m_mouseDownPos; // in our view's coords + double m_mouseDownTimestamp; + PlatformMouseEvent m_mouseDown; + +#if PLATFORM(MAC) + NSView *m_mouseDownView; + bool m_sendingEventToSubview; + int m_activationEventNumber; +#endif + +}; + +} // namespace WebCore + +#endif // EventHandler_h diff --git a/WebCore/page/FocusController.cpp b/WebCore/page/FocusController.cpp new file mode 100644 index 0000000..a3542aa --- /dev/null +++ b/WebCore/page/FocusController.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FocusController.h" + +#include "AXObjectCache.h" +#include "Chrome.h" +#include "Document.h" +#include "Editor.h" +#include "EditorClient.h" +#include "Element.h" +#include "Event.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameView.h" +#include "FrameTree.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#include "KeyboardEvent.h" +#include "Page.h" +#include "Range.h" +#include "RenderObject.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include "Widget.h" +#include <wtf/Platform.h> + +namespace WebCore { + +using namespace EventNames; +using namespace HTMLNames; + +FocusController::FocusController(Page* page) + : m_page(page) + , m_isActive(false) +{ +} + +void FocusController::setFocusedFrame(PassRefPtr<Frame> frame) +{ + if (m_focusedFrame == frame) + return; + + if (m_focusedFrame) + m_focusedFrame->selectionController()->setFocused(false); + + m_focusedFrame = frame; + + if (m_focusedFrame) + m_focusedFrame->selectionController()->setFocused(true); +} + +Frame* FocusController::focusedOrMainFrame() +{ + if (Frame* frame = focusedFrame()) + return frame; + return m_page->mainFrame(); +} + +static Node* deepFocusableNode(FocusDirection direction, Node* node, KeyboardEvent* event) +{ + // The node we found might be a HTMLFrameOwnerElement, so descend down the frame tree until we find either: + // 1) a focusable node, or + // 2) the deepest-nested HTMLFrameOwnerElement + while (node && node->isFrameOwnerElement()) { + HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node); + if (!owner->contentFrame()) + break; + + Document* document = owner->contentFrame()->document(); + if (!document) + break; + + node = (direction == FocusDirectionForward) + ? document->nextFocusableNode(0, event) + : document->previousFocusableNode(0, event); + if (!node) { + node = owner; + break; + } + } + + return node; +} + +bool FocusController::setInitialFocus(FocusDirection direction, KeyboardEvent* event) +{ + return advanceFocus(direction, event, true); +} + +bool FocusController::advanceFocus(FocusDirection direction, KeyboardEvent* event, bool initialFocus) +{ + Frame* frame = focusedOrMainFrame(); + ASSERT(frame); + Document* document = frame->document(); + if (!document) + return false; + + Node* node = (direction == FocusDirectionForward) + ? document->nextFocusableNode(document->focusedNode(), event) + : document->previousFocusableNode(document->focusedNode(), event); + + // If there's no focusable node to advance to, move up the frame tree until we find one. + while (!node && frame) { + Frame* parentFrame = frame->tree()->parent(); + if (!parentFrame) + break; + + Document* parentDocument = parentFrame->document(); + if (!parentDocument) + break; + + HTMLFrameOwnerElement* owner = frame->ownerElement(); + if (!owner) + break; + + node = (direction == FocusDirectionForward) + ? parentDocument->nextFocusableNode(owner, event) + : parentDocument->previousFocusableNode(owner, event); + + frame = parentFrame; + } + + node = deepFocusableNode(direction, node, event); + + if (!node) { + // We didn't find a node to focus, so we should try to pass focus to Chrome. + if (!initialFocus && m_page->chrome()->canTakeFocus(direction)) { + document->setFocusedNode(0); + setFocusedFrame(0); + m_page->chrome()->takeFocus(direction); + return true; + } + + // Chrome doesn't want focus, so we should wrap focus. + if (Document* d = m_page->mainFrame()->document()) + node = (direction == FocusDirectionForward) + ? d->nextFocusableNode(0, event) + : d->previousFocusableNode(0, event); + + node = deepFocusableNode(direction, node, event); + + if (!node) + return false; + } + + ASSERT(node); + + if (node == document->focusedNode()) + // Focus wrapped around to the same node. + return true; + + if (!node->isElementNode()) + // FIXME: May need a way to focus a document here. + return false; + + if (node->isFrameOwnerElement()) { + // We focus frames rather than frame owners. + // FIXME: We should not focus frames that have no scrollbars, as focusing them isn't useful to the user. + HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node); + if (!owner->contentFrame()) + return false; + + document->setFocusedNode(0); + setFocusedFrame(owner->contentFrame()); + return true; + } + + // FIXME: It would be nice to just be able to call setFocusedNode(node) here, but we can't do + // that because some elements (e.g. HTMLInputElement and HTMLTextAreaElement) do extra work in + // their focus() methods. + + Document* newDocument = node->document(); + + if (newDocument != document) + // Focus is going away from this document, so clear the focused node. + document->setFocusedNode(0); + + if (newDocument) + setFocusedFrame(newDocument->frame()); + + static_cast<Element*>(node)->focus(false); + return true; +} + +static bool relinquishesEditingFocus(Node *node) +{ + ASSERT(node); + ASSERT(node->isContentEditable()); + + Node* root = node->rootEditableElement(); + Frame* frame = node->document()->frame(); + if (!frame || !root) + return false; + + return frame->editor()->shouldEndEditing(rangeOfContents(root).get()); +} + +static void clearSelectionIfNeeded(Frame* oldFocusedFrame, Frame* newFocusedFrame, Node* newFocusedNode) +{ + if (!oldFocusedFrame || !newFocusedFrame) + return; + + if (oldFocusedFrame->document() != newFocusedFrame->document()) + return; + + SelectionController* s = oldFocusedFrame->selectionController(); + if (s->isNone()) + return; + + Node* selectionStartNode = s->selection().start().node(); + if (selectionStartNode == newFocusedNode || selectionStartNode->isDescendantOf(newFocusedNode) || selectionStartNode->shadowAncestorNode() == newFocusedNode) + return; + + if (Node* mousePressNode = newFocusedFrame->eventHandler()->mousePressNode()) + if (mousePressNode->renderer() && !mousePressNode->canStartSelection()) + if (Node* root = s->rootEditableElement()) + if (Node* shadowAncestorNode = root->shadowAncestorNode()) + // Don't do this for textareas and text fields, when they lose focus their selections should be cleared + // and then restored when they regain focus, to match other browsers. + if (!shadowAncestorNode->hasTagName(inputTag) && !shadowAncestorNode->hasTagName(textareaTag)) + return; + + s->clear(); +} + +bool FocusController::setFocusedNode(Node* node, PassRefPtr<Frame> newFocusedFrame) +{ + RefPtr<Frame> oldFocusedFrame = focusedFrame(); + RefPtr<Document> oldDocument = oldFocusedFrame ? oldFocusedFrame->document() : 0; + + Node* oldFocusedNode = oldDocument ? oldDocument->focusedNode() : 0; + if (oldFocusedNode == node) + return true; + + if (oldFocusedNode && oldFocusedNode->rootEditableElement() == oldFocusedNode && !relinquishesEditingFocus(oldFocusedNode)) + return false; + + clearSelectionIfNeeded(oldFocusedFrame.get(), newFocusedFrame.get(), node); + + if (!node) { + if (oldDocument) + oldDocument->setFocusedNode(0); + m_page->editorClient()->setInputMethodState(false); + return true; + } + + RefPtr<Document> newDocument = node ? node->document() : 0; + + if (newDocument && newDocument->focusedNode() == node) { + m_page->editorClient()->setInputMethodState(node->shouldUseInputMethod()); + return true; + } + + if (oldDocument && oldDocument != newDocument) + oldDocument->setFocusedNode(0); + + setFocusedFrame(newFocusedFrame); + + if (newDocument) + newDocument->setFocusedNode(node); + + m_page->editorClient()->setInputMethodState(node->shouldUseInputMethod()); + + return true; +} + +void FocusController::setActive(bool active) +{ + if (m_isActive == active) + return; + + m_isActive = active; + + // FIXME: It would be nice to make Mac use this implementation someday. + // Right now Mac calls updateControlTints from within WebKit, and moving + // the call to here is not simple. +#if !PLATFORM(MAC) + if (FrameView* view = m_page->mainFrame()->view()) + view->updateControlTints(); +#endif + + focusedOrMainFrame()->selectionController()->pageActivationChanged(); +} + +} // namespace WebCore diff --git a/WebCore/page/FocusController.h b/WebCore/page/FocusController.h new file mode 100644 index 0000000..f4a6632 --- /dev/null +++ b/WebCore/page/FocusController.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FocusController_h +#define FocusController_h + +#include "FocusDirection.h" +#include <wtf/Forward.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class Frame; + class KeyboardEvent; + class Node; + class Page; + + class FocusController { + public: + FocusController(Page*); + + void setFocusedFrame(PassRefPtr<Frame>); + Frame* focusedFrame() const { return m_focusedFrame.get(); } + Frame* focusedOrMainFrame(); + + bool setInitialFocus(FocusDirection, KeyboardEvent*); + bool advanceFocus(FocusDirection, KeyboardEvent*, bool initialFocus = false); + + bool setFocusedNode(Node*, PassRefPtr<Frame>); + + void setActive(bool); + bool isActive() const { return m_isActive; } + + private: + Page* m_page; + RefPtr<Frame> m_focusedFrame; + bool m_isActive; + }; + +} // namespace WebCore + +#endif // FocusController_h diff --git a/WebCore/page/FocusDirection.h b/WebCore/page/FocusDirection.h new file mode 100644 index 0000000..261c745 --- /dev/null +++ b/WebCore/page/FocusDirection.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FocusDirection_h +#define FocusDirection_h + +namespace WebCore { + enum FocusDirection { + FocusDirectionForward = 0, + FocusDirectionBackward + }; +} + +#endif // FocusDirection_h diff --git a/WebCore/page/Frame.cpp b/WebCore/page/Frame.cpp new file mode 100644 index 0000000..2552ed4 --- /dev/null +++ b/WebCore/page/Frame.cpp @@ -0,0 +1,1886 @@ +/* + * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + * 1999 Lars Knoll <knoll@kde.org> + * 1999 Antti Koivisto <koivisto@kde.org> + * 2000 Simon Hausmann <hausmann@kde.org> + * 2000 Stefan Schimanski <1Stein@gmx.de> + * 2001 George Staikos <staikos@kde.org> + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com> + * Copyright (C) 2007 Trolltech ASA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "Frame.h" +#include "FramePrivate.h" + +#include "ApplyStyleCommand.h" +#include "BeforeUnloadEvent.h" +#include "CSSComputedStyleDeclaration.h" +#include "CSSProperty.h" +#include "CSSPropertyNames.h" +#include "CachedCSSStyleSheet.h" +#include "DOMWindow.h" +#include "DocLoader.h" +#include "DocumentType.h" +#include "EditingText.h" +#include "EditorClient.h" +#include "EventNames.h" +#include "FocusController.h" +#include "FrameLoader.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "HTMLDocument.h" +#include "HTMLFormElement.h" +#include "HTMLFrameElementBase.h" +#include "HTMLGenericFormElement.h" +#include "HTMLNames.h" +#include "HTMLTableCellElement.h" +#include "Logging.h" +#include "MediaFeatureNames.h" +#include "NodeList.h" +#include "Page.h" +#include "RegularExpression.h" +#include "RenderPart.h" +#include "RenderTableCell.h" +#include "RenderTextControl.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "Settings.h" +#include "SystemTime.h" +#include "TextIterator.h" +#include "TextResourceDecoder.h" +#include "XMLNames.h" +#include "bindings/NP_jsobject.h" +#include "bindings/npruntime_impl.h" +#include "bindings/runtime_root.h" +#include "kjs_proxy.h" +#include "kjs_window.h" +#include "visible_units.h" + +#if FRAME_LOADS_USER_STYLESHEET +#include "UserStyleSheetLoader.h" +#endif + +#if ENABLE(SVG) +#include "SVGDocument.h" +#include "SVGDocumentExtensions.h" +#include "SVGNames.h" +#include "XLinkNames.h" +#endif + +using namespace std; + +using KJS::JSLock; + +namespace WebCore { + +using namespace EventNames; +using namespace HTMLNames; + +double Frame::s_currentPaintTimeStamp = 0.0; + +#ifndef NDEBUG +WTFLogChannel LogWebCoreFrameLeaks = { 0x00000000, "", WTFLogChannelOn }; + +struct FrameCounter { + static int count; + ~FrameCounter() + { + if (count) + LOG(WebCoreFrameLeaks, "LEAK: %d Frame\n", count); + } +}; +int FrameCounter::count = 0; +static FrameCounter frameCounter; +#endif + +static inline Frame* parentFromOwnerElement(HTMLFrameOwnerElement* ownerElement) +{ + if (!ownerElement) + return 0; + return ownerElement->document()->frame(); +} + +Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* frameLoaderClient) + : RefCounted<Frame>(0) + , d(new FramePrivate(page, parentFromOwnerElement(ownerElement), this, ownerElement, frameLoaderClient)) +{ + AtomicString::init(); + EventNames::init(); + HTMLNames::init(); + QualifiedName::init(); + MediaFeatureNames::init(); + +#if ENABLE(SVG) + SVGNames::init(); + XLinkNames::init(); +#endif + + XMLNames::init(); + + if (!ownerElement) + page->setMainFrame(this); + else { + // FIXME: It's bad to have a ref() here but not in the !ownerElement case. + // We need to straighten this out. + ref(); + page->incrementFrameCount(); + ownerElement->m_contentFrame = this; + } + +#ifndef NDEBUG + ++FrameCounter::count; +#endif +} + +Frame::~Frame() +{ + setView(0); + loader()->clearRecordedFormValues(); + +#if PLATFORM(MAC) + setBridge(0); +#endif + + loader()->cancelAndClear(); + + // FIXME: We should not be doing all this work inside the destructor + + ASSERT(!d->m_lifeSupportTimer.isActive()); + +#ifndef NDEBUG + --FrameCounter::count; +#endif + + if (d->m_jscript && d->m_jscript->haveGlobalObject()) + static_cast<KJS::Window*>(d->m_jscript->globalObject())->disconnectFrame(); + + disconnectOwnerElement(); + + if (d->m_domWindow) + d->m_domWindow->disconnectFrame(); + + if (d->m_view) { + d->m_view->hide(); + d->m_view->clearFrame(); + } + + ASSERT(!d->m_lifeSupportTimer.isActive()); + +#if FRAME_LOADS_USER_STYLESHEET + delete d->m_userStyleSheetLoader; +#endif + + delete d; + d = 0; +} + +void Frame::init() +{ + d->m_loader->init(); +} + +FrameLoader* Frame::loader() const +{ + return d->m_loader; +} + +FrameView* Frame::view() const +{ + return d->m_view.get(); +} + +void Frame::setView(FrameView* view) +{ + // Detach the document now, so any onUnload handlers get run - if + // we wait until the view is destroyed, then things won't be + // hooked up enough for some JavaScript calls to work. + if (!view && d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { + // FIXME: We don't call willRemove here. Why is that OK? + d->m_doc->detach(); + if (d->m_view) + d->m_view->unscheduleRelayout(); + } + eventHandler()->clear(); + + d->m_view = view; + + // Only one form submission is allowed per view of a part. + // Since this part may be getting reused as a result of being + // pulled from the back/forward cache, reset this flag. + loader()->resetMultipleFormSubmissionProtection(); +} + +KJSProxy *Frame::scriptProxy() +{ + if (!d->m_jscript) + d->m_jscript = new KJSProxy(this); + return d->m_jscript; +} + +Document *Frame::document() const +{ + if (d) + return d->m_doc.get(); + return 0; +} + +void Frame::setDocument(PassRefPtr<Document> newDoc) +{ + if (d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { + // FIXME: We don't call willRemove here. Why is that OK? + d->m_doc->detach(); + } + + d->m_doc = newDoc; + if (d->m_doc && selectionController()->isFocusedAndActive()) + setUseSecureKeyboardEntry(d->m_doc->useSecureKeyboardEntryWhenActive()); + + if (d->m_doc && !d->m_doc->attached()) + d->m_doc->attach(); + + // Remove the cached 'document' property, which is now stale. + if (d->m_jscript) + d->m_jscript->clearDocumentWrapper(); +} + +Settings* Frame::settings() const +{ + return d->m_page ? d->m_page->settings() : 0; +} + +String Frame::selectedText() const +{ + return plainText(selectionController()->toRange().get()); +} + +IntRect Frame::firstRectForRange(Range* range) const +{ + int extraWidthToEndOfLine = 0; + ExceptionCode ec = 0; + ASSERT(range->startContainer(ec)); + ASSERT(range->endContainer(ec)); + IntRect startCaretRect = range->startContainer(ec)->renderer()->caretRect(range->startOffset(ec), DOWNSTREAM, &extraWidthToEndOfLine); + ASSERT(!ec); + IntRect endCaretRect = range->endContainer(ec)->renderer()->caretRect(range->endOffset(ec), UPSTREAM); + ASSERT(!ec); + + if (startCaretRect.y() == endCaretRect.y()) { + // start and end are on the same line + return IntRect(min(startCaretRect.x(), endCaretRect.x()), + startCaretRect.y(), + abs(endCaretRect.x() - startCaretRect.x()), + max(startCaretRect.height(), endCaretRect.height())); + } + + // start and end aren't on the same line, so go from start to the end of its line + return IntRect(startCaretRect.x(), + startCaretRect.y(), + startCaretRect.width() + extraWidthToEndOfLine, + startCaretRect.height()); +} + +SelectionController* Frame::selectionController() const +{ + return &d->m_selectionController; +} + +Editor* Frame::editor() const +{ + return &d->m_editor; +} + +TextGranularity Frame::selectionGranularity() const +{ + return d->m_selectionGranularity; +} + +void Frame::setSelectionGranularity(TextGranularity granularity) const +{ + d->m_selectionGranularity = granularity; +} + +SelectionController* Frame::dragCaretController() const +{ + return d->m_page->dragCaretController(); +} + + +AnimationController* Frame::animationController() const +{ + return &d->m_animationController; +} + +static RegularExpression* createRegExpForLabels(const Vector<String>& labels) +{ + // REVIEW- version of this call in FrameMac.mm caches based on the NSArray ptrs being + // the same across calls. We can't do that. + + static RegularExpression wordRegExp = RegularExpression("\\w"); + String pattern("("); + unsigned int numLabels = labels.size(); + unsigned int i; + for (i = 0; i < numLabels; i++) { + String label = labels[i]; + + bool startsWithWordChar = false; + bool endsWithWordChar = false; + if (label.length() != 0) { + startsWithWordChar = wordRegExp.search(label.substring(0, 1)) >= 0; + endsWithWordChar = wordRegExp.search(label.substring(label.length() - 1, 1)) >= 0; + } + + if (i != 0) + pattern.append("|"); + // Search for word boundaries only if label starts/ends with "word characters". + // If we always searched for word boundaries, this wouldn't work for languages + // such as Japanese. + if (startsWithWordChar) { + pattern.append("\\b"); + } + pattern.append(label); + if (endsWithWordChar) { + pattern.append("\\b"); + } + } + pattern.append(")"); + return new RegularExpression(pattern, false); +} + +String Frame::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell) +{ + RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer()); + + if (cellRenderer && cellRenderer->isTableCell()) { + RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer); + + if (cellAboveRenderer) { + HTMLTableCellElement* aboveCell = + static_cast<HTMLTableCellElement*>(cellAboveRenderer->element()); + + if (aboveCell) { + // search within the above cell we found for a match + for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { + if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) + return nodeString.substring(pos, regExp->matchedLength()); + } + } + } + } + } + // Any reason in practice to search all cells in that are above cell? + return String(); +} + +String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element) +{ + OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels)); + // We stop searching after we've seen this many chars + const unsigned int charsSearchedThreshold = 500; + // This is the absolute max we search. We allow a little more slop than + // charsSearchedThreshold, to make it more likely that we'll search whole nodes. + const unsigned int maxCharsSearched = 600; + // If the starting element is within a table, the cell that contains it + HTMLTableCellElement* startingTableCell = 0; + bool searchedCellAbove = false; + + // walk backwards in the node tree, until another element, or form, or end of tree + int unsigned lengthSearched = 0; + Node* n; + for (n = element->traversePreviousNode(); + n && lengthSearched < charsSearchedThreshold; + n = n->traversePreviousNode()) + { + if (n->hasTagName(formTag) + || (n->isHTMLElement() + && static_cast<HTMLElement*>(n)->isGenericFormElement())) + { + // We hit another form element or the start of the form - bail out + break; + } else if (n->hasTagName(tdTag) && !startingTableCell) { + startingTableCell = static_cast<HTMLTableCellElement*>(n); + } else if (n->hasTagName(trTag) && startingTableCell) { + String result = searchForLabelsAboveCell(regExp.get(), startingTableCell); + if (!result.isEmpty()) + return result; + searchedCellAbove = true; + } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + // add 100 for slop, to make it more likely that we'll search whole nodes + if (lengthSearched + nodeString.length() > maxCharsSearched) + nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) + return nodeString.substring(pos, regExp->matchedLength()); + lengthSearched += nodeString.length(); + } + } + + // If we started in a cell, but bailed because we found the start of the form or the + // previous element, we still might need to search the row above us for a label. + if (startingTableCell && !searchedCellAbove) { + return searchForLabelsAboveCell(regExp.get(), startingTableCell); + } + return String(); +} + +String Frame::matchLabelsAgainstElement(const Vector<String>& labels, Element* element) +{ + String name = element->getAttribute(nameAttr); + // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" + replace(name, RegularExpression("\\d"), " "); + name.replace('_', ' '); + + OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels)); + // Use the largest match we can find in the whole name string + int pos; + int length; + int bestPos = -1; + int bestLength = -1; + int start = 0; + do { + pos = regExp->search(name, start); + if (pos != -1) { + length = regExp->matchedLength(); + if (length >= bestLength) { + bestPos = pos; + bestLength = length; + } + start = pos+1; + } + } while (pos != -1); + + if (bestPos != -1) + return name.substring(bestPos, bestLength); + return String(); +} + +const Selection& Frame::mark() const +{ + return d->m_mark; +} + +void Frame::setMark(const Selection& s) +{ + ASSERT(!s.base().node() || s.base().node()->document() == document()); + ASSERT(!s.extent().node() || s.extent().node()->document() == document()); + ASSERT(!s.start().node() || s.start().node()->document() == document()); + ASSERT(!s.end().node() || s.end().node()->document() == document()); + + d->m_mark = s; +} + +void Frame::notifyRendererOfSelectionChange(bool userTriggered) +{ + RenderObject* renderer = 0; + if (selectionController()->rootEditableElement()) + renderer = selectionController()->rootEditableElement()->shadowAncestorNode()->renderer(); + + // If the current selection is in a textfield or textarea, notify the renderer that the selection has changed + if (renderer && (renderer->isTextArea() || renderer->isTextField())) + static_cast<RenderTextControl*>(renderer)->selectionChanged(userTriggered); +} + +void Frame::invalidateSelection() +{ + selectionController()->setNeedsLayout(); + selectionLayoutChanged(); +} + +void Frame::setCaretVisible(bool flag) +{ + if (d->m_caretVisible == flag) + return; + clearCaretRectIfNeeded(); + d->m_caretVisible = flag; + selectionLayoutChanged(); +} + +void Frame::clearCaretRectIfNeeded() +{ + if (d->m_caretPaint) { + d->m_caretPaint = false; + selectionController()->invalidateCaretRect(); + } +} + +// Helper function that tells whether a particular node is an element that has an entire +// Frame and FrameView, a <frame>, <iframe>, or <object>. +static bool isFrameElement(const Node *n) +{ + if (!n) + return false; + RenderObject *renderer = n->renderer(); + if (!renderer || !renderer->isWidget()) + return false; + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + return widget && widget->isFrameView(); +} + +void Frame::setFocusedNodeIfNeeded() +{ + if (!document() || selectionController()->isNone() || !selectionController()->isFocusedAndActive()) + return; + + Node* target = selectionController()->rootEditableElement(); + if (target) { + RenderObject* renderer = target->renderer(); + + // Walk up the render tree to search for a node to focus. + // Walking up the DOM tree wouldn't work for shadow trees, like those behind the engine-based text fields. + while (renderer) { + // We don't want to set focus on a subframe when selecting in a parent frame, + // so add the !isFrameElement check here. There's probably a better way to make this + // work in the long term, but this is the safest fix at this time. + if (target && target->isMouseFocusable() && !isFrameElement(target)) { + page()->focusController()->setFocusedNode(target, this); + return; + } + renderer = renderer->parent(); + if (renderer) + target = renderer->element(); + } + document()->setFocusedNode(0); + } +} + +void Frame::selectionLayoutChanged() +{ + bool caretRectChanged = selectionController()->recomputeCaretRect(); + + bool shouldBlink = d->m_caretVisible + && selectionController()->isCaret() && selectionController()->isContentEditable(); + + // If the caret moved, stop the blink timer so we can restart with a + // black caret in the new location. + if (caretRectChanged || !shouldBlink) + d->m_caretBlinkTimer.stop(); + + // Start blinking with a black caret. Be sure not to restart if we're + // already blinking in the right location. + if (shouldBlink && !d->m_caretBlinkTimer.isActive()) { + d->m_caretBlinkTimer.startRepeating(theme()->caretBlinkFrequency()); + if (!d->m_caretPaint) { + d->m_caretPaint = true; + selectionController()->invalidateCaretRect(); + } + } + + if (!renderer()) + return; + RenderView* canvas = static_cast<RenderView*>(renderer()); + + Selection selection = selectionController()->selection(); + + if (!selection.isRange()) + canvas->clearSelection(); + else { + // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection. + // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3] + // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected + // and will fill the gap before 'bar'. + Position startPos = selection.visibleStart().deepEquivalent(); + if (startPos.downstream().isCandidate()) + startPos = startPos.downstream(); + Position endPos = selection.visibleEnd().deepEquivalent(); + if (endPos.upstream().isCandidate()) + endPos = endPos.upstream(); + + // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted + // because we don't yet notify the SelectionController of text removal. + if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) { + RenderObject *startRenderer = startPos.node()->renderer(); + RenderObject *endRenderer = endPos.node()->renderer(); + canvas->setSelection(startRenderer, startPos.offset(), endRenderer, endPos.offset()); + } + } +} + +void Frame::caretBlinkTimerFired(Timer<Frame>*) +{ + ASSERT(d->m_caretVisible); + ASSERT(selectionController()->isCaret()); + bool caretPaint = d->m_caretPaint; + if (selectionController()->isCaretBlinkingSuspended() && caretPaint) + return; + d->m_caretPaint = !caretPaint; + selectionController()->invalidateCaretRect(); +} + +void Frame::paintCaret(GraphicsContext* p, const IntRect& rect) const +{ + if (d->m_caretPaint && d->m_caretVisible) + selectionController()->paintCaret(p, rect); +} + +void Frame::paintDragCaret(GraphicsContext* p, const IntRect& rect) const +{ + SelectionController* dragCaretController = d->m_page->dragCaretController(); + ASSERT(dragCaretController->selection().isCaret()); + if (dragCaretController->selection().start().node()->document()->frame() == this) + dragCaretController->paintCaret(p, rect); +} + +int Frame::zoomFactor() const +{ + return d->m_zoomFactor; +} + +void Frame::setZoomFactor(int percent) +{ + if (d->m_zoomFactor == percent) + return; + +#if ENABLE(SVG) + if (d->m_doc && d->m_doc->isSVGDocument()) { + if (!static_cast<SVGDocument*>(d->m_doc.get())->zoomAndPanEnabled()) + return; + d->m_zoomFactor = percent; + if (d->m_doc->renderer()) + d->m_doc->renderer()->repaint(); + return; + } +#endif + d->m_zoomFactor = percent; + if (d->m_doc) + d->m_doc->recalcStyle(Node::Force); + + for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->setZoomFactor(d->m_zoomFactor); + + if (d->m_doc && d->m_doc->renderer() && d->m_doc->renderer()->needsLayout()) + view()->layout(); +} + +void Frame::setPrinting(bool printing, float minPageWidth, float maxPageWidth, bool adjustViewSize) +{ + if (!d->m_doc) + return; + + d->m_doc->setPrinting(printing); + view()->setMediaType(printing ? "print" : "screen"); + d->m_doc->updateStyleSelector(); + forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, adjustViewSize); + + for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->setPrinting(printing, minPageWidth, maxPageWidth, adjustViewSize); +} + +void Frame::setJSStatusBarText(const String& text) +{ + d->m_kjsStatusBarText = text; + if (d->m_page) + d->m_page->chrome()->setStatusbarText(this, d->m_kjsStatusBarText); +} + +void Frame::setJSDefaultStatusBarText(const String& text) +{ + d->m_kjsDefaultStatusBarText = text; + if (d->m_page) + d->m_page->chrome()->setStatusbarText(this, d->m_kjsDefaultStatusBarText); +} + +String Frame::jsStatusBarText() const +{ + return d->m_kjsStatusBarText; +} + +String Frame::jsDefaultStatusBarText() const +{ + return d->m_kjsDefaultStatusBarText; +} + +void Frame::setNeedsReapplyStyles() +{ + if (d->m_needsReapplyStyles) + return; + + d->m_needsReapplyStyles = true; + + // Invalidate the FrameView so that FrameView::layout will get called, + // which calls reapplyStyles. + view()->invalidate(); +} + +bool Frame::needsReapplyStyles() const +{ + return d->m_needsReapplyStyles; +} + +void Frame::reapplyStyles() +{ + d->m_needsReapplyStyles = false; + + // FIXME: This call doesn't really make sense in a method called + // "reapplyStyles". We should probably eventually move it into its own + // method. + if (d->m_doc) + d->m_doc->docLoader()->setAutoLoadImages(d->m_page && d->m_page->settings()->loadsImagesAutomatically()); + +#if FRAME_LOADS_USER_STYLESHEET + const KURL userStyleSheetLocation = d->m_page ? d->m_page->settings()->userStyleSheetLocation() : KURL(); + if (!userStyleSheetLocation.isEmpty()) + setUserStyleSheetLocation(userStyleSheetLocation); + else + setUserStyleSheet(String()); +#endif + + // FIXME: It's not entirely clear why the following is needed. + // The document automatically does this as required when you set the style sheet. + // But we had problems when this code was removed. Details are in + // <http://bugs.webkit.org/show_bug.cgi?id=8079>. + if (d->m_doc) + d->m_doc->updateStyleSelector(); +} + +bool Frame::shouldChangeSelection(const Selection& newSelection) const +{ + return shouldChangeSelection(selectionController()->selection(), newSelection, newSelection.affinity(), false); +} + +bool Frame::shouldChangeSelection(const Selection& oldSelection, const Selection& newSelection, EAffinity affinity, bool stillSelecting) const +{ + return editor()->client()->shouldChangeSelectedRange(oldSelection.toRange().get(), newSelection.toRange().get(), + affinity, stillSelecting); +} + +bool Frame::shouldDeleteSelection(const Selection& selection) const +{ + return editor()->client()->shouldDeleteRange(selection.toRange().get()); +} + +bool Frame::isContentEditable() const +{ + if (d->m_editor.clientIsEditable()) + return true; + if (!d->m_doc) + return false; + return d->m_doc->inDesignMode(); +} + +#if !PLATFORM(MAC) + +void Frame::setUseSecureKeyboardEntry(bool) +{ +} + +#endif + +void Frame::updateSecureKeyboardEntryIfActive() +{ + if (selectionController()->isFocusedAndActive()) + setUseSecureKeyboardEntry(d->m_doc->useSecureKeyboardEntryWhenActive()); +} + +CSSMutableStyleDeclaration *Frame::typingStyle() const +{ + return d->m_typingStyle.get(); +} + +void Frame::setTypingStyle(CSSMutableStyleDeclaration *style) +{ + d->m_typingStyle = style; +} + +void Frame::clearTypingStyle() +{ + d->m_typingStyle = 0; +} + +void Frame::computeAndSetTypingStyle(CSSStyleDeclaration *style, EditAction editingAction) +{ + if (!style || style->length() == 0) { + clearTypingStyle(); + return; + } + + // Calculate the current typing style. + RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable(); + if (typingStyle()) { + typingStyle()->merge(mutableStyle.get()); + mutableStyle = typingStyle(); + } + + Node *node = selectionController()->selection().visibleStart().deepEquivalent().node(); + CSSComputedStyleDeclaration computedStyle(node); + computedStyle.diff(mutableStyle.get()); + + // Handle block styles, substracting these from the typing style. + RefPtr<CSSMutableStyleDeclaration> blockStyle = mutableStyle->copyBlockProperties(); + blockStyle->diff(mutableStyle.get()); + if (document() && blockStyle->length() > 0) + applyCommand(new ApplyStyleCommand(document(), blockStyle.get(), editingAction)); + + // Set the remaining style as the typing style. + d->m_typingStyle = mutableStyle.release(); +} + +String Frame::selectionStartStylePropertyValue(int stylePropertyID) const +{ + Node *nodeToRemove; + RefPtr<CSSStyleDeclaration> selectionStyle = selectionComputedStyle(nodeToRemove); + if (!selectionStyle) + return String(); + + String value = selectionStyle->getPropertyValue(stylePropertyID); + + if (nodeToRemove) { + ExceptionCode ec = 0; + nodeToRemove->remove(ec); + ASSERT(ec == 0); + } + + return value; +} + +CSSComputedStyleDeclaration *Frame::selectionComputedStyle(Node *&nodeToRemove) const +{ + nodeToRemove = 0; + + if (!document()) + return 0; + + if (selectionController()->isNone()) + return 0; + + RefPtr<Range> range(selectionController()->toRange()); + Position pos = range->editingStartPosition(); + + Element *elem = pos.element(); + if (!elem) + return 0; + + RefPtr<Element> styleElement = elem; + ExceptionCode ec = 0; + + if (d->m_typingStyle) { + styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec); + ASSERT(ec == 0); + + styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl(), ec); + ASSERT(ec == 0); + + styleElement->appendChild(document()->createEditingTextNode(""), ec); + ASSERT(ec == 0); + + if (elem->renderer() && elem->renderer()->canHaveChildren()) { + elem->appendChild(styleElement, ec); + } else { + Node *parent = elem->parent(); + Node *next = elem->nextSibling(); + + if (next) { + parent->insertBefore(styleElement, next, ec); + } else { + parent->appendChild(styleElement, ec); + } + } + ASSERT(ec == 0); + + nodeToRemove = styleElement.get(); + } + + return new CSSComputedStyleDeclaration(styleElement); +} + +void Frame::textFieldDidBeginEditing(Element* e) +{ + if (editor()->client()) + editor()->client()->textFieldDidBeginEditing(e); +} + +void Frame::textFieldDidEndEditing(Element* e) +{ + if (editor()->client()) + editor()->client()->textFieldDidEndEditing(e); +} + +void Frame::textDidChangeInTextField(Element* e) +{ + if (editor()->client()) + editor()->client()->textDidChangeInTextField(e); +} + +bool Frame::doTextFieldCommandFromEvent(Element* e, KeyboardEvent* ke) +{ + if (editor()->client()) + return editor()->client()->doTextFieldCommandFromEvent(e, ke); + + return false; +} + +void Frame::textWillBeDeletedInTextField(Element* input) +{ + if (editor()->client()) + editor()->client()->textWillBeDeletedInTextField(input); +} + +void Frame::textDidChangeInTextArea(Element* e) +{ + if (editor()->client()) + editor()->client()->textDidChangeInTextArea(e); +} + +void Frame::applyEditingStyleToBodyElement() const +{ + if (!d->m_doc) + return; + + RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; i++) { + applyEditingStyleToElement(static_cast<Element*>(list->item(i))); + } +} + +void Frame::removeEditingStyleFromBodyElement() const +{ + if (!d->m_doc) + return; + + RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; i++) { + removeEditingStyleFromElement(static_cast<Element*>(list->item(i))); + } +} + +void Frame::applyEditingStyleToElement(Element* element) const +{ + if (!element) + return; + + CSSStyleDeclaration* style = element->style(); + ASSERT(style); + + ExceptionCode ec = 0; + style->setProperty(CSS_PROP_WORD_WRAP, "break-word", false, ec); + ASSERT(ec == 0); + style->setProperty(CSS_PROP__WEBKIT_NBSP_MODE, "space", false, ec); + ASSERT(ec == 0); + style->setProperty(CSS_PROP__WEBKIT_LINE_BREAK, "after-white-space", false, ec); + ASSERT(ec == 0); +} + +void Frame::removeEditingStyleFromElement(Element*) const +{ +} + +#ifndef NDEBUG +static HashSet<Frame*>& keepAliveSet() +{ + static HashSet<Frame*> staticKeepAliveSet; + return staticKeepAliveSet; +} +#endif + +void Frame::keepAlive() +{ + if (d->m_lifeSupportTimer.isActive()) + return; +#ifndef NDEBUG + keepAliveSet().add(this); +#endif + ref(); + d->m_lifeSupportTimer.startOneShot(0); +} + +#ifndef NDEBUG +void Frame::cancelAllKeepAlive() +{ + HashSet<Frame*>::iterator end = keepAliveSet().end(); + for (HashSet<Frame*>::iterator it = keepAliveSet().begin(); it != end; ++it) { + Frame* frame = *it; + frame->d->m_lifeSupportTimer.stop(); + frame->deref(); + } + keepAliveSet().clear(); +} +#endif + +void Frame::lifeSupportTimerFired(Timer<Frame>*) +{ +#ifndef NDEBUG + keepAliveSet().remove(this); +#endif + deref(); +} + +KJS::Bindings::RootObject* Frame::bindingRootObject() +{ + if (!scriptProxy()->isEnabled()) + return 0; + + if (!d->m_bindingRootObject) { + JSLock lock; + d->m_bindingRootObject = KJS::Bindings::RootObject::create(0, scriptProxy()->globalObject()); + } + return d->m_bindingRootObject.get(); +} + +PassRefPtr<KJS::Bindings::RootObject> Frame::createRootObject(void* nativeHandle, KJS::JSGlobalObject* globalObject) +{ + RootObjectMap::iterator it = d->m_rootObjects.find(nativeHandle); + if (it != d->m_rootObjects.end()) + return it->second; + + RefPtr<KJS::Bindings::RootObject> rootObject = KJS::Bindings::RootObject::create(nativeHandle, globalObject); + + d->m_rootObjects.set(nativeHandle, rootObject); + return rootObject.release(); +} + +#if USE(NPOBJECT) +NPObject* Frame::windowScriptNPObject() +{ + if (!d->m_windowScriptNPObject) { + if (scriptProxy()->isEnabled()) { + // JavaScript is enabled, so there is a JavaScript window object. Return an NPObject bound to the window + // object. + KJS::JSLock lock; + KJS::JSObject* win = KJS::Window::retrieveWindow(this); + ASSERT(win); + KJS::Bindings::RootObject* root = bindingRootObject(); + d->m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root); + } else { + // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. + // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. + d->m_windowScriptNPObject = _NPN_CreateNoScriptObject(); + } + } + + return d->m_windowScriptNPObject; +} +#endif + +void Frame::clearScriptProxy() +{ + if (d->m_jscript) + d->m_jscript->clear(); +} + +void Frame::clearDOMWindow() +{ + if (d->m_domWindow) + d->m_domWindow->clear(); +} + +void Frame::cleanupScriptObjectsForPlugin(void* nativeHandle) +{ + RootObjectMap::iterator it = d->m_rootObjects.find(nativeHandle); + + if (it == d->m_rootObjects.end()) + return; + + it->second->invalidate(); + d->m_rootObjects.remove(it); +} + +void Frame::clearScriptObjects() +{ + JSLock lock; + + RootObjectMap::const_iterator end = d->m_rootObjects.end(); + for (RootObjectMap::const_iterator it = d->m_rootObjects.begin(); it != end; ++it) + it->second->invalidate(); + + d->m_rootObjects.clear(); + + if (d->m_bindingRootObject) { + d->m_bindingRootObject->invalidate(); + d->m_bindingRootObject = 0; + } + +#if USE(NPOBJECT) + if (d->m_windowScriptNPObject) { + // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window + // script object properly. + // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. + _NPN_DeallocateObject(d->m_windowScriptNPObject); + d->m_windowScriptNPObject = 0; + } +#endif + + clearPlatformScriptObjects(); +} + +RenderObject *Frame::renderer() const +{ + Document *doc = document(); + return doc ? doc->renderer() : 0; +} + +HTMLFrameOwnerElement* Frame::ownerElement() const +{ + return d->m_ownerElement; +} + +RenderPart* Frame::ownerRenderer() +{ + HTMLFrameOwnerElement* ownerElement = d->m_ownerElement; + if (!ownerElement) + return 0; + return static_cast<RenderPart*>(ownerElement->renderer()); +} + +// returns FloatRect because going through IntRect would truncate any floats +FloatRect Frame::selectionRect(bool clipToVisibleContent) const +{ + RenderView *root = static_cast<RenderView*>(renderer()); + if (!root) + return IntRect(); + + IntRect selectionRect = root->selectionRect(clipToVisibleContent); + return clipToVisibleContent ? intersection(selectionRect, d->m_view->visibleContentRect()) : selectionRect; +} + +void Frame::selectionTextRects(Vector<FloatRect>& rects, bool clipToVisibleContent) const +{ + RenderView *root = static_cast<RenderView*>(renderer()); + if (!root) + return; + + RefPtr<Range> selectedRange = selectionController()->toRange(); + + Vector<IntRect> intRects; + selectedRange->addLineBoxRects(intRects, true); + + unsigned size = intRects.size(); + FloatRect visibleContentRect = d->m_view->visibleContentRect(); + for (unsigned i = 0; i < size; ++i) + if (clipToVisibleContent) + rects.append(intersection(intRects[i], visibleContentRect)); + else + rects.append(intRects[i]); +} + + +bool Frame::isFrameSet() const +{ + Document* document = d->m_doc.get(); + if (!document || !document->isHTMLDocument()) + return false; + Node *body = static_cast<HTMLDocument*>(document)->body(); + return body && body->renderer() && body->hasTagName(framesetTag); +} + +// Scans logically forward from "start", including any child frames +static HTMLFormElement *scanForForm(Node *start) +{ + Node *n; + for (n = start; n; n = n->traverseNextNode()) { + if (n->hasTagName(formTag)) + return static_cast<HTMLFormElement*>(n); + else if (n->isHTMLElement() && static_cast<HTMLElement*>(n)->isGenericFormElement()) + return static_cast<HTMLGenericFormElement*>(n)->form(); + else if (n->hasTagName(frameTag) || n->hasTagName(iframeTag)) { + Node *childDoc = static_cast<HTMLFrameElementBase*>(n)->contentDocument(); + if (HTMLFormElement *frameResult = scanForForm(childDoc)) + return frameResult; + } + } + return 0; +} + +// We look for either the form containing the current focus, or for one immediately after it +HTMLFormElement *Frame::currentForm() const +{ + // start looking either at the active (first responder) node, or where the selection is + Node *start = d->m_doc ? d->m_doc->focusedNode() : 0; + if (!start) + start = selectionController()->start().node(); + + // try walking up the node tree to find a form element + Node *n; + for (n = start; n; n = n->parentNode()) { + if (n->hasTagName(formTag)) + return static_cast<HTMLFormElement*>(n); + else if (n->isHTMLElement() + && static_cast<HTMLElement*>(n)->isGenericFormElement()) + return static_cast<HTMLGenericFormElement*>(n)->form(); + } + + // try walking forward in the node tree to find a form element + return start ? scanForForm(start) : 0; +} + +// FIXME: should this go in SelectionController? +void Frame::revealSelection(const RenderLayer::ScrollAlignment& alignment) const +{ + IntRect rect; + + switch (selectionController()->state()) { + case Selection::NONE: + return; + + case Selection::CARET: + rect = selectionController()->caretRect(); + break; + + case Selection::RANGE: + rect = enclosingIntRect(selectionRect(false)); + break; + } + + Position start = selectionController()->start(); + + ASSERT(start.node()); + if (start.node() && start.node()->renderer()) { + // FIXME: This code only handles scrolling the startContainer's layer, but + // the selection rect could intersect more than just that. + // See <rdar://problem/4799899>. + if (RenderLayer *layer = start.node()->renderer()->enclosingLayer()) + layer->scrollRectToVisible(rect, alignment, alignment); + } +} + +void Frame::revealCaret(const RenderLayer::ScrollAlignment& alignment) const +{ + if (selectionController()->isNone()) + return; + + Position extent = selectionController()->extent(); + if (extent.node() && extent.node()->renderer()) { + IntRect extentRect = VisiblePosition(extent).caretRect(); + RenderLayer* layer = extent.node()->renderer()->enclosingLayer(); + if (layer) + layer->scrollRectToVisible(extentRect, alignment, alignment); + } +} + +// FIXME: why is this here instead of on the FrameView? +void Frame::paint(GraphicsContext* p, const IntRect& rect) +{ +#ifndef NDEBUG + bool fillWithRed; + if (!document() || document()->printing()) + fillWithRed = false; // Printing, don't fill with red (can't remember why). + else if (document()->ownerElement()) + fillWithRed = false; // Subframe, don't fill with red. + else if (view() && view()->isTransparent()) + fillWithRed = false; // Transparent, don't fill with red. + else if (d->m_paintRestriction == PaintRestrictionSelectionOnly || d->m_paintRestriction == PaintRestrictionSelectionOnlyBlackText) + fillWithRed = false; // Selections are transparent, don't fill with red. + else if (d->m_elementToDraw) + fillWithRed = false; // Element images are transparent, don't fill with red. + else + fillWithRed = true; + + if (fillWithRed) + p->fillRect(rect, Color(0xFF, 0, 0)); +#endif + + bool isTopLevelPainter = !s_currentPaintTimeStamp; + if (isTopLevelPainter) + s_currentPaintTimeStamp = currentTime(); + + if (renderer()) { + ASSERT(d->m_view && !d->m_view->needsLayout()); + ASSERT(!d->m_isPainting); + + d->m_isPainting = true; + + // d->m_elementToDraw is used to draw only one element + RenderObject *eltRenderer = d->m_elementToDraw ? d->m_elementToDraw->renderer() : 0; + if (d->m_paintRestriction == PaintRestrictionNone) + renderer()->document()->invalidateRenderedRectsForMarkersInRect(rect); + renderer()->layer()->paint(p, rect, d->m_paintRestriction, eltRenderer); + + d->m_isPainting = false; + + // Regions may have changed as a result of the visibility/z-index of element changing. + if (renderer()->document()->dashboardRegionsDirty()) + renderer()->view()->frameView()->updateDashboardRegions(); + } else + LOG_ERROR("called Frame::paint with nil renderer"); + + if (isTopLevelPainter) + s_currentPaintTimeStamp = 0; +} + +void Frame::setPaintRestriction(PaintRestriction pr) +{ + d->m_paintRestriction = pr; +} + +bool Frame::isPainting() const +{ + return d->m_isPainting; +} + +void Frame::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit) +{ + RenderView *root = static_cast<RenderView*>(document()->renderer()); + if (root) { + // Use a context with painting disabled. + GraphicsContext context((PlatformGraphicsContext*)0); + root->setTruncatedAt((int)floorf(oldBottom)); + IntRect dirtyRect(0, (int)floorf(oldTop), root->docWidth(), (int)ceilf(oldBottom - oldTop)); + root->layer()->paint(&context, dirtyRect); + *newBottom = root->bestTruncatedAt(); + if (*newBottom == 0) + *newBottom = oldBottom; + } else + *newBottom = oldBottom; +} + +Frame* Frame::frameForWidget(const Widget* widget) +{ + ASSERT_ARG(widget, widget); + + if (RenderWidget* renderer = RenderWidget::find(widget)) + if (Node* node = renderer->node()) + return node->document()->frame(); + + // Assume all widgets are either a FrameView or owned by a RenderWidget. + // FIXME: That assumption is not right for scroll bars! + ASSERT(widget->isFrameView()); + return static_cast<const FrameView*>(widget)->frame(); +} + +void Frame::forceLayout(bool allowSubtree) +{ + FrameView *v = d->m_view.get(); + if (v) { + v->layout(allowSubtree); + // We cannot unschedule a pending relayout, since the force can be called with + // a tiny rectangle from a drawRect update. By unscheduling we in effect + // "validate" and stop the necessary full repaint from occurring. Basically any basic + // append/remove DHTML is broken by this call. For now, I have removed the optimization + // until we have a better invalidation stategy. -dwh + //v->unscheduleRelayout(); + } +} + +void Frame::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth, bool adjustViewSize) +{ + // Dumping externalRepresentation(m_frame->renderer()).ascii() is a good trick to see + // the state of things before and after the layout + RenderView *root = static_cast<RenderView*>(document()->renderer()); + if (root) { + // This magic is basically copied from khtmlview::print + int pageW = (int)ceilf(minPageWidth); + root->setWidth(pageW); + root->setNeedsLayoutAndPrefWidthsRecalc(); + forceLayout(); + + // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the + // maximum page width, we will lay out to the maximum page width and clip extra content. + // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping + // implementation should not do this! + int rightmostPos = root->rightmostPosition(); + if (rightmostPos > minPageWidth) { + pageW = min(rightmostPos, (int)ceilf(maxPageWidth)); + root->setWidth(pageW); + root->setNeedsLayoutAndPrefWidthsRecalc(); + forceLayout(); + } + } + + if (adjustViewSize && view()) + view()->adjustViewSize(); +} + +void Frame::sendResizeEvent() +{ + if (Document* doc = document()) + doc->dispatchWindowEvent(EventNames::resizeEvent, false, false); +} + +void Frame::sendScrollEvent() +{ + FrameView* v = d->m_view.get(); + if (!v) + return; + v->setWasScrolledByUser(true); + Document* doc = document(); + if (!doc) + return; + doc->dispatchHTMLEvent(scrollEvent, true, false); +} + +void Frame::clearTimers(FrameView *view) +{ + if (view) { + view->unscheduleRelayout(); + if (view->frame()) { + Document* document = view->frame()->document(); + if (document && document->renderer() && document->renderer()->hasLayer()) + document->renderer()->layer()->suspendMarquees(); + view->frame()->animationController()->suspendAnimations(); + } + } +} + +void Frame::clearTimers() +{ + clearTimers(d->m_view.get()); +} + +RenderStyle *Frame::styleForSelectionStart(Node *&nodeToRemove) const +{ + nodeToRemove = 0; + + if (!document()) + return 0; + if (selectionController()->isNone()) + return 0; + + Position pos = selectionController()->selection().visibleStart().deepEquivalent(); + if (!pos.isCandidate()) + return 0; + Node *node = pos.node(); + if (!node) + return 0; + + if (!d->m_typingStyle) + return node->renderer()->style(); + + ExceptionCode ec = 0; + RefPtr<Element> styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec); + ASSERT(ec == 0); + + String styleText = d->m_typingStyle->cssText() + " display: inline"; + styleElement->setAttribute(styleAttr, styleText.impl(), ec); + ASSERT(ec == 0); + + styleElement->appendChild(document()->createEditingTextNode(""), ec); + ASSERT(ec == 0); + + node->parentNode()->appendChild(styleElement, ec); + ASSERT(ec == 0); + + nodeToRemove = styleElement.get(); + return styleElement->renderer() ? styleElement->renderer()->style() : 0; +} + +void Frame::setSelectionFromNone() +{ + // Put a caret inside the body if the entire frame is editable (either the + // entire WebView is editable or designMode is on for this document). + Document *doc = document(); + if (!doc || !selectionController()->isNone() || !isContentEditable()) + return; + + Node* node = doc->documentElement(); + while (node && !node->hasTagName(bodyTag)) + node = node->traverseNextNode(); + if (node) + selectionController()->setSelection(Selection(Position(node, 0), DOWNSTREAM)); +} + +bool Frame::inViewSourceMode() const +{ + return d->m_inViewSourceMode; +} + +void Frame::setInViewSourceMode(bool mode) const +{ + d->m_inViewSourceMode = mode; +} + +UChar Frame::backslashAsCurrencySymbol() const +{ + Document *doc = document(); + if (!doc) + return '\\'; + TextResourceDecoder *decoder = doc->decoder(); + if (!decoder) + return '\\'; + + return decoder->encoding().backslashAsCurrencySymbol(); +} + +static bool isInShadowTree(Node* node) +{ + for (Node* n = node; n; n = n->parentNode()) + if (n->isShadowNode()) + return true; + return false; +} + +// Searches from the beginning of the document if nothing is selected. +bool Frame::findString(const String& target, bool forward, bool caseFlag, bool wrapFlag, bool startInSelection) +{ + if (target.isEmpty() || !document()) + return false; + + // Start from an edge of the selection, if there's a selection that's not in shadow content. Which edge + // is used depends on whether we're searching forward or backward, and whether startInSelection is set. + RefPtr<Range> searchRange(rangeOfContents(document())); + Selection selection(selectionController()->selection()); + Node* selectionBaseNode = selection.base().node(); + + // FIXME 3099526: We don't search in the shadow trees (e.g. text fields and textareas), though we'd like to + // someday. If we don't explicitly skip them here, we'll miss hits in the regular content. + bool selectionIsInMainContent = selectionBaseNode && !isInShadowTree(selectionBaseNode); + + if (selectionIsInMainContent) { + if (forward) + setStart(searchRange.get(), startInSelection ? selection.visibleStart() : selection.visibleEnd()); + else + setEnd(searchRange.get(), startInSelection ? selection.visibleEnd() : selection.visibleStart()); + } + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, forward, caseFlag)); + // If we started in the selection and the found range exactly matches the existing selection, find again. + // Build a selection with the found range to remove collapsed whitespace. + // Compare ranges instead of selection objects to ignore the way that the current selection was made. + if (startInSelection && selectionIsInMainContent && *Selection(resultRange.get()).toRange() == *selection.toRange()) { + searchRange = rangeOfContents(document()); + if (forward) + setStart(searchRange.get(), selection.visibleEnd()); + else + setEnd(searchRange.get(), selection.visibleStart()); + resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); + } + + int exception = 0; + + // If we didn't find anything and we're wrapping, search again in the entire document (this will + // redundantly re-search the area already searched in some cases). + if (resultRange->collapsed(exception) && wrapFlag) { + searchRange = rangeOfContents(document()); + resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); + // We used to return false here if we ended up with the same range that we started with + // (e.g., the selection was already the only instance of this text). But we decided that + // this should be a success case instead, so we'll just fall through in that case. + } + + if (resultRange->collapsed(exception)) + return false; + + selectionController()->setSelection(Selection(resultRange.get(), DOWNSTREAM)); + revealSelection(); + return true; +} + +unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag, unsigned limit) +{ + if (target.isEmpty() || !document()) + return 0; + + RefPtr<Range> searchRange(rangeOfContents(document())); + + int exception = 0; + unsigned matchCount = 0; + do { + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, caseFlag)); + if (resultRange->collapsed(exception)) + break; + + // A non-collapsed result range can in some funky whitespace cases still not + // advance the range's start position (4509328). Break to avoid infinite loop. + VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM); + if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) + break; + + ++matchCount; + + document()->addMarker(resultRange.get(), DocumentMarker::TextMatch); + + // Stop looking if we hit the specified limit. A limit of 0 means no limit. + if (limit > 0 && matchCount >= limit) + break; + + setStart(searchRange.get(), newStart); + } while (true); + + // Do a "fake" paint in order to execute the code that computes the rendered rect for + // each text match. + Document* doc = document(); + if (doc && d->m_view && renderer()) { + doc->updateLayout(); // Ensure layout is up to date. + IntRect visibleRect(enclosingIntRect(d->m_view->visibleContentRect())); + GraphicsContext context((PlatformGraphicsContext*)0); + context.setPaintingDisabled(true); + paint(&context, visibleRect); + } + + return matchCount; +} + +bool Frame::markedTextMatchesAreHighlighted() const +{ + return d->m_highlightTextMatches; +} + +void Frame::setMarkedTextMatchesAreHighlighted(bool flag) +{ + if (flag == d->m_highlightTextMatches || !document()) + return; + + d->m_highlightTextMatches = flag; + document()->repaintMarkers(DocumentMarker::TextMatch); +} + +FrameTree* Frame::tree() const +{ + return &d->m_treeNode; +} + +DOMWindow* Frame::domWindow() const +{ + if (!d->m_domWindow) + d->m_domWindow = DOMWindow::create(const_cast<Frame*>(this)); + + return d->m_domWindow.get(); +} + +Page* Frame::page() const +{ + return d->m_page; +} + +EventHandler* Frame::eventHandler() const +{ + return &d->m_eventHandler; +} + +void Frame::pageDestroyed() +{ + if (Frame* parent = tree()->parent()) + parent->loader()->checkLoadComplete(); + + if (d->m_page && d->m_page->focusController()->focusedFrame() == this) + d->m_page->focusController()->setFocusedFrame(0); + + // This will stop any JS timers + if (d->m_jscript && d->m_jscript->haveGlobalObject()) + if (KJS::Window* w = KJS::Window::retrieveWindow(this)) + w->disconnectFrame(); + + clearScriptObjects(); + + d->m_page = 0; +} + +void Frame::disconnectOwnerElement() +{ + if (d->m_ownerElement) { + d->m_ownerElement->m_contentFrame = 0; + if (d->m_page) + d->m_page->decrementFrameCount(); + } + d->m_ownerElement = 0; +} + +String Frame::documentTypeString() const +{ + if (Document *doc = document()) + if (DocumentType *doctype = doc->doctype()) + return doctype->toString(); + + return String(); +} + +bool Frame::prohibitsScrolling() const +{ + return d->m_prohibitsScrolling; +} + +void Frame::setProhibitsScrolling(bool prohibit) +{ + d->m_prohibitsScrolling = prohibit; +} + +void Frame::focusWindow() +{ + if (!page()) + return; + + // If we're a top level window, bring the window to the front. + if (!tree()->parent()) + page()->chrome()->focus(); + + eventHandler()->focusDocumentView(); +} + +void Frame::unfocusWindow() +{ + if (!page()) + return; + + // If we're a top level window, deactivate the window. + if (!tree()->parent()) + page()->chrome()->unfocus(); +} + +bool Frame::shouldClose() +{ + Chrome* chrome = page() ? page()->chrome() : 0; + if (!chrome || !chrome->canRunBeforeUnloadConfirmPanel()) + return true; + + RefPtr<Document> doc = document(); + if (!doc) + return true; + HTMLElement* body = doc->body(); + if (!body) + return true; + + RefPtr<BeforeUnloadEvent> beforeUnloadEvent = new BeforeUnloadEvent; + beforeUnloadEvent->setTarget(doc); + doc->handleWindowEvent(beforeUnloadEvent.get(), false); + + if (!beforeUnloadEvent->defaultPrevented() && doc) + doc->defaultEventHandler(beforeUnloadEvent.get()); + if (beforeUnloadEvent->result().isNull()) + return true; + + String text = beforeUnloadEvent->result(); + text.replace('\\', backslashAsCurrencySymbol()); + + return chrome->runBeforeUnloadConfirmPanel(text, this); +} + +void Frame::scheduleClose() +{ + if (!shouldClose()) + return; + + Chrome* chrome = page() ? page()->chrome() : 0; + if (chrome) + chrome->closeWindowSoon(); +} + +void Frame::respondToChangedSelection(const Selection& oldSelection, bool closeTyping) +{ + if (document()) { + bool isContinuousSpellCheckingEnabled = editor()->isContinuousSpellCheckingEnabled(); + bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && editor()->isGrammarCheckingEnabled(); + if (isContinuousSpellCheckingEnabled) { + Selection newAdjacentWords; + Selection newSelectedSentence; + if (selectionController()->selection().isContentEditable()) { + VisiblePosition newStart(selectionController()->selection().visibleStart()); + newAdjacentWords = Selection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary)); + if (isContinuousGrammarCheckingEnabled) + newSelectedSentence = Selection(startOfSentence(newStart), endOfSentence(newStart)); + } + + // When typing we check spelling elsewhere, so don't redo it here. + // If this is a change in selection resulting from a delete operation, + // oldSelection may no longer be in the document. + if (closeTyping && oldSelection.isContentEditable() && oldSelection.start().node() && oldSelection.start().node()->inDocument()) { + VisiblePosition oldStart(oldSelection.visibleStart()); + Selection oldAdjacentWords = Selection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); + if (oldAdjacentWords != newAdjacentWords) { + editor()->markMisspellings(oldAdjacentWords); + if (isContinuousGrammarCheckingEnabled) { + Selection oldSelectedSentence = Selection(startOfSentence(oldStart), endOfSentence(oldStart)); + if (oldSelectedSentence != newSelectedSentence) + editor()->markBadGrammar(oldSelectedSentence); + } + } + } + + // This only erases markers that are in the first unit (word or sentence) of the selection. + // Perhaps peculiar, but it matches AppKit. + if (RefPtr<Range> wordRange = newAdjacentWords.toRange()) + document()->removeMarkers(wordRange.get(), DocumentMarker::Spelling); + if (RefPtr<Range> sentenceRange = newSelectedSentence.toRange()) + document()->removeMarkers(sentenceRange.get(), DocumentMarker::Grammar); + } + + // When continuous spell checking is off, existing markers disappear after the selection changes. + if (!isContinuousSpellCheckingEnabled) + document()->removeMarkers(DocumentMarker::Spelling); + if (!isContinuousGrammarCheckingEnabled) + document()->removeMarkers(DocumentMarker::Grammar); + } + + editor()->respondToChangedSelection(oldSelection); +} + +VisiblePosition Frame::visiblePositionForPoint(const IntPoint& framePoint) +{ + HitTestResult result = eventHandler()->hitTestResultAtPoint(framePoint, true); + Node* node = result.innerNode(); + if (!node) + return VisiblePosition(); + RenderObject* renderer = node->renderer(); + if (!renderer) + return VisiblePosition(); + VisiblePosition visiblePos = renderer->positionForCoordinates(result.localPoint().x(), result.localPoint().y()); + if (visiblePos.isNull()) + visiblePos = VisiblePosition(Position(node, 0)); + return visiblePos; +} + +Document* Frame::documentAtPoint(const IntPoint& point) +{ + if (!view()) + return 0; + + IntPoint pt = view()->windowToContents(point); + HitTestResult result = HitTestResult(pt); + + if (renderer()) + result = eventHandler()->hitTestResultAtPoint(pt, false); + return result.innerNode() ? result.innerNode()->document() : 0; +} + +FramePrivate::FramePrivate(Page* page, Frame* parent, Frame* thisFrame, HTMLFrameOwnerElement* ownerElement, + FrameLoaderClient* frameLoaderClient) + : m_page(page) + , m_treeNode(thisFrame, parent) + , m_ownerElement(ownerElement) + , m_jscript(0) + , m_zoomFactor(parent ? parent->d->m_zoomFactor : 100) + , m_selectionGranularity(CharacterGranularity) + , m_selectionController(thisFrame) + , m_caretBlinkTimer(thisFrame, &Frame::caretBlinkTimerFired) + , m_editor(thisFrame) + , m_eventHandler(thisFrame) + , m_animationController(thisFrame) + , m_caretVisible(false) + , m_caretPaint(true) + , m_isPainting(false) + , m_lifeSupportTimer(thisFrame, &Frame::lifeSupportTimerFired) + , m_loader(new FrameLoader(thisFrame, frameLoaderClient)) + , m_paintRestriction(PaintRestrictionNone) + , m_highlightTextMatches(false) + , m_inViewSourceMode(false) + , frameCount(0) + , m_prohibitsScrolling(false) + , m_needsReapplyStyles(false) + , m_windowScriptNPObject(0) +#if FRAME_LOADS_USER_STYLESHEET + , m_userStyleSheetLoader(0) +#endif +#if PLATFORM(MAC) + , m_windowScriptObject(nil) + , m_bridge(nil) +#endif +{ +} + +FramePrivate::~FramePrivate() +{ + delete m_jscript; + delete m_loader; +} + +} // namespace WebCore diff --git a/WebCore/page/Frame.h b/WebCore/page/Frame.h new file mode 100644 index 0000000..24c9903 --- /dev/null +++ b/WebCore/page/Frame.h @@ -0,0 +1,391 @@ +// -*- c-basic-offset: 4 -*- +/* + * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + * 1999-2001 Lars Knoll <knoll@kde.org> + * 1999-2001 Antti Koivisto <koivisto@kde.org> + * 2000-2001 Simon Hausmann <hausmann@kde.org> + * 2000-2001 Dirk Mueller <mueller@kde.org> + * 2000 Stefan Schimanski <1Stein@gmx.de> + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Trolltech ASA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef Frame_h +#define Frame_h + +#include "Color.h" +#include "EditAction.h" +#include "DragImage.h" +#include "RenderLayer.h" +#include "TextGranularity.h" +#include "VisiblePosition.h" +#include <wtf/unicode/Unicode.h> +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +struct NPObject; + +namespace KJS { + + class Interpreter; + class JSGlobalObject; + + namespace Bindings { + class Instance; + class RootObject; + } + +} + +#if PLATFORM(MAC) +#ifdef __OBJC__ +@class NSArray; +@class NSDictionary; +@class NSMenu; +@class NSMutableDictionary; +@class NSString; +@class WebCoreFrameBridge; +@class WebScriptObject; +#else +class NSArray; +class NSDictionary; +class NSMenu; +class NSMutableDictionary; +class NSString; +class WebCoreFrameBridge; +class WebScriptObject; +typedef int NSWritingDirection; +#endif +#endif + +namespace WebCore { + +class AnimationController; +class CSSComputedStyleDeclaration; +class CSSMutableStyleDeclaration; +class CSSStyleDeclaration; +class DOMWindow; +class Document; +class Editor; +class Element; +class EventHandler; +class FloatRect; +class FrameLoader; +class FrameLoaderClient; +class HTMLFrameOwnerElement; +class HTMLTableCellElement; +class FramePrivate; +class FrameTree; +class FrameView; +class GraphicsContext; +class HTMLFormElement; +class IntRect; +class KJSProxy; +class KURL; +class Node; +class Page; +class Range; +class RegularExpression; +class RenderPart; +class Selection; +class SelectionController; +class Settings; +class Widget; + +struct FrameLoadRequest; + +template <typename T> class Timer; + +class Frame : public RefCounted<Frame> { +public: + Frame(Page*, HTMLFrameOwnerElement*, FrameLoaderClient*); + virtual void setView(FrameView*); + virtual ~Frame(); + + void init(); + +#if PLATFORM(MAC) + void setBridge(WebCoreFrameBridge*); + WebCoreFrameBridge* bridge() const; +#endif + + Page* page() const; + HTMLFrameOwnerElement* ownerElement() const; + + void pageDestroyed(); + void disconnectOwnerElement(); + + Document* document() const; + FrameView* view() const; + + DOMWindow* domWindow() const; + Editor* editor() const; + EventHandler* eventHandler() const; + FrameLoader* loader() const; + SelectionController* selectionController() const; + FrameTree* tree() const; + AnimationController* animationController() const; + + // FIXME: Rename to contentRenderer and change type to RenderView. + RenderObject* renderer() const; // root renderer for the document contained in this frame + RenderPart* ownerRenderer(); // renderer for the element that contains this frame + + friend class FramePrivate; + +private: + FramePrivate* d; + +// === undecided, would like to consider moving to another class + +public: + static Frame* frameForWidget(const Widget*); + + Settings* settings() const; // can be NULL + +#if FRAME_LOADS_USER_STYLESHEET + void setUserStyleSheetLocation(const KURL&); + void setUserStyleSheet(const String& styleSheetData); +#endif + + void setPrinting(bool printing, float minPageWidth, float maxPageWidth, bool adjustViewSize); + + bool inViewSourceMode() const; + void setInViewSourceMode(bool = true) const; + + void keepAlive(); // Used to keep the frame alive when running a script that might destroy it. +#ifndef NDEBUG + static void cancelAllKeepAlive(); +#endif + + KJS::Bindings::Instance* createScriptInstanceForWidget(Widget*); + KJS::Bindings::RootObject* bindingRootObject(); + + PassRefPtr<KJS::Bindings::RootObject> createRootObject(void* nativeHandle, KJS::JSGlobalObject*); + +#if PLATFORM(MAC) + WebScriptObject* windowScriptObject(); +#endif + +#if USE(NPOBJECT) + NPObject* windowScriptNPObject(); +#endif + + void setDocument(PassRefPtr<Document>); + + KJSProxy* scriptProxy(); + + void clearTimers(); + static void clearTimers(FrameView*); + + // Convenience, to avoid repeating the code to dig down to get this. + UChar backslashAsCurrencySymbol() const; + + void setNeedsReapplyStyles(); + bool needsReapplyStyles() const; + void reapplyStyles(); + + String documentTypeString() const; + + void dashboardRegionsChanged(); + + void clearScriptProxy(); + void clearDOMWindow(); + + void clearScriptObjects(); + void cleanupScriptObjectsForPlugin(void*); + +private: + void clearPlatformScriptObjects(); + + void lifeSupportTimerFired(Timer<Frame>*); + +// === to be moved into Document + +public: + bool isFrameSet() const; + +// === to be moved into EventHandler + +public: + void sendResizeEvent(); + void sendScrollEvent(); + +// === to be moved into FrameView + +public: + void paint(GraphicsContext*, const IntRect&); + void setPaintRestriction(PaintRestriction); + bool isPainting() const; + + static double currentPaintTimeStamp() { return s_currentPaintTimeStamp; } // returns 0 if not painting + + void forceLayout(bool allowSubtree = false); + void forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth, bool adjustViewSize); + + void adjustPageHeight(float* newBottom, float oldTop, float oldBottom, float bottomLimit); + + void setZoomFactor(int percent); + int zoomFactor() const; // FIXME: This is a multiplier for text size only; needs a better name. + + bool prohibitsScrolling() const; + void setProhibitsScrolling(const bool); + +private: + static double s_currentPaintTimeStamp; // used for detecting decoded resource thrash in the cache + +// === to be moved into Chrome + +public: + void focusWindow(); + void unfocusWindow(); + bool shouldClose(); + void scheduleClose(); + + void setJSStatusBarText(const String&); + void setJSDefaultStatusBarText(const String&); + String jsStatusBarText() const; + String jsDefaultStatusBarText() const; + +// === to be moved into Editor + +public: + String selectedText() const; + bool findString(const String&, bool forward, bool caseFlag, bool wrapFlag, bool startInSelection); + + const Selection& mark() const; // Mark, to be used as emacs uses it. + void setMark(const Selection&); + + void computeAndSetTypingStyle(CSSStyleDeclaration* , EditAction = EditActionUnspecified); + String selectionStartStylePropertyValue(int stylePropertyID) const; + void applyEditingStyleToBodyElement() const; + void removeEditingStyleFromBodyElement() const; + void applyEditingStyleToElement(Element*) const; + void removeEditingStyleFromElement(Element*) const; + + IntRect firstRectForRange(Range*) const; + + void respondToChangedSelection(const Selection& oldSelection, bool closeTyping); + bool shouldChangeSelection(const Selection& oldSelection, const Selection& newSelection, EAffinity, bool stillSelecting) const; + + RenderStyle* styleForSelectionStart(Node*& nodeToRemove) const; + + unsigned markAllMatchesForText(const String&, bool caseFlag, unsigned limit); + bool markedTextMatchesAreHighlighted() const; + void setMarkedTextMatchesAreHighlighted(bool flag); + + CSSComputedStyleDeclaration* selectionComputedStyle(Node*& nodeToRemove) const; + + void textFieldDidBeginEditing(Element*); + void textFieldDidEndEditing(Element*); + void textDidChangeInTextField(Element*); + bool doTextFieldCommandFromEvent(Element*, KeyboardEvent*); + void textWillBeDeletedInTextField(Element* input); + void textDidChangeInTextArea(Element*); + + DragImageRef dragImageForSelection(); + +// === to be moved into SelectionController + +public: + TextGranularity selectionGranularity() const; + void setSelectionGranularity(TextGranularity) const; + + bool shouldChangeSelection(const Selection&) const; + bool shouldDeleteSelection(const Selection&) const; + void clearCaretRectIfNeeded(); + void setFocusedNodeIfNeeded(); + void selectionLayoutChanged(); + void notifyRendererOfSelectionChange(bool userTriggered); + + void invalidateSelection(); + + void setCaretVisible(bool = true); + void paintCaret(GraphicsContext*, const IntRect&) const; + void paintDragCaret(GraphicsContext*, const IntRect&) const; + + bool isContentEditable() const; // if true, everything in frame is editable + + void updateSecureKeyboardEntryIfActive(); + + CSSMutableStyleDeclaration* typingStyle() const; + void setTypingStyle(CSSMutableStyleDeclaration*); + void clearTypingStyle(); + + FloatRect selectionRect(bool clipToVisibleContent = true) const; + void selectionTextRects(Vector<FloatRect>&, bool clipToVisibleContent = true) const; + + HTMLFormElement* currentForm() const; + + void revealSelection(const RenderLayer::ScrollAlignment& = RenderLayer::gAlignCenterIfNeeded) const; + void revealCaret(const RenderLayer::ScrollAlignment& = RenderLayer::gAlignCenterIfNeeded) const; + void setSelectionFromNone(); + + void setUseSecureKeyboardEntry(bool); + +private: + void caretBlinkTimerFired(Timer<Frame>*); + +public: + SelectionController* dragCaretController() const; + + String searchForLabelsAboveCell(RegularExpression*, HTMLTableCellElement*); + String searchForLabelsBeforeElement(const Vector<String>& labels, Element*); + String matchLabelsAgainstElement(const Vector<String>& labels, Element*); + + VisiblePosition visiblePositionForPoint(const IntPoint& framePoint); + Document* documentAtPoint(const IntPoint& windowPoint); + +#if PLATFORM(MAC) + +// === undecided, would like to consider moving to another class + +public: + NSString* searchForNSLabelsAboveCell(RegularExpression*, HTMLTableCellElement*); + NSString* searchForLabelsBeforeElement(NSArray* labels, Element*); + NSString* matchLabelsAgainstElement(NSArray* labels, Element*); + + NSMutableDictionary* dashboardRegionsDictionary(); + + void willPopupMenu(NSMenu*); + + NSImage* selectionImage(bool forceBlackText = false) const; + NSImage* snapshotDragImage(Node*, NSRect* imageRect, NSRect* elementRect) const; + +private: + NSImage* imageFromRect(NSRect) const; + +// === to be moved into Chrome + +public: + FloatRect customHighlightLineRect(const AtomicString& type, const FloatRect& lineRect, Node*); + void paintCustomHighlight(const AtomicString& type, const FloatRect& boxRect, const FloatRect& lineRect, bool text, bool line, Node*); + +// === to be moved into Editor + +public: + NSDictionary* fontAttributesForSelectionStart() const; + NSWritingDirection baseWritingDirectionForSelectionStart() const; + void issuePasteCommand(); + +#endif + +}; + +} // namespace WebCore + +#endif // Frame_h diff --git a/WebCore/page/FrameLoadRequest.h b/WebCore/page/FrameLoadRequest.h new file mode 100644 index 0000000..c916a05 --- /dev/null +++ b/WebCore/page/FrameLoadRequest.h @@ -0,0 +1,74 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FrameLoadRequest_h +#define FrameLoadRequest_h + +#include "ResourceRequest.h" + +namespace WebCore { + + struct FrameLoadRequest { + public: + FrameLoadRequest() + : m_lockHistory(false) + { + } + + FrameLoadRequest(const ResourceRequest& resourceRequest) + : m_resourceRequest(resourceRequest) + , m_lockHistory(false) + { + } + + FrameLoadRequest(const ResourceRequest& resourceRequest, const String& frameName) + : m_resourceRequest(resourceRequest) + , m_frameName(frameName) + , m_lockHistory(false) + { + } + + bool isEmpty() const { return m_resourceRequest.isEmpty(); } + + ResourceRequest& resourceRequest() { return m_resourceRequest; } + const ResourceRequest& resourceRequest() const { return m_resourceRequest; } + + const String& frameName() const { return m_frameName; } + void setFrameName(const String& frameName) { m_frameName = frameName; } + + bool lockHistory() const { return m_lockHistory; } + void setLockHistory(bool lock) { m_lockHistory = lock; } + + private: + ResourceRequest m_resourceRequest; + String m_frameName; + bool m_lockHistory; + }; + +} + +#endif // FrameLoadRequest_h + diff --git a/WebCore/page/FramePrivate.h b/WebCore/page/FramePrivate.h new file mode 100644 index 0000000..c780db2 --- /dev/null +++ b/WebCore/page/FramePrivate.h @@ -0,0 +1,134 @@ +/* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + * 1999-2001 Lars Knoll <knoll@kde.org> + * 1999-2001 Antti Koivisto <koivisto@kde.org> + * 2000-2001 Simon Hausmann <hausmann@kde.org> + * 2000-2001 Dirk Mueller <mueller@kde.org> + * 2000 Stefan Schimanski <1Stein@gmx.de> + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Trolltech ASA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FramePrivate_h +#define FramePrivate_h + +#include "AnimationController.h" +#include "Editor.h" +#include "EventHandler.h" +#include "FrameTree.h" +#include "Range.h" +#include "SelectionController.h" +#include "StringHash.h" + +namespace KJS { + class Interpreter; + + namespace Bindings { + class Instance; + class RootObject; + } +} + +#if PLATFORM(MAC) +#ifdef __OBJC__ +@class WebCoreFrameBridge; +@class WebScriptObject; +#else +class WebCoreFrameBridge; +class WebScriptObject; +#endif +#endif + +#if PLATFORM(WIN) +#include "FrameWin.h" +#endif + +namespace WebCore { + +#if FRAME_LOADS_USER_STYLESHEET + class UserStyleSheetLoader; +#endif + + typedef HashMap<void*, RefPtr<KJS::Bindings::RootObject> > RootObjectMap; + + class FramePrivate { + public: + FramePrivate(Page*, Frame* parent, Frame* thisFrame, HTMLFrameOwnerElement*, FrameLoaderClient*); + ~FramePrivate(); + + Page* m_page; + FrameTree m_treeNode; + RefPtr<DOMWindow> m_domWindow; + + HTMLFrameOwnerElement* m_ownerElement; + RefPtr<FrameView> m_view; + RefPtr<Document> m_doc; + + KJSProxy* m_jscript; + + String m_kjsStatusBarText; + String m_kjsDefaultStatusBarText; + + int m_zoomFactor; + + TextGranularity m_selectionGranularity; + + SelectionController m_selectionController; + Selection m_mark; + Timer<Frame> m_caretBlinkTimer; + Editor m_editor; + EventHandler m_eventHandler; + AnimationController m_animationController; + + bool m_caretVisible : 1; + bool m_caretPaint : 1; + bool m_isPainting : 1; + + RefPtr<CSSMutableStyleDeclaration> m_typingStyle; + + Timer<Frame> m_lifeSupportTimer; + + FrameLoader* m_loader; + + RefPtr<Node> m_elementToDraw; + PaintRestriction m_paintRestriction; + + bool m_highlightTextMatches; + + bool m_inViewSourceMode; + + unsigned frameCount; + + bool m_prohibitsScrolling; + + bool m_needsReapplyStyles; + + // The root object used for objects bound outside the context of a plugin. + RefPtr<KJS::Bindings::RootObject> m_bindingRootObject; + RootObjectMap m_rootObjects; + NPObject* m_windowScriptNPObject; +#if FRAME_LOADS_USER_STYLESHEET + UserStyleSheetLoader* m_userStyleSheetLoader; +#endif +#if PLATFORM(MAC) + RetainPtr<WebScriptObject> m_windowScriptObject; + WebCoreFrameBridge* m_bridge; +#endif + }; +} + +#endif diff --git a/WebCore/page/FrameTree.cpp b/WebCore/page/FrameTree.cpp new file mode 100644 index 0000000..0831be8 --- /dev/null +++ b/WebCore/page/FrameTree.cpp @@ -0,0 +1,296 @@ +// -*- c-basic-offset: 4 -*- +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "FrameTree.h" + +#include "Frame.h" +#include "Page.h" +#include <stdarg.h> +#include <wtf/Platform.h> +#include <wtf/StringExtras.h> +#include <wtf/Vector.h> + +using std::swap; + +namespace WebCore { + +FrameTree::~FrameTree() +{ + for (Frame* child = firstChild(); child; child = child->tree()->nextSibling()) + child->setView(0); +} + +void FrameTree::setName(const AtomicString& name) +{ + if (!parent()) { + m_name = name; + return; + } + m_name = AtomicString(); // Remove our old frame name so it's not considered in uniqueChildName. + m_name = parent()->tree()->uniqueChildName(name); +} + +void FrameTree::appendChild(PassRefPtr<Frame> child) +{ + ASSERT(child->page() == m_thisFrame->page()); + child->tree()->m_parent = m_thisFrame; + + Frame* oldLast = m_lastChild; + m_lastChild = child.get(); + + if (oldLast) { + child->tree()->m_previousSibling = oldLast; + oldLast->tree()->m_nextSibling = child; + } else + m_firstChild = child; + + m_childCount++; + + ASSERT(!m_lastChild->tree()->m_nextSibling); +} + +void FrameTree::removeChild(Frame* child) +{ + child->tree()->m_parent = 0; + child->setView(0); + if (child->ownerElement()) + child->page()->decrementFrameCount(); + child->pageDestroyed(); + + // Slightly tricky way to prevent deleting the child until we are done with it, w/o + // extra refs. These swaps leave the child in a circular list by itself. Clearing its + // previous and next will then finally deref it. + + RefPtr<Frame>& newLocationForNext = m_firstChild == child ? m_firstChild : child->tree()->m_previousSibling->tree()->m_nextSibling; + Frame*& newLocationForPrevious = m_lastChild == child ? m_lastChild : child->tree()->m_nextSibling->tree()->m_previousSibling; + swap(newLocationForNext, child->tree()->m_nextSibling); + // For some inexplicable reason, the following line does not compile without the explicit std:: namepsace + std::swap(newLocationForPrevious, child->tree()->m_previousSibling); + + child->tree()->m_previousSibling = 0; + child->tree()->m_nextSibling = 0; + + m_childCount--; +} + +AtomicString FrameTree::uniqueChildName(const AtomicString& requestedName) const +{ + if (!requestedName.isEmpty() && !child(requestedName) && requestedName != "_blank") + return requestedName; + + // Create a repeatable name for a child about to be added to us. The name must be + // unique within the frame tree. The string we generate includes a "path" of names + // from the root frame down to us. For this path to be unique, each set of siblings must + // contribute a unique name to the path, which can't collide with any HTML-assigned names. + // We generate this path component by index in the child list along with an unlikely + // frame name that can't be set in HTML because it collides with comment syntax. + + const char framePathPrefix[] = "<!--framePath "; + const int framePathPrefixLength = 14; + const int framePathSuffixLength = 3; + + // Find the nearest parent that has a frame with a path in it. + Vector<Frame*, 16> chain; + Frame* frame; + for (frame = m_thisFrame; frame; frame = frame->tree()->parent()) { + if (frame->tree()->name().startsWith(framePathPrefix)) + break; + chain.append(frame); + } + String name; + name += framePathPrefix; + if (frame) + name += frame->tree()->name().string().substring(framePathPrefixLength, + frame->tree()->name().length() - framePathPrefixLength - framePathSuffixLength); + for (int i = chain.size() - 1; i >= 0; --i) { + frame = chain[i]; + name += "/"; + name += frame->tree()->name(); + } + + // Suffix buffer has more than enough space for: + // 10 characters before the number + // a number (3 digits for the highest this gets in practice, 20 digits for the largest 64-bit integer) + // 6 characters after the number + // trailing null byte + // But we still use snprintf just to be extra-safe. + char suffix[40]; + snprintf(suffix, sizeof(suffix), "/<!--frame%u-->-->", childCount()); + + name += suffix; + + return AtomicString(name); +} + +Frame* FrameTree::child(unsigned index) const +{ + Frame* result = firstChild(); + for (unsigned i = 0; result && i != index; ++i) + result = result->tree()->nextSibling(); + return result; +} + +Frame* FrameTree::child(const AtomicString& name) const +{ + for (Frame* child = firstChild(); child; child = child->tree()->nextSibling()) + if (child->tree()->name() == name) + return child; + return 0; +} + +Frame* FrameTree::find(const AtomicString& name) const +{ + if (name == "_self" || name == "_current" || name.isEmpty()) + return m_thisFrame; + + if (name == "_top") + return m_thisFrame->page()->mainFrame(); + + if (name == "_parent") + return parent() ? parent() : m_thisFrame; + + // Since "_blank" should never be any frame's name, the following just amounts to an optimization. + if (name == "_blank") + return 0; + + // Search subtree starting with this frame first. + for (Frame* frame = m_thisFrame; frame; frame = frame->tree()->traverseNext(m_thisFrame)) + if (frame->tree()->name() == name) + return frame; + + // Search the entire tree for this page next. + Page* page = m_thisFrame->page(); + for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) + if (frame->tree()->name() == name) + return frame; + + // Search the entire tree for all other pages in this namespace. + const HashSet<Page*>* pages = page->frameNamespace(); + if (pages) { + HashSet<Page*>::const_iterator end = pages->end(); + for (HashSet<Page*>::const_iterator it = pages->begin(); it != end; ++it) { + Page* otherPage = *it; + if (otherPage != page) + for (Frame* frame = otherPage->mainFrame(); frame; frame = frame->tree()->traverseNext()) + if (frame->tree()->name() == name) + return frame; + } + } + + return 0; +} + +bool FrameTree::isDescendantOf(const Frame* ancestor) const +{ + if (!ancestor) + return false; + + if (m_thisFrame->page() != ancestor->page()) + return false; + + for (Frame* frame = m_thisFrame; frame; frame = frame->tree()->parent()) + if (frame == ancestor) + return true; + return false; +} + +Frame* FrameTree::traverseNext(const Frame* stayWithin) const +{ + Frame* child = firstChild(); + if (child) { + ASSERT(!stayWithin || child->tree()->isDescendantOf(stayWithin)); + return child; + } + + if (m_thisFrame == stayWithin) + return 0; + + Frame* sibling = nextSibling(); + if (sibling) { + ASSERT(!stayWithin || sibling->tree()->isDescendantOf(stayWithin)); + return sibling; + } + + Frame* frame = m_thisFrame; + while (!sibling && (!stayWithin || frame->tree()->parent() != stayWithin)) { + frame = frame->tree()->parent(); + if (!frame) + return 0; + sibling = frame->tree()->nextSibling(); + } + + if (frame) { + ASSERT(!stayWithin || !sibling || sibling->tree()->isDescendantOf(stayWithin)); + return sibling; + } + + return 0; +} + +Frame* FrameTree::traverseNextWithWrap(bool wrap) const +{ + if (Frame* result = traverseNext()) + return result; + + if (wrap) + return m_thisFrame->page()->mainFrame(); + + return 0; +} + +Frame* FrameTree::traversePreviousWithWrap(bool wrap) const +{ + // FIXME: besides the wrap feature, this is just the traversePreviousNode algorithm + + if (Frame* prevSibling = previousSibling()) + return prevSibling->tree()->deepLastChild(); + if (Frame* parentFrame = parent()) + return parentFrame; + + // no siblings, no parent, self==top + if (wrap) + return deepLastChild(); + + // top view is always the last one in this ordering, so prev is nil without wrap + return 0; +} + +Frame* FrameTree::deepLastChild() const +{ + Frame* result = m_thisFrame; + for (Frame* last = lastChild(); last; last = last->tree()->lastChild()) + result = last; + + return result; +} + +Frame* FrameTree::top() const +{ + if (Page* page = m_thisFrame->page()) + return page->mainFrame(); + + Frame* frame = m_thisFrame; + while (Frame* parent = frame->tree()->parent()) + frame = parent; + return frame; +} + +} // namespace WebCore diff --git a/WebCore/page/FrameTree.h b/WebCore/page/FrameTree.h new file mode 100644 index 0000000..3f3e20a --- /dev/null +++ b/WebCore/page/FrameTree.h @@ -0,0 +1,87 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef FrameTree_h +#define FrameTree_h + +#include "AtomicString.h" + +namespace WebCore { + + class Frame; + + class FrameTree : Noncopyable { + public: + FrameTree(Frame* thisFrame, Frame* parentFrame) + : m_thisFrame(thisFrame) + , m_parent(parentFrame) + , m_previousSibling(0) + , m_lastChild(0) + , m_childCount(0) + { + } + ~FrameTree(); + + const AtomicString& name() const { return m_name; } + void setName(const AtomicString&); + Frame* parent() const { return m_parent; } + void setParent(Frame* parent) { m_parent = parent; } + + Frame* nextSibling() const { return m_nextSibling.get(); } + Frame* previousSibling() const { return m_previousSibling; } + Frame* firstChild() const { return m_firstChild.get(); } + Frame* lastChild() const { return m_lastChild; } + unsigned childCount() const { return m_childCount; } + + bool isDescendantOf(const Frame* ancestor) const; + Frame* traverseNext(const Frame* stayWithin = 0) const; + Frame* traverseNextWithWrap(bool) const; + Frame* traversePreviousWithWrap(bool) const; + + void appendChild(PassRefPtr<Frame>); + void removeChild(Frame*); + + Frame* child(unsigned index) const; + Frame* child(const AtomicString& name) const; + Frame* find(const AtomicString& name) const; + + AtomicString uniqueChildName(const AtomicString& requestedName) const; + + Frame* top() const; + + private: + Frame* deepLastChild() const; + + Frame* m_thisFrame; + + Frame* m_parent; + AtomicString m_name; + + // FIXME: use ListRefPtr? + RefPtr<Frame> m_nextSibling; + Frame* m_previousSibling; + RefPtr<Frame> m_firstChild; + Frame* m_lastChild; + unsigned m_childCount; + }; + +} // namespace WebCore + +#endif // FrameTree_h diff --git a/WebCore/page/FrameView.cpp b/WebCore/page/FrameView.cpp new file mode 100644 index 0000000..d56175a --- /dev/null +++ b/WebCore/page/FrameView.cpp @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + * 1999 Lars Knoll <knoll@kde.org> + * 1999 Antti Koivisto <koivisto@kde.org> + * 2000 Dirk Mueller <mueller@kde.org> + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * (C) 2006 Graham Dennis (graham.dennis@gmail.com) + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "FrameView.h" + +#include "AXObjectCache.h" +#include "CSSStyleSelector.h" +#include "EventHandler.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "GraphicsContext.h" +#include "HTMLDocument.h" +#include "HTMLFrameSetElement.h" +#include "HTMLNames.h" +#include "OverflowEvent.h" +#include "RenderPart.h" +#include "RenderPartObject.h" +#include "RenderTheme.h" +#include "RenderView.h" + +namespace WebCore { + +using namespace HTMLNames; + +struct ScheduledEvent { + RefPtr<Event> m_event; + RefPtr<EventTargetNode> m_eventTarget; + bool m_tempEvent; +}; + +class FrameViewPrivate { +public: + FrameViewPrivate(FrameView* view) + : m_slowRepaintObjectCount(0) + , layoutTimer(view, &FrameView::layoutTimerFired) + , layoutRoot(0) + , postLayoutTasksTimer(view, &FrameView::postLayoutTimerFired) + , m_mediaType("screen") + , m_enqueueEvents(0) + , m_overflowStatusDirty(true) + , m_viewportRenderer(0) + , m_wasScrolledByUser(false) + , m_inProgrammaticScroll(false) + { + isTransparent = false; + baseBackgroundColor = Color::white; + vmode = hmode = ScrollbarAuto; + needToInitScrollbars = true; + reset(); + } + void reset() + { + useSlowRepaints = false; + borderX = 30; + borderY = 30; + layoutTimer.stop(); + layoutRoot = 0; + delayedLayout = false; + doFullRepaint = true; + layoutSchedulingEnabled = true; + midLayout = false; + layoutCount = 0; + nestedLayoutCount = 0; + postLayoutTasksTimer.stop(); + firstLayout = true; + repaintRects.clear(); + m_wasScrolledByUser = false; + lastLayoutSize = IntSize(); + } + + bool doFullRepaint; + + ScrollbarMode vmode; + ScrollbarMode hmode; + bool useSlowRepaints; + unsigned m_slowRepaintObjectCount; + + int borderX, borderY; + + Timer<FrameView> layoutTimer; + bool delayedLayout; + RenderObject* layoutRoot; + + bool layoutSchedulingEnabled; + bool midLayout; + int layoutCount; + unsigned nestedLayoutCount; + Timer<FrameView> postLayoutTasksTimer; + + bool firstLayout; + bool needToInitScrollbars; + bool isTransparent; + Color baseBackgroundColor; + IntSize lastLayoutSize; + + // Used by objects during layout to communicate repaints that need to take place only + // after all layout has been completed. + Vector<RenderObject::RepaintInfo> repaintRects; + + String m_mediaType; + + unsigned m_enqueueEvents; + Vector<ScheduledEvent*> m_scheduledEvents; + + bool m_overflowStatusDirty; + bool horizontalOverflow; + bool m_verticalOverflow; + RenderObject* m_viewportRenderer; + + bool m_wasScrolledByUser; + bool m_inProgrammaticScroll; +}; + +FrameView::FrameView(Frame* frame) + : m_refCount(1) + , m_frame(frame) + , d(new FrameViewPrivate(this)) +{ + init(); + show(); +} + +#if !PLATFORM(MAC) +FrameView::FrameView(Frame* frame, const IntSize& initialSize) + : m_refCount(1) + , m_frame(frame) + , d(new FrameViewPrivate(this)) +{ + init(); + Widget::setFrameGeometry(IntRect(x(), y(), initialSize.width(), initialSize.height())); + show(); +} +#endif + +FrameView::~FrameView() +{ + if (d->postLayoutTasksTimer.isActive()) { + d->postLayoutTasksTimer.stop(); + d->m_scheduledEvents.clear(); + d->m_enqueueEvents = 0; + } + + resetScrollbars(); + + ASSERT(m_refCount == 0); + ASSERT(d->m_scheduledEvents.isEmpty()); + ASSERT(!d->m_enqueueEvents); + + if (m_frame) { + ASSERT(m_frame->view() != this || !m_frame->document() || !m_frame->document()->renderer()); + RenderPart* renderer = m_frame->ownerRenderer(); + if (renderer && renderer->widget() == this) + renderer->setWidget(0); + } + + delete d; + d = 0; +} + +bool FrameView::isFrameView() const +{ + return true; +} + +void FrameView::clearFrame() +{ + m_frame = 0; +} + +void FrameView::resetScrollbars() +{ + // Reset the document's scrollbars back to our defaults before we yield the floor. + d->firstLayout = true; + suppressScrollbars(true); + ScrollView::setVScrollbarMode(d->vmode); + ScrollView::setHScrollbarMode(d->hmode); + suppressScrollbars(false); +} + +void FrameView::init() +{ + m_margins = IntSize(-1, -1); // undefined + m_size = IntSize(); +} + +void FrameView::clear() +{ + setStaticBackground(false); + + d->reset(); + + if (m_frame) + if (RenderPart* renderer = m_frame->ownerRenderer()) + renderer->viewCleared(); + + suppressScrollbars(true); +} + +bool FrameView::didFirstLayout() const +{ + return !d->firstLayout; +} + +void FrameView::initScrollbars() +{ + if (!d->needToInitScrollbars) + return; + d->needToInitScrollbars = false; + setScrollbarsMode(hScrollbarMode()); +} + +void FrameView::setMarginWidth(int w) +{ + // make it update the rendering area when set + m_margins.setWidth(w); +} + +void FrameView::setMarginHeight(int h) +{ + // make it update the rendering area when set + m_margins.setHeight(h); +} + +void FrameView::adjustViewSize() +{ + ASSERT(m_frame->view() == this); + RenderView* root = static_cast<RenderView*>(m_frame->renderer()); + if (!root) + return; + resizeContents(root->overflowWidth(), root->overflowHeight()); +} + +void FrameView::applyOverflowToViewport(RenderObject* o, ScrollbarMode& hMode, ScrollbarMode& vMode) +{ + // Handle the overflow:hidden/scroll case for the body/html elements. WinIE treats + // overflow:hidden and overflow:scroll on <body> as applying to the document's + // scrollbars. The CSS2.1 draft states that HTML UAs should use the <html> or <body> element and XML/XHTML UAs should + // use the root element. + switch (o->style()->overflowX()) { + case OHIDDEN: + hMode = ScrollbarAlwaysOff; + break; + case OSCROLL: + hMode = ScrollbarAlwaysOn; + break; + case OAUTO: + hMode = ScrollbarAuto; + break; + default: + // Don't set it at all. + ; + } + + switch (o->style()->overflowY()) { + case OHIDDEN: + vMode = ScrollbarAlwaysOff; + break; + case OSCROLL: + vMode = ScrollbarAlwaysOn; + break; + case OAUTO: + vMode = ScrollbarAuto; + break; + default: + // Don't set it at all. + ; + } + + d->m_viewportRenderer = o; +} + +int FrameView::layoutCount() const +{ + return d->layoutCount; +} + +bool FrameView::needsFullRepaint() const +{ + return d->doFullRepaint; +} + +void FrameView::addRepaintInfo(RenderObject* o, const IntRect& r) +{ + d->repaintRects.append(RenderObject::RepaintInfo(o, r)); +} + +RenderObject* FrameView::layoutRoot(bool onlyDuringLayout) const +{ + return onlyDuringLayout && layoutPending() ? 0 : d->layoutRoot; +} + +void FrameView::layout(bool allowSubtree) +{ + if (d->midLayout) + return; + + d->layoutTimer.stop(); + d->delayedLayout = false; + + // Protect the view from being deleted during layout (in recalcStyle) + RefPtr<FrameView> protector(this); + + if (!m_frame) { + // FIXME: Do we need to set m_size.width here? + // FIXME: Should we set m_size.height here too? + m_size.setWidth(visibleWidth()); + return; + } + + // we shouldn't enter layout() while painting + ASSERT(!m_frame->isPainting()); + if (m_frame->isPainting()) + return; + + if (!allowSubtree && d->layoutRoot) { + d->layoutRoot->markContainingBlocksForLayout(false); + d->layoutRoot = 0; + } + + ASSERT(m_frame->view() == this); + // This early return should be removed when rdar://5598072 is resolved. In the meantime, there is a + // gigantic CrashTracer because of this issue, and the early return will hopefully cause graceful + // failure instead. + if (m_frame->view() != this) + return; + + Document* document = m_frame->document(); + if (!document) { + // FIXME: Should we set m_size.height here too? + m_size.setWidth(visibleWidth()); + return; + } + + d->layoutSchedulingEnabled = false; + + if (!d->nestedLayoutCount && d->postLayoutTasksTimer.isActive()) { + // This is a new top-level layout. If there are any remaining tasks from the previous + // layout, finish them now. + d->postLayoutTasksTimer.stop(); + performPostLayoutTasks(); + } + + // Viewport-dependent media queries may cause us to need completely different style information. + // Check that here. + if (document->styleSelector()->affectedByViewportChange()) + document->updateStyleSelector(); + + // Always ensure our style info is up-to-date. This can happen in situations where + // the layout beats any sort of style recalc update that needs to occur. + if (m_frame->needsReapplyStyles()) + m_frame->reapplyStyles(); + else if (document->hasChangedChild()) + document->recalcStyle(); + + bool subtree = d->layoutRoot; + + // If there is only one ref to this view left, then its going to be destroyed as soon as we exit, + // so there's no point to continuing to layout + if (protector->hasOneRef()) + return; + + RenderObject* root = subtree ? d->layoutRoot : document->renderer(); + if (!root) { + // FIXME: Do we need to set m_size here? + d->layoutSchedulingEnabled = true; + return; + } + + d->nestedLayoutCount++; + + ScrollbarMode hMode = d->hmode; + ScrollbarMode vMode = d->vmode; + + if (!subtree) { + RenderObject* rootRenderer = document->documentElement() ? document->documentElement()->renderer() : 0; + if (document->isHTMLDocument()) { + Node* body = static_cast<HTMLDocument*>(document)->body(); + if (body && body->renderer()) { + if (body->hasTagName(framesetTag)) { + body->renderer()->setChildNeedsLayout(true); + vMode = ScrollbarAlwaysOff; + hMode = ScrollbarAlwaysOff; + } else if (body->hasTagName(bodyTag)) { + if (!d->firstLayout && m_size.height() != visibleHeight() + && static_cast<RenderBox*>(body->renderer())->stretchesToViewHeight()) + body->renderer()->setChildNeedsLayout(true); + // It's sufficient to just check the X overflow, + // since it's illegal to have visible in only one direction. + RenderObject* o = rootRenderer->style()->overflowX() == OVISIBLE + ? body->renderer() : rootRenderer; + applyOverflowToViewport(o, hMode, vMode); // Only applies to HTML UAs, not to XML/XHTML UAs + } + } + } else if (rootRenderer) + applyOverflowToViewport(rootRenderer, hMode, vMode); // XML/XHTML UAs use the root element. +#ifdef INSTRUMENT_LAYOUT_SCHEDULING + if (d->firstLayout && !document->ownerElement()) + printf("Elapsed time before first layout: %d\n", document->elapsedTime()); +#endif + } + + d->doFullRepaint = !subtree && (d->firstLayout || static_cast<RenderView*>(root)->printing()); + ASSERT(d->nestedLayoutCount > 1 || d->repaintRects.isEmpty()); + + bool didFirstLayout = false; + if (!subtree) { + // Now set our scrollbar state for the layout. + ScrollbarMode currentHMode = hScrollbarMode(); + ScrollbarMode currentVMode = vScrollbarMode(); + + if (d->firstLayout || (hMode != currentHMode || vMode != currentVMode)) { + suppressScrollbars(true); + if (d->firstLayout) { + d->firstLayout = false; + didFirstLayout = true; + d->lastLayoutSize = IntSize(width(), height()); + + // Set the initial vMode to AlwaysOn if we're auto. + if (vMode == ScrollbarAuto) + ScrollView::setVScrollbarMode(ScrollbarAlwaysOn); // This causes a vertical scrollbar to appear. + // Set the initial hMode to AlwaysOff if we're auto. + if (hMode == ScrollbarAuto) + ScrollView::setHScrollbarMode(ScrollbarAlwaysOff); // This causes a horizontal scrollbar to disappear. + } + + if (hMode == vMode) + ScrollView::setScrollbarsMode(hMode); + else { + ScrollView::setHScrollbarMode(hMode); + ScrollView::setVScrollbarMode(vMode); + } + + suppressScrollbars(false, true); + } + + IntSize oldSize = m_size; + + m_size = IntSize(visibleWidth(), visibleHeight()); + + if (oldSize != m_size) + d->doFullRepaint = true; + } + + RenderLayer* layer = root->enclosingLayer(); + + pauseScheduledEvents(); + + if (subtree) + root->view()->pushLayoutState(root); + d->midLayout = true; + root->layout(); + d->midLayout = false; + if (subtree) + root->view()->popLayoutState(); + d->layoutRoot = 0; + + m_frame->invalidateSelection(); + + d->layoutSchedulingEnabled = true; + + if (!subtree && !static_cast<RenderView*>(root)->printing()) + adjustViewSize(); + + // Now update the positions of all layers. + layer->updateLayerPositions(d->doFullRepaint); + + // FIXME: Could optimize this and have objects removed from this list + // if they ever do full repaints. + Vector<RenderObject::RepaintInfo>::iterator end = d->repaintRects.end(); + for (Vector<RenderObject::RepaintInfo>::iterator it = d->repaintRects.begin(); it != end; ++it) + it->m_object->repaintRectangle(it->m_repaintRect); + d->repaintRects.clear(); + + d->layoutCount++; + +#if PLATFORM(MAC) + if (AXObjectCache::accessibilityEnabled()) + root->document()->axObjectCache()->postNotificationToElement(root, "AXLayoutComplete"); +#endif + updateDashboardRegions(); + + if (didFirstLayout) + m_frame->loader()->didFirstLayout(); + + ASSERT(!root->needsLayout()); + + setStaticBackground(useSlowRepaints()); + + if (document->hasListenerType(Document::OVERFLOWCHANGED_LISTENER)) + updateOverflowStatus(visibleWidth() < contentsWidth(), + visibleHeight() < contentsHeight()); + + if (!d->postLayoutTasksTimer.isActive()) { + // Calls resumeScheduledEvents() + performPostLayoutTasks(); + + if (needsLayout()) { + // Post-layout widget updates or an event handler made us need layout again. + // Lay out again, but this time defer widget updates and event dispatch until after + // we return. + d->postLayoutTasksTimer.startOneShot(0); + pauseScheduledEvents(); + layout(); + } + } else { + resumeScheduledEvents(); + ASSERT(d->m_enqueueEvents); + } + + d->nestedLayoutCount--; +} + +void FrameView::addWidgetToUpdate(RenderPartObject* object) +{ + if (!m_widgetUpdateSet) + m_widgetUpdateSet.set(new HashSet<RenderPartObject*>); + + m_widgetUpdateSet->add(object); +} + +void FrameView::removeWidgetToUpdate(RenderPartObject* object) +{ + if (!m_widgetUpdateSet) + return; + + m_widgetUpdateSet->remove(object); +} + +// +// Event Handling +// +///////////////// + +bool FrameView::scrollTo(const IntRect& bounds) +{ + int x, y, xe, ye; + x = bounds.x(); + y = bounds.y(); + xe = bounds.right() - 1; + ye = bounds.bottom() - 1; + + int deltax; + int deltay; + + int curHeight = visibleHeight(); + int curWidth = visibleWidth(); + + if (ye - y>curHeight-d->borderY) + ye = y + curHeight - d->borderY; + + if (xe - x>curWidth-d->borderX) + xe = x + curWidth - d->borderX; + + // is xpos of target left of the view's border? + if (x < contentsX() + d->borderX) + deltax = x - contentsX() - d->borderX; + // is xpos of target right of the view's right border? + else if (xe + d->borderX > contentsX() + curWidth) + deltax = xe + d->borderX - (contentsX() + curWidth); + else + deltax = 0; + + // is ypos of target above upper border? + if (y < contentsY() + d->borderY) + deltay = y - contentsY() - d->borderY; + // is ypos of target below lower border? + else if (ye + d->borderY > contentsY() + curHeight) + deltay = ye + d->borderY - (contentsY() + curHeight); + else + deltay = 0; + + int maxx = curWidth - d->borderX; + int maxy = curHeight - d->borderY; + + int scrollX = deltax > 0 ? (deltax > maxx ? maxx : deltax) : deltax == 0 ? 0 : (deltax > -maxx ? deltax : -maxx); + int scrollY = deltay > 0 ? (deltay > maxy ? maxy : deltay) : deltay == 0 ? 0 : (deltay > -maxy ? deltay : -maxy); + + if (contentsX() + scrollX < 0) + scrollX = -contentsX(); + else if (contentsWidth() - visibleWidth() - contentsX() < scrollX) + scrollX = contentsWidth() - visibleWidth() - contentsX(); + + if (contentsY() + scrollY < 0) + scrollY = -contentsY(); + else if (contentsHeight() - visibleHeight() - contentsY() < scrollY) + scrollY = contentsHeight() - visibleHeight() - contentsY(); + + scrollBy(scrollX, scrollY); + + // generate abs(scroll.) + if (scrollX < 0) + scrollX = -scrollX; + if (scrollY < 0) + scrollY = -scrollY; + + return scrollX != maxx && scrollY != maxy; +} + +void FrameView::setMediaType(const String& mediaType) +{ + d->m_mediaType = mediaType; +} + +String FrameView::mediaType() const +{ + // See if we have an override type. + String overrideType = m_frame->loader()->client()->overrideMediaType(); + if (!overrideType.isNull()) + return overrideType; + return d->m_mediaType; +} + +bool FrameView::useSlowRepaints() const +{ + return d->useSlowRepaints || d->m_slowRepaintObjectCount > 0; +} + +void FrameView::setUseSlowRepaints() +{ + d->useSlowRepaints = true; + setStaticBackground(true); +} + +void FrameView::addSlowRepaintObject() +{ + if (!d->m_slowRepaintObjectCount) + setStaticBackground(true); + d->m_slowRepaintObjectCount++; +} + +void FrameView::removeSlowRepaintObject() +{ + ASSERT(d->m_slowRepaintObjectCount > 0); + d->m_slowRepaintObjectCount--; + if (!d->m_slowRepaintObjectCount) + setStaticBackground(d->useSlowRepaints); +} + +void FrameView::setScrollbarsMode(ScrollbarMode mode) +{ + d->vmode = mode; + d->hmode = mode; + + ScrollView::setScrollbarsMode(mode); +} + +void FrameView::setVScrollbarMode(ScrollbarMode mode) +{ + d->vmode = mode; + ScrollView::setVScrollbarMode(mode); +} + +void FrameView::setHScrollbarMode(ScrollbarMode mode) +{ + d->hmode = mode; + ScrollView::setHScrollbarMode(mode); +} + +void FrameView::restoreScrollbar() +{ + suppressScrollbars(false); +} + +void FrameView::scrollRectIntoViewRecursively(const IntRect& r) +{ + if (frame()->prohibitsScrolling()) + return; + bool wasInProgrammaticScroll = d->m_inProgrammaticScroll; + d->m_inProgrammaticScroll = true; + ScrollView::scrollRectIntoViewRecursively(r); + d->m_inProgrammaticScroll = wasInProgrammaticScroll; +} + +void FrameView::setContentsPos(int x, int y) +{ + if (frame()->prohibitsScrolling()) + return; + bool wasInProgrammaticScroll = d->m_inProgrammaticScroll; + d->m_inProgrammaticScroll = true; + ScrollView::setContentsPos(x, y); + d->m_inProgrammaticScroll = wasInProgrammaticScroll; +} + +void FrameView::repaintRectangle(const IntRect& r, bool immediate) +{ + updateContents(r, immediate); +} + +void FrameView::layoutTimerFired(Timer<FrameView>*) +{ +#ifdef INSTRUMENT_LAYOUT_SCHEDULING + if (m_frame->document() && !m_frame->document()->ownerElement()) + printf("Layout timer fired at %d\n", m_frame->document()->elapsedTime()); +#endif + layout(); +} + +void FrameView::scheduleRelayout() +{ + ASSERT(!m_frame->document() || !m_frame->document()->inPageCache()); + ASSERT(m_frame->view() == this); + + if (d->layoutRoot) { + d->layoutRoot->markContainingBlocksForLayout(false); + d->layoutRoot = 0; + } + if (!d->layoutSchedulingEnabled) + return; + + if (!m_frame->document() || !m_frame->document()->shouldScheduleLayout()) + return; + + int delay = m_frame->document()->minimumLayoutDelay(); + if (d->layoutTimer.isActive() && d->delayedLayout && !delay) + unscheduleRelayout(); + if (d->layoutTimer.isActive()) + return; + + d->delayedLayout = delay != 0; + +#ifdef INSTRUMENT_LAYOUT_SCHEDULING + if (!m_frame->document()->ownerElement()) + printf("Scheduling layout for %d\n", delay); +#endif + + d->layoutTimer.startOneShot(delay * 0.001); +} + +static bool isObjectAncestorContainerOf(RenderObject* ancestor, RenderObject* descendant) +{ + for (RenderObject* r = descendant; r; r = r->container()) { + if (r == ancestor) + return true; + } + return false; +} + +void FrameView::scheduleRelayoutOfSubtree(RenderObject* o) +{ + ASSERT(m_frame->view() == this); + + if (!d->layoutSchedulingEnabled || (m_frame->document() + && m_frame->document()->renderer() + && m_frame->document()->renderer()->needsLayout())) { + if (o) + o->markContainingBlocksForLayout(false); + return; + } + + if (layoutPending()) { + if (d->layoutRoot != o) { + if (isObjectAncestorContainerOf(d->layoutRoot, o)) { + // Keep the current root + o->markContainingBlocksForLayout(false, d->layoutRoot); + } else if (d->layoutRoot && isObjectAncestorContainerOf(o, d->layoutRoot)) { + // Re-root at o + d->layoutRoot->markContainingBlocksForLayout(false, o); + d->layoutRoot = o; + } else { + // Just do a full relayout + if (d->layoutRoot) + d->layoutRoot->markContainingBlocksForLayout(false); + d->layoutRoot = 0; + o->markContainingBlocksForLayout(false); + } + } + } else { + int delay = m_frame->document()->minimumLayoutDelay(); + d->layoutRoot = o; + d->delayedLayout = delay != 0; + d->layoutTimer.startOneShot(delay * 0.001); + } +} + +bool FrameView::layoutPending() const +{ + return d->layoutTimer.isActive(); +} + +bool FrameView::needsLayout() const +{ + // It is possible that our document will not have a body yet. If this is the case, + // then we are not allowed to schedule layouts yet, so we won't be pending layout. + if (!m_frame) + return false; + RenderView* root = static_cast<RenderView*>(m_frame->renderer()); + Document * doc = m_frame->document(); + // doc->hasChangedChild() condition can occur when using WebKit ObjC interface + return layoutPending() || (root && root->needsLayout()) || d->layoutRoot || (doc && doc->hasChangedChild()) || m_frame->needsReapplyStyles(); +} + +void FrameView::setNeedsLayout() +{ + if (m_frame->renderer()) + m_frame->renderer()->setNeedsLayout(true); +} + +void FrameView::unscheduleRelayout() +{ + if (!d->layoutTimer.isActive()) + return; + +#ifdef INSTRUMENT_LAYOUT_SCHEDULING + if (m_frame->document() && !m_frame->document()->ownerElement()) + printf("Layout timer unscheduled at %d\n", m_frame->document()->elapsedTime()); +#endif + + d->layoutTimer.stop(); + d->delayedLayout = false; +} + +bool FrameView::isTransparent() const +{ + return d->isTransparent; +} + +void FrameView::setTransparent(bool isTransparent) +{ + d->isTransparent = isTransparent; +} + +Color FrameView::baseBackgroundColor() const +{ + return d->baseBackgroundColor; +} + +void FrameView::setBaseBackgroundColor(Color bc) +{ + if (!bc.isValid()) + bc = Color::white; + d->baseBackgroundColor = bc; +} + +void FrameView::scheduleEvent(PassRefPtr<Event> event, PassRefPtr<EventTargetNode> eventTarget, bool tempEvent) +{ + if (!d->m_enqueueEvents) { + ExceptionCode ec = 0; + eventTarget->dispatchEvent(event, ec, tempEvent); + return; + } + + ScheduledEvent* scheduledEvent = new ScheduledEvent; + scheduledEvent->m_event = event; + scheduledEvent->m_eventTarget = eventTarget; + scheduledEvent->m_tempEvent = tempEvent; + d->m_scheduledEvents.append(scheduledEvent); +} + +void FrameView::pauseScheduledEvents() +{ + ASSERT(d->m_scheduledEvents.isEmpty() || d->m_enqueueEvents); + d->m_enqueueEvents++; +} + +void FrameView::resumeScheduledEvents() +{ + d->m_enqueueEvents--; + if (!d->m_enqueueEvents) + dispatchScheduledEvents(); + ASSERT(d->m_scheduledEvents.isEmpty() || d->m_enqueueEvents); +} + +void FrameView::performPostLayoutTasks() +{ + RenderView* root = static_cast<RenderView*>(m_frame->document()->renderer()); + + root->updateWidgetPositions(); + if (m_widgetUpdateSet && d->nestedLayoutCount <= 1) { + Vector<RenderPartObject*> objectVector; + copyToVector(*m_widgetUpdateSet, objectVector); + size_t size = objectVector.size(); + for (size_t i = 0; i < size; ++i) { + RenderPartObject* object = objectVector[i]; + object->updateWidget(false); + + // updateWidget() can destroy the RenderPartObject, so we need to make sure it's + // alive by checking if it's still in m_widgetUpdateSet. + if (m_widgetUpdateSet->contains(object)) + object->updateWidgetPosition(); + } + m_widgetUpdateSet->clear(); + } + + resumeScheduledEvents(); + + if (!root->printing()) { + IntSize currentSize = IntSize(width(), height()); + bool resized = !d->firstLayout && currentSize != d->lastLayoutSize; + d->lastLayoutSize = currentSize; + if (resized) + m_frame->sendResizeEvent(); + } +} + +void FrameView::postLayoutTimerFired(Timer<FrameView>*) +{ + performPostLayoutTasks(); +} + +void FrameView::updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow) +{ + if (!d->m_viewportRenderer) + return; + + if (d->m_overflowStatusDirty) { + d->horizontalOverflow = horizontalOverflow; + d->m_verticalOverflow = verticalOverflow; + d->m_overflowStatusDirty = false; + return; + } + + bool horizontalOverflowChanged = (d->horizontalOverflow != horizontalOverflow); + bool verticalOverflowChanged = (d->m_verticalOverflow != verticalOverflow); + + if (horizontalOverflowChanged || verticalOverflowChanged) { + d->horizontalOverflow = horizontalOverflow; + d->m_verticalOverflow = verticalOverflow; + + scheduleEvent(new OverflowEvent(horizontalOverflowChanged, horizontalOverflow, + verticalOverflowChanged, verticalOverflow), + EventTargetNodeCast(d->m_viewportRenderer->element()), true); + } + +} + +void FrameView::dispatchScheduledEvents() +{ + if (d->m_scheduledEvents.isEmpty()) + return; + + Vector<ScheduledEvent*> scheduledEventsCopy = d->m_scheduledEvents; + d->m_scheduledEvents.clear(); + + Vector<ScheduledEvent*>::iterator end = scheduledEventsCopy.end(); + for (Vector<ScheduledEvent*>::iterator it = scheduledEventsCopy.begin(); it != end; ++it) { + ScheduledEvent* scheduledEvent = *it; + + ExceptionCode ec = 0; + + // Only dispatch events to nodes that are in the document + if (scheduledEvent->m_eventTarget->inDocument()) + scheduledEvent->m_eventTarget->dispatchEvent(scheduledEvent->m_event, + ec, scheduledEvent->m_tempEvent); + + delete scheduledEvent; + } +} + +IntRect FrameView::windowClipRect() const +{ + return windowClipRect(true); +} + +IntRect FrameView::windowClipRect(bool clipToContents) const +{ + ASSERT(m_frame->view() == this); + + // Set our clip rect to be our contents. + IntRect clipRect; + if (clipToContents) + clipRect = enclosingIntRect(visibleContentRect()); + else + clipRect = IntRect(contentsX(), contentsY(), width(), height()); + clipRect = contentsToWindow(clipRect); + + if (!m_frame || !m_frame->document() || !m_frame->document()->ownerElement()) + return clipRect; + + // Take our owner element and get the clip rect from the enclosing layer. + Element* elt = m_frame->document()->ownerElement(); + RenderLayer* layer = elt->renderer()->enclosingLayer(); + // FIXME: layer should never be null, but sometimes seems to be anyway. + if (!layer) + return clipRect; + FrameView* parentView = elt->document()->view(); + clipRect.intersect(parentView->windowClipRectForLayer(layer, true)); + return clipRect; +} + +IntRect FrameView::windowClipRectForLayer(const RenderLayer* layer, bool clipToLayerContents) const +{ + // If we have no layer, just return our window clip rect. + if (!layer) + return windowClipRect(); + + // Apply the clip from the layer. + IntRect clipRect; + if (clipToLayerContents) + clipRect = layer->childrenClipRect(); + else + clipRect = layer->selfClipRect(); + clipRect = contentsToWindow(clipRect); + return intersection(clipRect, windowClipRect()); +} + +void FrameView::updateDashboardRegions() +{ + Document* doc = m_frame->document(); + if (doc->hasDashboardRegions()) { + Vector<DashboardRegionValue> newRegions; + doc->renderer()->collectDashboardRegions(newRegions); + doc->setDashboardRegions(newRegions); + m_frame.get()->dashboardRegionsChanged(); + } +} + +void FrameView::updateControlTints() +{ + // This is called when control tints are changed from aqua/graphite to clear and vice versa. + // We do a "fake" paint, and when the theme gets a paint call, it can then do an invalidate. + // This is only done if the theme supports control tinting. It's up to the theme and platform + // to define when controls get the tint and to call this function when that changes. + + // Optimize the common case where we bring a window to the front while it's still empty. + if (!m_frame || m_frame->loader()->url().isEmpty()) + return; + + if (theme()->supportsControlTints() && m_frame->renderer()) { + if (needsLayout()) + layout(); + PlatformGraphicsContext* const noContext = 0; + GraphicsContext context(noContext); + context.setUpdatingControlTints(true); +#if !PLATFORM(MAC) + ScrollView::paint(&context, frameGeometry()); +#else + m_frame->paint(&context, enclosingIntRect(visibleContentRect())); +#endif + } +} + +bool FrameView::wasScrolledByUser() const +{ + return d->m_wasScrolledByUser; +} + +void FrameView::setWasScrolledByUser(bool wasScrolledByUser) +{ + if (d->m_inProgrammaticScroll) + return; + d->m_wasScrolledByUser = wasScrolledByUser; +} + +#if PLATFORM(WIN) || PLATFORM(GTK) || PLATFORM(QT) +void FrameView::layoutIfNeededRecursive() +{ + // We have to crawl our entire tree looking for any FrameViews that need + // layout and make sure they are up to date. + // Mac actually tests for intersection with the dirty region and tries not to + // update layout for frames that are outside the dirty region. Not only does this seem + // pointless (since those frames will have set a zero timer to layout anyway), but + // it is also incorrect, since if two frames overlap, the first could be excluded from the dirty + // region but then become included later by the second frame adding rects to the dirty region + // when it lays out. + + if (needsLayout()) + layout(); + + HashSet<Widget*>* viewChildren = children(); + HashSet<Widget*>::iterator end = viewChildren->end(); + for (HashSet<Widget*>::iterator current = viewChildren->begin(); current != end; ++current) + if ((*current)->isFrameView()) + static_cast<FrameView*>(*current)->layoutIfNeededRecursive(); +} +#endif + +} diff --git a/WebCore/page/FrameView.h b/WebCore/page/FrameView.h new file mode 100644 index 0000000..7577b10 --- /dev/null +++ b/WebCore/page/FrameView.h @@ -0,0 +1,173 @@ +/* + Copyright (C) 1997 Martin Jones (mjones@kde.org) + (C) 1998 Waldo Bastian (bastian@kde.org) + (C) 1998, 1999 Torben Weis (weis@kde.org) + (C) 1999 Lars Knoll (knoll@kde.org) + (C) 1999 Antti Koivisto (koivisto@kde.org) + Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FrameView_h +#define FrameView_h + +#include "ScrollView.h" +#include "IntSize.h" +#include <wtf/Forward.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class Color; +class Event; +class EventTargetNode; +class Frame; +class FrameViewPrivate; +class IntRect; +class PlatformMouseEvent; +class Node; +class RenderLayer; +class RenderObject; +class RenderPartObject; +class String; + +template <typename T> class Timer; + +class FrameView : public ScrollView { +public: + FrameView(Frame*); + + // On the Mac, FrameViews always get their size from the underlying NSView, + // so passing in a size is nonsensical. +#if !PLATFORM(MAC) + FrameView(Frame*, const IntSize& initialSize); +#endif + + virtual ~FrameView(); + + Frame* frame() const { return m_frame.get(); } + void clearFrame(); + + void ref() { ++m_refCount; } + void deref() { if (!--m_refCount) delete this; } + bool hasOneRef() { return m_refCount == 1; } + + int marginWidth() const { return m_margins.width(); } // -1 means default + int marginHeight() const { return m_margins.height(); } // -1 means default + void setMarginWidth(int); + void setMarginHeight(int); + + virtual void setVScrollbarMode(ScrollbarMode); + virtual void setHScrollbarMode(ScrollbarMode); + virtual void setScrollbarsMode(ScrollbarMode); + + void layout(bool allowSubtree = true); + bool didFirstLayout() const; + void layoutTimerFired(Timer<FrameView>*); + void scheduleRelayout(); + void scheduleRelayoutOfSubtree(RenderObject*); + void unscheduleRelayout(); + bool layoutPending() const; + + RenderObject* layoutRoot(bool onlyDuringLayout = false) const; + int layoutCount() const; + + // These two helper functions just pass through to the RenderView. + bool needsLayout() const; + void setNeedsLayout(); + + bool needsFullRepaint() const; + void repaintRectangle(const IntRect&, bool immediate); + void addRepaintInfo(RenderObject*, const IntRect&); + + void resetScrollbars(); + + void clear(); + + bool isTransparent() const; + void setTransparent(bool isTransparent); + + Color baseBackgroundColor() const; + void setBaseBackgroundColor(Color); + + void adjustViewSize(); + void initScrollbars(); + + virtual IntRect windowClipRect() const; + IntRect windowClipRect(bool clipToContents) const; + IntRect windowClipRectForLayer(const RenderLayer*, bool clipToLayerContents) const; + + virtual void scrollRectIntoViewRecursively(const IntRect&); + virtual void setContentsPos(int x, int y); + + String mediaType() const; + void setMediaType(const String&); + + void setUseSlowRepaints(); + + void addSlowRepaintObject(); + void removeSlowRepaintObject(); + + void updateDashboardRegions(); + void updateControlTints(); + + void restoreScrollbar(); + + void scheduleEvent(PassRefPtr<Event>, PassRefPtr<EventTargetNode>, bool tempEvent); + void pauseScheduledEvents(); + void resumeScheduledEvents(); + void postLayoutTimerFired(Timer<FrameView>*); + + bool wasScrolledByUser() const; + void setWasScrolledByUser(bool); + + void addWidgetToUpdate(RenderPartObject*); + void removeWidgetToUpdate(RenderPartObject*); + + // FIXME: This method should be used by all platforms, but currently depends on ScrollView::children, + // which not all methods have. Once FrameView and ScrollView are merged, this #if should be removed. +#if PLATFORM(WIN) || PLATFORM(GTK) || PLATFORM(QT) + void layoutIfNeededRecursive(); +#endif + +private: + void init(); + + virtual bool isFrameView() const; + + bool scrollTo(const IntRect&); + + bool useSlowRepaints() const; + + void applyOverflowToViewport(RenderObject*, ScrollbarMode& hMode, ScrollbarMode& vMode); + + void updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow); + + void dispatchScheduledEvents(); + void performPostLayoutTasks(); + + unsigned m_refCount; + IntSize m_size; + IntSize m_margins; + OwnPtr<HashSet<RenderPartObject*> > m_widgetUpdateSet; + RefPtr<Frame> m_frame; + FrameViewPrivate* d; +}; + +} + +#endif diff --git a/WebCore/page/GlobalHistory.h b/WebCore/page/GlobalHistory.h new file mode 100644 index 0000000..4857f6f --- /dev/null +++ b/WebCore/page/GlobalHistory.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GlobalHistory_h +#define GlobalHistory_h + +#include <wtf/unicode/Unicode.h> + +namespace WebCore { + + bool historyContains(const UChar* characters, unsigned length); + +} // namespace WebCore + +#endif // GlobalHistory_h diff --git a/WebCore/page/History.cpp b/WebCore/page/History.cpp new file mode 100644 index 0000000..2527132 --- /dev/null +++ b/WebCore/page/History.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "History.h" + +#include "Frame.h" +#include "FrameLoader.h" + +namespace WebCore { + +History::History(Frame* frame) + : m_frame(frame) +{ +} + +Frame* History::frame() const +{ + return m_frame; +} + +void History::disconnectFrame() +{ + m_frame = 0; +} + +unsigned History::length() const +{ + if (!m_frame) + return 0; + return m_frame->loader()->getHistoryLength(); +} + +void History::back() +{ + if (!m_frame) + return; + m_frame->loader()->scheduleHistoryNavigation(-1); +} + +void History::forward() +{ + if (!m_frame) + return; + m_frame->loader()->scheduleHistoryNavigation(1); +} + +void History::go(int distance) +{ + if (!m_frame) + return; + m_frame->loader()->scheduleHistoryNavigation(distance); +} + +} // namespace WebCore diff --git a/WebCore/page/History.h b/WebCore/page/History.h new file mode 100644 index 0000000..f0df2de --- /dev/null +++ b/WebCore/page/History.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef History_h +#define History_h + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + + class Frame; + + class History : public RefCounted<History> { + public: + static PassRefPtr<History> create(Frame* frame) { return adoptRef(new History(frame)); } + + Frame* frame() const; + void disconnectFrame(); + + unsigned length() const; + void back(); + void forward(); + void go(int distance); + + private: + History(Frame*); + + Frame* m_frame; + }; + +} // namespace WebCore + +#endif // History_h diff --git a/WebCore/page/History.idl b/WebCore/page/History.idl new file mode 100644 index 0000000..e86cf92 --- /dev/null +++ b/WebCore/page/History.idl @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module window { + + interface [ + CustomGetOwnPropertySlot, + CustomPutFunction, + CustomDeleteProperty, + CustomGetPropertyNames + ] History { + readonly attribute unsigned long length; + + void back(); + void forward(); + void go(in long distance); + }; + +} diff --git a/WebCore/page/InspectorClient.h b/WebCore/page/InspectorClient.h new file mode 100644 index 0000000..ec1ee92 --- /dev/null +++ b/WebCore/page/InspectorClient.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef InspectorClient_h +#define InspectorClient_h + +namespace WebCore { + +class Node; +class Page; +class String; + +class InspectorClient { +public: + virtual ~InspectorClient() { } + + virtual void inspectorDestroyed() = 0; + + virtual Page* createPage() = 0; + + virtual String localizedStringsURL() = 0; + + virtual void showWindow() = 0; + virtual void closeWindow() = 0; + + virtual void attachWindow() = 0; + virtual void detachWindow() = 0; + + virtual void highlight(Node*) = 0; + virtual void hideHighlight() = 0; + + virtual void inspectedURLChanged(const String& newURL) = 0; +}; + +} // namespace WebCore + +#endif // !defined(InspectorClient_h) diff --git a/WebCore/page/InspectorController.cpp b/WebCore/page/InspectorController.cpp new file mode 100644 index 0000000..ba08f01 --- /dev/null +++ b/WebCore/page/InspectorController.cpp @@ -0,0 +1,1653 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "InspectorController.h" + +#include "CString.h" +#include "CachedResource.h" +#include "DocLoader.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Element.h" +#include "FloatConversion.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HTMLFrameOwnerElement.h" +#include "InspectorClient.h" +#include "JSRange.h" +#include "Page.h" +#include "Range.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "SystemTime.h" +#include "TextEncoding.h" +#include "TextIterator.h" +#include "kjs_dom.h" +#include "kjs_proxy.h" +#include "kjs_window.h" +#include <JavaScriptCore/APICast.h> +#include <JavaScriptCore/JSLock.h> +#include <JavaScriptCore/JSRetainPtr.h> +#include <JavaScriptCore/JSStringRef.h> +#include <wtf/RefCounted.h> + +#if ENABLE(DATABASE) +#include "Database.h" +#include "JSDatabase.h" +#endif + +namespace WebCore { + +static JSValueRef callSimpleFunction(JSContextRef context, JSObjectRef thisObject, const char* functionName) +{ + ASSERT_ARG(context, context); + ASSERT_ARG(thisObject, thisObject); + + JSRetainPtr<JSStringRef> functionNameString(Adopt, JSStringCreateWithUTF8CString(functionName)); + JSObjectRef function = JSValueToObject(context, JSObjectGetProperty(context, thisObject, functionNameString.get(), 0), 0); + + return JSObjectCallAsFunction(context, function, thisObject, 0, 0, 0); +} + +#pragma mark - +#pragma mark ConsoleMessage Struct + +struct ConsoleMessage { + ConsoleMessage(MessageSource s, MessageLevel l, const String& m, unsigned li, const String& u) + : source(s) + , level(l) + , message(m) + , line(li) + , url(u) + { + } + + MessageSource source; + MessageLevel level; + String message; + unsigned line; + String url; +}; + +#pragma mark - +#pragma mark InspectorResource Struct + +struct InspectorResource : public RefCounted<InspectorResource> { + // Keep these in sync with WebInspector.Resource.Type + enum Type { + Doc, + Stylesheet, + Image, + Font, + Script, + Other + }; + + static PassRefPtr<InspectorResource> create(long long identifier, DocumentLoader* documentLoader, Frame* frame) + { + return adoptRef(new InspectorResource(identifier, documentLoader, frame)); + } + + ~InspectorResource() + { + setScriptObject(0, 0); + } + + Type type() const + { + if (requestURL == loader->requestURL()) + return Doc; + + if (loader->frameLoader() && requestURL == loader->frameLoader()->iconURL()) + return Image; + + CachedResource* cachedResource = frame->document()->docLoader()->cachedResource(requestURL.string()); + if (!cachedResource) + return Other; + + switch (cachedResource->type()) { + case CachedResource::ImageResource: + return Image; + case CachedResource::FontResource: + return Font; + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return Stylesheet; + case CachedResource::Script: + return Script; + default: + return Other; + } + } + + void setScriptObject(JSContextRef context, JSObjectRef newScriptObject) + { + if (scriptContext && scriptObject) + JSValueUnprotect(scriptContext, scriptObject); + + scriptObject = newScriptObject; + scriptContext = context; + + ASSERT((context && newScriptObject) || (!context && !newScriptObject)); + if (context && newScriptObject) + JSValueProtect(context, newScriptObject); + } + + long long identifier; + RefPtr<DocumentLoader> loader; + RefPtr<Frame> frame; + KURL requestURL; + HTTPHeaderMap requestHeaderFields; + HTTPHeaderMap responseHeaderFields; + String mimeType; + String suggestedFilename; + JSContextRef scriptContext; + JSObjectRef scriptObject; + long long expectedContentLength; + bool cached; + bool finished; + bool failed; + int length; + int responseStatusCode; + double startTime; + double responseReceivedTime; + double endTime; + +private: + InspectorResource(long long identifier, DocumentLoader* documentLoader, Frame* frame) + : identifier(identifier) + , loader(documentLoader) + , frame(frame) + , scriptContext(0) + , scriptObject(0) + , expectedContentLength(0) + , cached(false) + , finished(false) + , failed(false) + , length(0) + , responseStatusCode(0) + , startTime(-1.0) + , responseReceivedTime(-1.0) + , endTime(-1.0) + { + } +}; + +#pragma mark - +#pragma mark InspectorDatabaseResource Struct + +#if ENABLE(DATABASE) +struct InspectorDatabaseResource : public RefCounted<InspectorDatabaseResource> { + static PassRefPtr<InspectorDatabaseResource> create(Database* database, const String& domain, const String& name, const String& version) + { + return adoptRef(new InspectorDatabaseResource(database, domain, name, version)); + } + + void setScriptObject(JSContextRef context, JSObjectRef newScriptObject) + { + if (scriptContext && scriptObject) + JSValueUnprotect(scriptContext, scriptObject); + + scriptObject = newScriptObject; + scriptContext = context; + + ASSERT((context && newScriptObject) || (!context && !newScriptObject)); + if (context && newScriptObject) + JSValueProtect(context, newScriptObject); + } + + RefPtr<Database> database; + String domain; + String name; + String version; + JSContextRef scriptContext; + JSObjectRef scriptObject; + +private: + InspectorDatabaseResource(Database* database, const String& domain, const String& name, const String& version) + : database(database) + , domain(domain) + , name(name) + , version(version) + , scriptContext(0) + , scriptObject(0) + { + } +}; +#endif + +#pragma mark - +#pragma mark JavaScript Callbacks + +static JSValueRef addSourceToFrame(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + JSValueRef undefined = JSValueMakeUndefined(ctx); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount < 2 || !controller) + return undefined; + + JSValueRef identifierValue = arguments[0]; + if (!JSValueIsNumber(ctx, identifierValue)) + return undefined; + + unsigned long identifier = static_cast<unsigned long>(JSValueToNumber(ctx, identifierValue, 0)); + RefPtr<InspectorResource> resource = controller->resources().get(identifier); + ASSERT(resource); + if (!resource) + return undefined; + + RefPtr<SharedBuffer> buffer; + String textEncodingName; + if (resource->requestURL == resource->loader->requestURL()) { + buffer = resource->loader->mainResourceData(); + textEncodingName = resource->frame->document()->inputEncoding(); + } else { + CachedResource* cachedResource = resource->frame->document()->docLoader()->cachedResource(resource->requestURL.string()); + if (!cachedResource) + return undefined; + + buffer = cachedResource->data(); + textEncodingName = cachedResource->encoding(); + } + + if (!buffer) + return undefined; + + TextEncoding encoding(textEncodingName); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + String sourceString = encoding.decode(buffer->data(), buffer->size()); + + Node* node = toNode(toJS(arguments[1])); + ASSERT(node); + if (!node) + return undefined; + + if (!node->attached()) { + ASSERT_NOT_REACHED(); + return undefined; + } + + ASSERT(node->isElementNode()); + if (!node->isElementNode()) + return undefined; + + Element* element = static_cast<Element*>(node); + ASSERT(element->isFrameOwnerElement()); + if (!element->isFrameOwnerElement()) + return undefined; + + HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(element); + ASSERT(frameOwner->contentFrame()); + if (!frameOwner->contentFrame()) + return undefined; + + FrameLoader* loader = frameOwner->contentFrame()->loader(); + + loader->setResponseMIMEType(resource->mimeType); + loader->begin(); + loader->write(sourceString); + loader->end(); + + return undefined; +} + +static JSValueRef getResourceDocumentNode(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + JSValueRef undefined = JSValueMakeUndefined(ctx); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!argumentCount || argumentCount > 1 || !controller) + return undefined; + + JSValueRef identifierValue = arguments[0]; + if (!JSValueIsNumber(ctx, identifierValue)) + return undefined; + + unsigned long identifier = static_cast<unsigned long>(JSValueToNumber(ctx, identifierValue, 0)); + RefPtr<InspectorResource> resource = controller->resources().get(identifier); + ASSERT(resource); + if (!resource) + return undefined; + + Document* document = resource->frame->document(); + if (!document) + return undefined; + + if (document->isPluginDocument() || document->isImageDocument()) + return undefined; + + KJS::JSLock lock; + JSValueRef documentValue = toRef(toJS(toJS(controller->scriptContext()), document)); + return documentValue; +} + +static JSValueRef highlightDOMNode(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + JSValueRef undefined = JSValueMakeUndefined(context); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount < 1 || !controller) + return undefined; + + Node* node = toNode(toJS(arguments[0])); + if (!node) + return undefined; + + controller->highlight(node); + + return undefined; +} + +static JSValueRef hideDOMNodeHighlight(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + JSValueRef undefined = JSValueMakeUndefined(context); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount || !controller) + return undefined; + + controller->hideHighlight(); + + return undefined; +} + +static JSValueRef loaded(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + controller->scriptObjectReady(); + return JSValueMakeUndefined(ctx); +} + +static JSValueRef unloading(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + controller->close(); + return JSValueMakeUndefined(ctx); +} + +static JSValueRef attach(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + controller->attachWindow(); + return JSValueMakeUndefined(ctx); +} + +static JSValueRef detach(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + controller->detachWindow(); + return JSValueMakeUndefined(ctx); +} + +static JSValueRef search(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2 || !JSValueIsString(ctx, arguments[1])) + return JSValueMakeUndefined(ctx); + + Node* node = toNode(toJS(arguments[0])); + if (!node) + return JSValueMakeUndefined(ctx); + + JSRetainPtr<JSStringRef> searchString(Adopt, JSValueToStringCopy(ctx, arguments[1], 0)); + String target(JSStringGetCharactersPtr(searchString.get()), JSStringGetLength(searchString.get())); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + JSRetainPtr<JSStringRef> arrayString(Adopt, JSStringCreateWithUTF8CString("Array")); + JSObjectRef arrayConstructor = JSValueToObject(ctx, JSObjectGetProperty(ctx, global, arrayString.get(), 0), 0); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, 0); + + JSRetainPtr<JSStringRef> pushString(Adopt, JSStringCreateWithUTF8CString("push")); + JSObjectRef pushFunction = JSValueToObject(ctx, JSObjectGetProperty(ctx, result, pushString.get(), 0), 0); + + RefPtr<Range> searchRange(rangeOfContents(node)); + + int exception = 0; + do { + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, false)); + if (resultRange->collapsed(exception)) + break; + + // A non-collapsed result range can in some funky whitespace cases still not + // advance the range's start position (4509328). Break to avoid infinite loop. + VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM); + if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) + break; + + KJS::JSLock lock; + JSValueRef arg0 = toRef(toJS(toJS(ctx), resultRange.get())); + JSObjectCallAsFunction(ctx, pushFunction, result, 1, &arg0, 0); + + setStart(searchRange.get(), newStart); + } while (true); + + return result; +} + +#if ENABLE(DATABASE) +static JSValueRef databaseTableNames(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + Database* database = toDatabase(toJS(arguments[0])); + if (!database) + return JSValueMakeUndefined(ctx); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + JSRetainPtr<JSStringRef> arrayString(Adopt, JSStringCreateWithUTF8CString("Array")); + JSObjectRef arrayConstructor = JSValueToObject(ctx, JSObjectGetProperty(ctx, global, arrayString.get(), 0), 0); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, 0); + + JSRetainPtr<JSStringRef> pushString(Adopt, JSStringCreateWithUTF8CString("push")); + JSObjectRef pushFunction = JSValueToObject(ctx, JSObjectGetProperty(ctx, result, pushString.get(), 0), 0); + + Vector<String> tableNames = database->tableNames(); + unsigned length = tableNames.size(); + for (unsigned i = 0; i < length; ++i) { + String tableName = tableNames[i]; + JSRetainPtr<JSStringRef> tableNameString(Adopt, JSStringCreateWithCharacters(tableName.characters(), tableName.length())); + JSValueRef tableNameValue = JSValueMakeString(ctx, tableNameString.get()); + + JSValueRef pushArguments[] = { tableNameValue }; + JSObjectCallAsFunction(ctx, pushFunction, result, 1, pushArguments, 0); + } + + return result; +} +#endif + +static JSValueRef inspectedWindow(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + return toRef(KJS::Window::retrieve(controller->inspectedPage()->mainFrame())); +} + +static JSValueRef localizedStrings(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + String url = controller->localizedStringsURL(); + if (url.isNull()) + return JSValueMakeNull(ctx); + + JSRetainPtr<JSStringRef> urlString(Adopt, JSStringCreateWithCharacters(url.characters(), url.length())); + return JSValueMakeString(ctx, urlString.get()); +} + +static JSValueRef platform(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ +#if PLATFORM(MAC) +#ifdef BUILDING_ON_TIGER + static const String platform = "mac-tiger"; +#else + static const String platform = "mac-leopard"; +#endif +#elif PLATFORM(WIN_OS) + static const String platform = "windows"; +#elif PLATFORM(QT) + static const String platform = "qt"; +#elif PLATFORM(GTK) + static const String platform = "gtk"; +#elif PLATFORM(WX) + static const String platform = "wx"; +#else + static const String platform = "unknown"; +#endif + + JSRetainPtr<JSStringRef> platformString(Adopt, JSStringCreateWithCharacters(platform.characters(), platform.length())); + JSValueRef platformValue = JSValueMakeString(ctx, platformString.get()); + + return platformValue; +} + +static JSValueRef moveByUnrestricted(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2) + return JSValueMakeUndefined(ctx); + + controller->moveWindowBy(narrowPrecisionToFloat(JSValueToNumber(ctx, arguments[0], 0)), narrowPrecisionToFloat(JSValueToNumber(ctx, arguments[1], 0))); + + return JSValueMakeUndefined(ctx); +} + +#pragma mark - +#pragma mark InspectorController Class + +InspectorController::InspectorController(Page* page, InspectorClient* client) + : m_inspectedPage(page) + , m_client(client) + , m_page(0) + , m_scriptObject(0) + , m_controllerScriptObject(0) + , m_scriptContext(0) + , m_windowVisible(false) + , m_showAfterVisible(FocusedNodeDocumentPanel) + , m_nextIdentifier(-2) +{ + ASSERT_ARG(page, page); + ASSERT_ARG(client, client); +} + +InspectorController::~InspectorController() +{ + m_client->inspectorDestroyed(); + + if (m_scriptContext) { + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + JSRetainPtr<JSStringRef> controllerProperty(Adopt, JSStringCreateWithUTF8CString("InspectorController")); + JSObjectRef controller = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, global, controllerProperty.get(), 0), 0); + if (controller) + JSObjectSetPrivate(controller, 0); + } + + if (m_page) + m_page->setParentInspectorController(0); + + deleteAllValues(m_frameResources); + deleteAllValues(m_consoleMessages); +} + +bool InspectorController::enabled() const +{ + return m_inspectedPage->settings()->developerExtrasEnabled(); +} + +String InspectorController::localizedStringsURL() +{ + if (!enabled()) + return String(); + return m_client->localizedStringsURL(); +} + +// Trying to inspect something in a frame with JavaScript disabled would later lead to +// crashes trying to create JavaScript wrappers. Some day we could fix this issue, but +// for now prevent crashes here by never targeting a node in such a frame. +static bool canPassNodeToJavaScript(Node* node) +{ + if (!node) + return false; + Frame* frame = node->document()->frame(); + return frame && frame->scriptProxy()->isEnabled(); +} + +void InspectorController::inspect(Node* node) +{ + if (!canPassNodeToJavaScript(node) || !enabled()) + return; + + show(); + + if (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE) + node = node->parentNode(); + m_nodeToFocus = node; + + if (!m_scriptObject) { + m_showAfterVisible = FocusedNodeDocumentPanel; + return; + } + + if (windowVisible()) + focusNode(); +} + +void InspectorController::focusNode() +{ + if (!enabled()) + return; + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + ASSERT(m_nodeToFocus); + + JSValueRef arg0; + + { + KJS::JSLock lock; + arg0 = toRef(toJS(toJS(m_scriptContext), m_nodeToFocus.get())); + } + + m_nodeToFocus = 0; + + JSRetainPtr<JSStringRef> functionProperty(Adopt, JSStringCreateWithUTF8CString("updateFocusedNode")); + JSObjectRef function = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, functionProperty.get(), 0), 0); + ASSERT(function); + + JSObjectCallAsFunction(m_scriptContext, function, m_scriptObject, 1, &arg0, 0); +} + +void InspectorController::highlight(Node* node) +{ + if (!enabled()) + return; + ASSERT_ARG(node, node); + m_highlightedNode = node; + m_client->highlight(node); +} + +void InspectorController::hideHighlight() +{ + if (!enabled()) + return; + m_client->hideHighlight(); +} + +bool InspectorController::windowVisible() +{ + return m_windowVisible; +} + +void InspectorController::setWindowVisible(bool visible) +{ + if (visible == m_windowVisible) + return; + + m_windowVisible = visible; + + if (!m_scriptContext || !m_scriptObject) + return; + + if (m_windowVisible) { + populateScriptResources(); + if (m_nodeToFocus) + focusNode(); + if (m_showAfterVisible == ConsolePanel) + showConsole(); + else if (m_showAfterVisible == TimelinePanel) + showTimeline(); + } else { + clearScriptResources(); + clearScriptConsoleMessages(); + clearDatabaseScriptResources(); + clearNetworkTimeline(); + } + + m_showAfterVisible = FocusedNodeDocumentPanel; +} + +void InspectorController::addMessageToConsole(MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID) +{ + if (!enabled()) + return; + + ConsoleMessage* consoleMessage = new ConsoleMessage(source, level, message, lineNumber, sourceID); + m_consoleMessages.append(consoleMessage); + + if (windowVisible()) + addScriptConsoleMessage(consoleMessage); +} + +void InspectorController::attachWindow() +{ + if (!enabled()) + return; + m_client->attachWindow(); +} + +void InspectorController::detachWindow() +{ + if (!enabled()) + return; + m_client->detachWindow(); +} + +void InspectorController::windowScriptObjectAvailable() +{ + if (!m_page || !enabled()) + return; + + m_scriptContext = toRef(m_page->mainFrame()->scriptProxy()->globalObject()->globalExec()); + + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + ASSERT(global); + + static JSStaticFunction staticFunctions[] = { + { "addSourceToFrame", addSourceToFrame, kJSPropertyAttributeNone }, + { "getResourceDocumentNode", getResourceDocumentNode, kJSPropertyAttributeNone }, + { "highlightDOMNode", highlightDOMNode, kJSPropertyAttributeNone }, + { "hideDOMNodeHighlight", hideDOMNodeHighlight, kJSPropertyAttributeNone }, + { "loaded", loaded, kJSPropertyAttributeNone }, + { "windowUnloading", unloading, kJSPropertyAttributeNone }, + { "attach", attach, kJSPropertyAttributeNone }, + { "detach", detach, kJSPropertyAttributeNone }, + { "search", search, kJSPropertyAttributeNone }, +#if ENABLE(DATABASE) + { "databaseTableNames", databaseTableNames, kJSPropertyAttributeNone }, +#endif + { "inspectedWindow", inspectedWindow, kJSPropertyAttributeNone }, + { "localizedStringsURL", localizedStrings, kJSPropertyAttributeNone }, + { "platform", platform, kJSPropertyAttributeNone }, + { "moveByUnrestricted", moveByUnrestricted, kJSPropertyAttributeNone }, + { 0, 0, 0 } + }; + + JSClassDefinition inspectorControllerDefinition = { + 0, kJSClassAttributeNone, "InspectorController", 0, 0, staticFunctions, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + JSClassRef controllerClass = JSClassCreate(&inspectorControllerDefinition); + ASSERT(controllerClass); + + m_controllerScriptObject = JSObjectMake(m_scriptContext, controllerClass, reinterpret_cast<void*>(this)); + ASSERT(m_controllerScriptObject); + + JSRetainPtr<JSStringRef> controllerObjectString(Adopt, JSStringCreateWithUTF8CString("InspectorController")); + JSObjectSetProperty(m_scriptContext, global, controllerObjectString.get(), m_controllerScriptObject, kJSPropertyAttributeNone, 0); +} + +void InspectorController::scriptObjectReady() +{ + ASSERT(m_scriptContext); + if (!m_scriptContext) + return; + + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + ASSERT(global); + + JSRetainPtr<JSStringRef> inspectorString(Adopt, JSStringCreateWithUTF8CString("WebInspector")); + JSValueRef inspectorValue = JSObjectGetProperty(m_scriptContext, global, inspectorString.get(), 0); + + ASSERT(inspectorValue); + if (!inspectorValue) + return; + + m_scriptObject = JSValueToObject(m_scriptContext, inspectorValue, 0); + ASSERT(m_scriptObject); + + JSValueProtect(m_scriptContext, m_scriptObject); + + // Make sure our window is visible now that the page loaded + m_client->showWindow(); +} + +void InspectorController::show() +{ + if (!enabled()) + return; + + if (!m_page) { + m_page = m_client->createPage(); + if (!m_page) + return; + m_page->setParentInspectorController(this); + + // m_client->showWindow() will be called after the page loads in scriptObjectReady() + return; + } + + m_client->showWindow(); +} + +void InspectorController::showConsole() +{ + if (!enabled()) + return; + + show(); + + if (!m_scriptObject) { + m_showAfterVisible = ConsolePanel; + return; + } + + callSimpleFunction(m_scriptContext, m_scriptObject, "showConsole"); +} + +void InspectorController::showTimeline() +{ + if (!enabled()) + return; + + show(); + + if (!m_scriptObject) { + m_showAfterVisible = TimelinePanel; + return; + } + + callSimpleFunction(m_scriptContext, m_scriptObject, "showTimeline"); +} + +void InspectorController::close() +{ + if (!enabled()) + return; + + m_client->closeWindow(); + if (m_page) + m_page->setParentInspectorController(0); + + ASSERT(m_scriptContext && m_scriptObject); + JSValueUnprotect(m_scriptContext, m_scriptObject); + + m_page = 0; + m_scriptObject = 0; + m_scriptContext = 0; +} + +static void addHeaders(JSContextRef context, JSObjectRef object, const HTTPHeaderMap& headers) +{ + ASSERT_ARG(context, context); + ASSERT_ARG(object, object); + + HTTPHeaderMap::const_iterator end = headers.end(); + for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it) { + JSRetainPtr<JSStringRef> field(Adopt, JSStringCreateWithCharacters(it->first.characters(), it->first.length())); + JSRetainPtr<JSStringRef> valueString(Adopt, JSStringCreateWithCharacters(it->second.characters(), it->second.length())); + JSValueRef value = JSValueMakeString(context, valueString.get()); + JSObjectSetProperty(context, object, field.get(), value, kJSPropertyAttributeNone, 0); + } +} + +static JSObjectRef scriptObjectForRequest(JSContextRef context, const InspectorResource* resource) +{ + ASSERT_ARG(context, context); + + JSObjectRef object = JSObjectMake(context, 0, 0); + addHeaders(context, object, resource->requestHeaderFields); + + return object; +} + +static JSObjectRef scriptObjectForResponse(JSContextRef context, const InspectorResource* resource) +{ + ASSERT_ARG(context, context); + + JSObjectRef object = JSObjectMake(context, 0, 0); + addHeaders(context, object, resource->responseHeaderFields); + + return object; +} + +JSObjectRef InspectorController::addScriptResource(InspectorResource* resource) +{ + ASSERT_ARG(resource, resource); + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return 0; + + if (!resource->scriptObject) { + JSRetainPtr<JSStringRef> resourceString(Adopt, JSStringCreateWithUTF8CString("Resource")); + JSObjectRef resourceConstructor = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, resourceString.get(), 0), 0); + + String urlString = resource->requestURL.string(); + JSRetainPtr<JSStringRef> url(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef urlValue = JSValueMakeString(m_scriptContext, url.get()); + + urlString = resource->requestURL.host(); + JSRetainPtr<JSStringRef> domain(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef domainValue = JSValueMakeString(m_scriptContext, domain.get()); + + urlString = resource->requestURL.path(); + JSRetainPtr<JSStringRef> path(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef pathValue = JSValueMakeString(m_scriptContext, path.get()); + + urlString = resource->requestURL.lastPathComponent(); + JSRetainPtr<JSStringRef> lastPathComponent(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef lastPathComponentValue = JSValueMakeString(m_scriptContext, lastPathComponent.get()); + + JSValueRef identifier = JSValueMakeNumber(m_scriptContext, resource->identifier); + JSValueRef mainResource = JSValueMakeBoolean(m_scriptContext, m_mainResource == resource); + JSValueRef cached = JSValueMakeBoolean(m_scriptContext, resource->cached); + + JSValueRef arguments[] = { scriptObjectForRequest(m_scriptContext, resource), urlValue, domainValue, pathValue, lastPathComponentValue, identifier, mainResource, cached }; + JSObjectRef result = JSObjectCallAsConstructor(m_scriptContext, resourceConstructor, 8, arguments, 0); + ASSERT(result); + + resource->setScriptObject(m_scriptContext, result); + } + + JSRetainPtr<JSStringRef> addResourceString(Adopt, JSStringCreateWithUTF8CString("addResource")); + JSObjectRef addResourceFunction = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, addResourceString.get(), 0), 0); + + JSValueRef addArguments[] = { resource->scriptObject }; + JSObjectCallAsFunction(m_scriptContext, addResourceFunction, m_scriptObject, 1, addArguments, 0); + + return resource->scriptObject; +} + +JSObjectRef InspectorController::addAndUpdateScriptResource(InspectorResource* resource) +{ + ASSERT_ARG(resource, resource); + + JSObjectRef scriptResource = addScriptResource(resource); + updateScriptResourceResponse(resource); + updateScriptResource(resource, resource->length); + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource, resource->finished, resource->failed); + return scriptResource; +} + +void InspectorController::removeScriptResource(InspectorResource* resource) +{ + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return; + + ASSERT(resource); + ASSERT(resource->scriptObject); + if (!resource || !resource->scriptObject) + return; + + JSRetainPtr<JSStringRef> removeResourceString(Adopt, JSStringCreateWithUTF8CString("removeResource")); + JSObjectRef removeResourceFunction = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, removeResourceString.get(), 0), 0); + + JSValueRef arguments[] = { resource->scriptObject }; + JSObjectCallAsFunction(m_scriptContext, removeResourceFunction, m_scriptObject, 1, arguments, 0); + + resource->setScriptObject(0, 0); +} + +static void updateResourceRequest(InspectorResource* resource, const ResourceRequest& request) +{ + resource->requestHeaderFields = request.httpHeaderFields(); + resource->requestURL = request.url(); +} + +static void updateResourceResponse(InspectorResource* resource, const ResourceResponse& response) +{ + resource->expectedContentLength = response.expectedContentLength(); + resource->mimeType = response.mimeType(); + resource->responseHeaderFields = response.httpHeaderFields(); + resource->responseStatusCode = response.httpStatusCode(); + resource->suggestedFilename = response.suggestedFilename(); +} + +void InspectorController::updateScriptResourceRequest(InspectorResource* resource) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + String urlString = resource->requestURL.string(); + JSRetainPtr<JSStringRef> url(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef urlValue = JSValueMakeString(m_scriptContext, url.get()); + + urlString = resource->requestURL.host(); + JSRetainPtr<JSStringRef> domain(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef domainValue = JSValueMakeString(m_scriptContext, domain.get()); + + urlString = resource->requestURL.path(); + JSRetainPtr<JSStringRef> path(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef pathValue = JSValueMakeString(m_scriptContext, path.get()); + + urlString = resource->requestURL.lastPathComponent(); + JSRetainPtr<JSStringRef> lastPathComponent(Adopt, JSStringCreateWithCharacters(urlString.characters(), urlString.length())); + JSValueRef lastPathComponentValue = JSValueMakeString(m_scriptContext, lastPathComponent.get()); + + JSValueRef mainResourceValue = JSValueMakeBoolean(m_scriptContext, m_mainResource == resource); + + JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString("url")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), urlValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("domain")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), domainValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("path")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), pathValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("lastPathComponent")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), lastPathComponentValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("requestHeaders")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), scriptObjectForRequest(m_scriptContext, resource), kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("mainResource")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), mainResourceValue, kJSPropertyAttributeNone, 0); +} + +void InspectorController::updateScriptResourceResponse(InspectorResource* resource) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSRetainPtr<JSStringRef> mimeType(Adopt, JSStringCreateWithCharacters(resource->mimeType.characters(), resource->mimeType.length())); + JSValueRef mimeTypeValue = JSValueMakeString(m_scriptContext, mimeType.get()); + + JSRetainPtr<JSStringRef> suggestedFilename(Adopt, JSStringCreateWithCharacters(resource->suggestedFilename.characters(), resource->suggestedFilename.length())); + JSValueRef suggestedFilenameValue = JSValueMakeString(m_scriptContext, suggestedFilename.get()); + + JSValueRef expectedContentLengthValue = JSValueMakeNumber(m_scriptContext, static_cast<double>(resource->expectedContentLength)); + JSValueRef statusCodeValue = JSValueMakeNumber(m_scriptContext, resource->responseStatusCode); + + JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString("mimeType")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), mimeTypeValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("suggestedFilename")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), suggestedFilenameValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("expectedContentLength")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), expectedContentLengthValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("statusCode")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), statusCodeValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("responseHeaders")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), scriptObjectForResponse(m_scriptContext, resource), kJSPropertyAttributeNone, 0); + + JSValueRef typeValue = JSValueMakeNumber(m_scriptContext, resource->type()); + propertyName.adopt(JSStringCreateWithUTF8CString("type")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), typeValue, kJSPropertyAttributeNone, 0); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, int length) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef lengthValue = JSValueMakeNumber(m_scriptContext, length); + + JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString("contentLength")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), lengthValue, kJSPropertyAttributeNone, 0); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, bool finished, bool failed) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef failedValue = JSValueMakeBoolean(m_scriptContext, failed); + JSValueRef finishedValue = JSValueMakeBoolean(m_scriptContext, finished); + + JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString("failed")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), failedValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("finished")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), finishedValue, kJSPropertyAttributeNone, 0); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, double startTime, double responseReceivedTime, double endTime) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef startTimeValue = JSValueMakeNumber(m_scriptContext, startTime); + JSValueRef responseReceivedTimeValue = JSValueMakeNumber(m_scriptContext, responseReceivedTime); + JSValueRef endTimeValue = JSValueMakeNumber(m_scriptContext, endTime); + + JSRetainPtr<JSStringRef> propertyName(Adopt, JSStringCreateWithUTF8CString("startTime")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), startTimeValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("responseReceivedTime")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), responseReceivedTimeValue, kJSPropertyAttributeNone, 0); + + propertyName.adopt(JSStringCreateWithUTF8CString("endTime")); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, propertyName.get(), endTimeValue, kJSPropertyAttributeNone, 0); +} + +void InspectorController::populateScriptResources() +{ + ASSERT(m_scriptContext); + if (!m_scriptContext) + return; + + clearScriptResources(); + clearScriptConsoleMessages(); + clearDatabaseScriptResources(); + clearNetworkTimeline(); + + ResourcesMap::iterator resourcesEnd = m_resources.end(); + for (ResourcesMap::iterator it = m_resources.begin(); it != resourcesEnd; ++it) + addAndUpdateScriptResource(it->second.get()); + + unsigned messageCount = m_consoleMessages.size(); + for (unsigned i = 0; i < messageCount; ++i) + addScriptConsoleMessage(m_consoleMessages[i]); + +#if ENABLE(DATABASE) + DatabaseResourcesSet::iterator databasesEnd = m_databaseResources.end(); + for (DatabaseResourcesSet::iterator it = m_databaseResources.begin(); it != databasesEnd; ++it) + addDatabaseScriptResource((*it).get()); +#endif +} + +#if ENABLE(DATABASE) +JSObjectRef InspectorController::addDatabaseScriptResource(InspectorDatabaseResource* resource) +{ + ASSERT_ARG(resource, resource); + + if (resource->scriptObject) + return resource->scriptObject; + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return 0; + + JSRetainPtr<JSStringRef> databaseString(Adopt, JSStringCreateWithUTF8CString("Database")); + JSObjectRef databaseConstructor = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, databaseString.get(), 0), 0); + + JSValueRef database; + + { + KJS::JSLock lock; + database = toRef(toJS(toJS(m_scriptContext), resource->database.get())); + } + + JSRetainPtr<JSStringRef> domain(Adopt, JSStringCreateWithCharacters(resource->domain.characters(), resource->domain.length())); + JSValueRef domainValue = JSValueMakeString(m_scriptContext, domain.get()); + + JSRetainPtr<JSStringRef> name(Adopt, JSStringCreateWithCharacters(resource->name.characters(), resource->name.length())); + JSValueRef nameValue = JSValueMakeString(m_scriptContext, name.get()); + + JSRetainPtr<JSStringRef> version(Adopt, JSStringCreateWithCharacters(resource->version.characters(), resource->version.length())); + JSValueRef versionValue = JSValueMakeString(m_scriptContext, version.get()); + + JSValueRef arguments[] = { database, domainValue, nameValue, versionValue }; + JSObjectRef result = JSObjectCallAsConstructor(m_scriptContext, databaseConstructor, 4, arguments, 0); + + resource->setScriptObject(m_scriptContext, result); + + ASSERT(result); + + JSRetainPtr<JSStringRef> addResourceString(Adopt, JSStringCreateWithUTF8CString("addResource")); + JSObjectRef addResourceFunction = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, addResourceString.get(), 0), 0); + + JSValueRef addArguments[] = { result }; + JSObjectCallAsFunction(m_scriptContext, addResourceFunction, m_scriptObject, 1, addArguments, 0); + + return result; +} + +void InspectorController::removeDatabaseScriptResource(InspectorDatabaseResource* resource) +{ + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return; + + ASSERT(resource); + ASSERT(resource->scriptObject); + if (!resource || !resource->scriptObject) + return; + + JSRetainPtr<JSStringRef> removeResourceString(Adopt, JSStringCreateWithUTF8CString("removeResource")); + JSObjectRef removeResourceFunction = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, removeResourceString.get(), 0), 0); + + JSValueRef arguments[] = { resource->scriptObject }; + JSObjectCallAsFunction(m_scriptContext, removeResourceFunction, m_scriptObject, 1, arguments, 0); + + resource->setScriptObject(0, 0); +} +#endif + +void InspectorController::addScriptConsoleMessage(const ConsoleMessage* message) +{ + ASSERT_ARG(message, message); + + JSRetainPtr<JSStringRef> messageConstructorString(Adopt, JSStringCreateWithUTF8CString("ConsoleMessage")); + JSObjectRef messageConstructor = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, messageConstructorString.get(), 0), 0); + + JSRetainPtr<JSStringRef> addMessageString(Adopt, JSStringCreateWithUTF8CString("addMessageToConsole")); + JSObjectRef addMessage = JSValueToObject(m_scriptContext, JSObjectGetProperty(m_scriptContext, m_scriptObject, addMessageString.get(), 0), 0); + + JSValueRef sourceValue = JSValueMakeNumber(m_scriptContext, message->source); + JSValueRef levelValue = JSValueMakeNumber(m_scriptContext, message->level); + JSRetainPtr<JSStringRef> messageString(Adopt, JSStringCreateWithCharacters(message->message.characters(), message->message.length())); + JSValueRef messageValue = JSValueMakeString(m_scriptContext, messageString.get()); + JSValueRef lineValue = JSValueMakeNumber(m_scriptContext, message->line); + JSRetainPtr<JSStringRef> urlString(Adopt, JSStringCreateWithCharacters(message->url.characters(), message->url.length())); + JSValueRef urlValue = JSValueMakeString(m_scriptContext, urlString.get()); + + JSValueRef args[] = { sourceValue, levelValue, messageValue, lineValue, urlValue }; + JSObjectRef messageObject = JSObjectCallAsConstructor(m_scriptContext, messageConstructor, 5, args, 0); + + JSObjectCallAsFunction(m_scriptContext, addMessage, m_scriptObject, 1, &messageObject, 0); +} + +void InspectorController::clearScriptResources() +{ + if (!m_scriptContext || !m_scriptObject) + return; + + ResourcesMap::iterator resourcesEnd = m_resources.end(); + for (ResourcesMap::iterator it = m_resources.begin(); it != resourcesEnd; ++it) { + InspectorResource* resource = it->second.get(); + resource->setScriptObject(0, 0); + } + + callSimpleFunction(m_scriptContext, m_scriptObject, "clearResources"); +} + +void InspectorController::clearDatabaseScriptResources() +{ +#if ENABLE(DATABASE) + if (!m_scriptContext || !m_scriptObject) + return; + + DatabaseResourcesSet::iterator databasesEnd = m_databaseResources.end(); + for (DatabaseResourcesSet::iterator it = m_databaseResources.begin(); it != databasesEnd; ++it) { + InspectorDatabaseResource* resource = (*it).get(); + resource->setScriptObject(0, 0); + } + + callSimpleFunction(m_scriptContext, m_scriptObject, "clearDatabaseResources"); +#endif +} + +void InspectorController::clearScriptConsoleMessages() +{ + if (!m_scriptContext || !m_scriptObject) + return; + + callSimpleFunction(m_scriptContext, m_scriptObject, "clearConsoleMessages"); +} + +void InspectorController::clearNetworkTimeline() +{ + if (!m_scriptContext || !m_scriptObject) + return; + + callSimpleFunction(m_scriptContext, m_scriptObject, "clearNetworkTimeline"); +} + +void InspectorController::pruneResources(ResourcesMap* resourceMap, DocumentLoader* loaderToKeep) +{ + ASSERT_ARG(resourceMap, resourceMap); + + ResourcesMap mapCopy(*resourceMap); + ResourcesMap::iterator end = mapCopy.end(); + for (ResourcesMap::iterator it = mapCopy.begin(); it != end; ++it) { + InspectorResource* resource = (*it).second.get(); + if (resource == m_mainResource) + continue; + + if (!loaderToKeep || resource->loader != loaderToKeep) { + removeResource(resource); + if (windowVisible() && resource->scriptObject) + removeScriptResource(resource); + } + } +} + +void InspectorController::didCommitLoad(DocumentLoader* loader) +{ + if (!enabled()) + return; + + if (loader->frame() == m_inspectedPage->mainFrame()) { + m_client->inspectedURLChanged(loader->url().string()); + + deleteAllValues(m_consoleMessages); + m_consoleMessages.clear(); + +#if ENABLE(DATABASE) + m_databaseResources.clear(); +#endif + + if (windowVisible()) { + clearScriptConsoleMessages(); +#if ENABLE(DATABASE) + clearDatabaseScriptResources(); +#endif + clearNetworkTimeline(); + + if (!loader->isLoadingFromCachedPage()) { + ASSERT(m_mainResource && m_mainResource->loader == loader); + // We don't add the main resource until its load is committed. This is + // needed to keep the load for a user-entered URL from showing up in the + // list of resources for the page they are navigating away from. + addAndUpdateScriptResource(m_mainResource.get()); + } else { + // Pages loaded from the page cache are committed before + // m_mainResource is the right resource for this load, so we + // clear it here. It will be re-assigned in + // identifierForInitialRequest. + m_mainResource = 0; + } + } + } + + for (Frame* frame = loader->frame(); frame; frame = frame->tree()->traverseNext(loader->frame())) + if (ResourcesMap* resourceMap = m_frameResources.get(frame)) + pruneResources(resourceMap, loader); +} + +void InspectorController::frameDetachedFromParent(Frame* frame) +{ + if (!enabled()) + return; + if (ResourcesMap* resourceMap = m_frameResources.get(frame)) + removeAllResources(resourceMap); +} + +void InspectorController::addResource(InspectorResource* resource) +{ + m_resources.set(resource->identifier, resource); + + Frame* frame = resource->frame.get(); + ResourcesMap* resourceMap = m_frameResources.get(frame); + if (resourceMap) + resourceMap->set(resource->identifier, resource); + else { + resourceMap = new ResourcesMap; + resourceMap->set(resource->identifier, resource); + m_frameResources.set(frame, resourceMap); + } +} + +void InspectorController::removeResource(InspectorResource* resource) +{ + m_resources.remove(resource->identifier); + + Frame* frame = resource->frame.get(); + ResourcesMap* resourceMap = m_frameResources.get(frame); + if (!resourceMap) { + ASSERT_NOT_REACHED(); + return; + } + + resourceMap->remove(resource->identifier); + if (resourceMap->isEmpty()) { + m_frameResources.remove(frame); + delete resourceMap; + } +} + +void InspectorController::didLoadResourceFromMemoryCache(DocumentLoader* loader, const ResourceRequest& request, const ResourceResponse& response, int length) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = InspectorResource::create(m_nextIdentifier--, loader, loader->frame()); + resource->finished = true; + + updateResourceRequest(resource.get(), request); + updateResourceResponse(resource.get(), response); + + resource->length = length; + resource->cached = true; + resource->startTime = currentTime(); + resource->responseReceivedTime = resource->startTime; + resource->endTime = resource->startTime; + + if (loader->frame() == m_inspectedPage->mainFrame() && request.url() == loader->requestURL()) + m_mainResource = resource; + + addResource(resource.get()); + + if (windowVisible()) + addAndUpdateScriptResource(resource.get()); +} + +void InspectorController::identifierForInitialRequest(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = InspectorResource::create(identifier, loader, loader->frame()); + + updateResourceRequest(resource.get(), request); + + if (loader->frame() == m_inspectedPage->mainFrame() && request.url() == loader->requestURL()) + m_mainResource = resource; + + addResource(resource.get()); + + if (windowVisible() && loader->isLoadingFromCachedPage() && resource == m_mainResource) + addAndUpdateScriptResource(resource.get()); +} + +void InspectorController::willSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + resource->startTime = currentTime(); + + if (!redirectResponse.isNull()) { + updateResourceRequest(resource, request); + updateResourceResponse(resource, redirectResponse); + } + + if (resource != m_mainResource && windowVisible()) { + if (!resource->scriptObject) + addScriptResource(resource); + else + updateScriptResourceRequest(resource); + + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + + if (!redirectResponse.isNull()) + updateScriptResourceResponse(resource); + } +} + +void InspectorController::didReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse& response) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + updateResourceResponse(resource, response); + + resource->responseReceivedTime = currentTime(); + + if (windowVisible() && resource->scriptObject) { + updateScriptResourceResponse(resource); + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + } +} + +void InspectorController::didReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + resource->length += lengthReceived; + + if (windowVisible() && resource->scriptObject) + updateScriptResource(resource, resource->length); +} + +void InspectorController::didFinishLoading(DocumentLoader* loader, unsigned long identifier) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = m_resources.get(identifier); + if (!resource) + return; + + removeResource(resource.get()); + + resource->finished = true; + resource->endTime = currentTime(); + + addResource(resource.get()); + + if (windowVisible() && resource->scriptObject) { + updateScriptResource(resource.get(), resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource.get(), resource->finished); + } +} + +void InspectorController::didFailLoading(DocumentLoader* loader, unsigned long identifier, const ResourceError& /*error*/) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = m_resources.get(identifier); + if (!resource) + return; + + removeResource(resource.get()); + + resource->finished = true; + resource->failed = true; + resource->endTime = currentTime(); + + addResource(resource.get()); + + if (windowVisible() && resource->scriptObject) { + updateScriptResource(resource.get(), resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource.get(), resource->finished, resource->failed); + } +} + +#if ENABLE(DATABASE) +void InspectorController::didOpenDatabase(Database* database, const String& domain, const String& name, const String& version) +{ + if (!enabled()) + return; + + RefPtr<InspectorDatabaseResource> resource = InspectorDatabaseResource::create(database, domain, name, version); + + m_databaseResources.add(resource); + + if (windowVisible()) + addDatabaseScriptResource(resource.get()); +} +#endif + +void InspectorController::moveWindowBy(float x, float y) const +{ + if (!m_page || !enabled()) + return; + + FloatRect frameRect = m_page->chrome()->windowRect(); + frameRect.move(x, y); + m_page->chrome()->setWindowRect(frameRect); +} + +void InspectorController::drawNodeHighlight(GraphicsContext& context) const +{ + static const Color overlayFillColor(0, 0, 0, 128); + static const int outlineThickness = 1; + + if (!m_highlightedNode) + return; + + RenderObject* renderer = m_highlightedNode->renderer(); + if (!renderer) + return; + IntRect nodeRect(renderer->absoluteBoundingBoxRect()); + + Vector<IntRect> rects; + if (renderer->isInline() || (renderer->isText() && !m_highlightedNode->isSVGElement())) + renderer->addLineBoxRects(rects); + if (rects.isEmpty()) + rects.append(nodeRect); + + FrameView* view = m_inspectedPage->mainFrame()->view(); + FloatRect overlayRect = static_cast<ScrollView*>(view)->visibleContentRect(); + + if (!overlayRect.contains(nodeRect) && !nodeRect.contains(enclosingIntRect(overlayRect))) { + Element* element; + if (m_highlightedNode->isElementNode()) + element = static_cast<Element*>(m_highlightedNode.get()); + else + element = static_cast<Element*>(m_highlightedNode->parent()); + element->scrollIntoViewIfNeeded(); + overlayRect = static_cast<ScrollView*>(view)->visibleContentRect(); + } + + context.translate(-overlayRect.x(), -overlayRect.y()); + + // Draw translucent gray fill, out of which we will cut holes. + context.fillRect(overlayRect, overlayFillColor); + + // Draw white frames around holes in first pass, so they will be erased in + // places where holes overlap or abut. + for (size_t i = 0; i < rects.size(); ++i) { + IntRect rect = rects[i]; + rect.inflate(outlineThickness); + context.fillRect(rect, Color::white); + } + + // Erase holes in second pass. + for (size_t i = 0; i < rects.size(); ++i) + context.clearRect(rects[i]); +} + +} // namespace WebCore diff --git a/WebCore/page/InspectorController.h b/WebCore/page/InspectorController.h new file mode 100644 index 0000000..46a5df3 --- /dev/null +++ b/WebCore/page/InspectorController.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef InspectorController_h +#define InspectorController_h + +#include "Chrome.h" +#include <JavaScriptCore/JSContextRef.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Database; +class DocumentLoader; +class GraphicsContext; +class InspectorClient; +class Node; +class ResourceResponse; +class ResourceError; + +struct ConsoleMessage; +struct InspectorDatabaseResource; +struct InspectorResource; +class ResourceRequest; + +class InspectorController { +public: + typedef HashMap<long long, RefPtr<InspectorResource> > ResourcesMap; + typedef HashMap<RefPtr<Frame>, ResourcesMap*> FrameResourcesMap; + typedef HashSet<RefPtr<InspectorDatabaseResource> > DatabaseResourcesSet; + + typedef enum { + FocusedNodeDocumentPanel, + ConsolePanel, + TimelinePanel + } SpecialPanels; + + InspectorController(Page*, InspectorClient*); + ~InspectorController(); + + void pageDestroyed() { m_page = 0; } + + bool enabled() const; + + Page* inspectedPage() const { return m_inspectedPage; } + + String localizedStringsURL(); + + void inspect(Node*); + void highlight(Node*); + void hideHighlight(); + + void show(); + void showConsole(); + void showTimeline(); + void close(); + + bool windowVisible(); + void setWindowVisible(bool visible = true); + + void addMessageToConsole(MessageSource, MessageLevel, const String& message, unsigned lineNumber, const String& sourceID); + + void attachWindow(); + void detachWindow(); + + JSContextRef scriptContext() const { return m_scriptContext; }; + void setScriptContext(JSContextRef context) { m_scriptContext = context; }; + + void windowScriptObjectAvailable(); + + void scriptObjectReady(); + + void populateScriptResources(); + void clearScriptResources(); + + void didCommitLoad(DocumentLoader*); + void frameDetachedFromParent(Frame*); + + void didLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length); + + void identifierForInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); + void willSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + void didReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&); + void didReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived); + void didFinishLoading(DocumentLoader*, unsigned long identifier); + void didFailLoading(DocumentLoader*, unsigned long identifier, const ResourceError&); + +#if ENABLE(DATABASE) + void didOpenDatabase(Database*, const String& domain, const String& name, const String& version); +#endif + + const ResourcesMap& resources() const { return m_resources; } + + void moveWindowBy(float x, float y) const; + + void drawNodeHighlight(GraphicsContext&) const; + +private: + void focusNode(); + + void addScriptConsoleMessage(const ConsoleMessage*); + void clearScriptConsoleMessages(); + + void clearNetworkTimeline(); + void clearDatabaseScriptResources(); + + void addResource(InspectorResource*); + void removeResource(InspectorResource*); + + JSObjectRef addScriptResource(InspectorResource*); + void removeScriptResource(InspectorResource*); + + JSObjectRef addAndUpdateScriptResource(InspectorResource*); + void updateScriptResourceRequest(InspectorResource*); + void updateScriptResourceResponse(InspectorResource*); + void updateScriptResource(InspectorResource*, int length); + void updateScriptResource(InspectorResource*, bool finished, bool failed = false); + void updateScriptResource(InspectorResource*, double startTime, double responseReceivedTime, double endTime); + + void pruneResources(ResourcesMap*, DocumentLoader* loaderToKeep = 0); + void removeAllResources(ResourcesMap* map) { pruneResources(map); } + +#if ENABLE(DATABASE) + JSObjectRef addDatabaseScriptResource(InspectorDatabaseResource*); + void removeDatabaseScriptResource(InspectorDatabaseResource*); +#endif + + Page* m_inspectedPage; + InspectorClient* m_client; + Page* m_page; + RefPtr<Node> m_nodeToFocus; + RefPtr<InspectorResource> m_mainResource; + ResourcesMap m_resources; + FrameResourcesMap m_frameResources; + Vector<ConsoleMessage*> m_consoleMessages; +#if ENABLE(DATABASE) + DatabaseResourcesSet m_databaseResources; +#endif + JSObjectRef m_scriptObject; + JSObjectRef m_controllerScriptObject; + JSContextRef m_scriptContext; + bool m_windowVisible; + SpecialPanels m_showAfterVisible; + long long m_nextIdentifier; + RefPtr<Node> m_highlightedNode; +}; + +} // namespace WebCore + +#endif // !defined(InspectorController_h) diff --git a/WebCore/page/MouseEventWithHitTestResults.cpp b/WebCore/page/MouseEventWithHitTestResults.cpp new file mode 100644 index 0000000..d7596ce --- /dev/null +++ b/WebCore/page/MouseEventWithHitTestResults.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "MouseEventWithHitTestResults.h" + +#include "Element.h" +#include "Node.h" + +// Would TargetedMouseEvent be a better name? + +namespace WebCore { + +static inline Element* targetElement(Node* node) +{ + if (!node) + return 0; + Node* parent = node->parent(); + if (!parent || !parent->isElementNode()) + return 0; + return static_cast<Element*>(parent); +} + +MouseEventWithHitTestResults::MouseEventWithHitTestResults(const PlatformMouseEvent& event, const HitTestResult& hitTestResult) + : m_event(event) + , m_hitTestResult(hitTestResult) +{ +} + +Node* MouseEventWithHitTestResults::targetNode() const +{ + Node* node = m_hitTestResult.innerNode(); + if (node && node->inDocument()) + return node; + + Element* element = targetElement(node); + if (element && element->inDocument()) + return element; + + return node; +} + +const IntPoint MouseEventWithHitTestResults::localPoint() const +{ + return m_hitTestResult.localPoint(); +} + +PlatformScrollbar* MouseEventWithHitTestResults::scrollbar() const +{ + return m_hitTestResult.scrollbar(); +} + +bool MouseEventWithHitTestResults::isOverLink() const +{ + return m_hitTestResult.URLElement() && m_hitTestResult.URLElement()->isLink(); +} + +} diff --git a/WebCore/page/MouseEventWithHitTestResults.h b/WebCore/page/MouseEventWithHitTestResults.h new file mode 100644 index 0000000..8392702 --- /dev/null +++ b/WebCore/page/MouseEventWithHitTestResults.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Simon Hausmann <hausmann@kde.org> + Copyright (C) 2006 Apple Computer, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MouseEventWithHitTestResults_h +#define MouseEventWithHitTestResults_h + +#include "HitTestResult.h" +#include "PlatformMouseEvent.h" + +namespace WebCore { + +class PlatformScrollbar; + +// FIXME: Why doesn't this class just cache a HitTestResult instead of copying all of HitTestResult's fields over? +class MouseEventWithHitTestResults { +public: + MouseEventWithHitTestResults(const PlatformMouseEvent&, const HitTestResult&); + + const PlatformMouseEvent& event() const { return m_event; } + const HitTestResult& hitTestResult() const { return m_hitTestResult; } + Node* targetNode() const; + const IntPoint localPoint() const; + PlatformScrollbar* scrollbar() const; + bool isOverLink() const; + +private: + PlatformMouseEvent m_event; + HitTestResult m_hitTestResult; +}; + +} + +#endif diff --git a/WebCore/page/Page.cpp b/WebCore/page/Page.cpp new file mode 100644 index 0000000..f60d05c --- /dev/null +++ b/WebCore/page/Page.cpp @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "Page.h" + +#include "Chrome.h" +#include "ChromeClient.h" +#include "ContextMenuClient.h" +#include "ContextMenuController.h" +#include "EditorClient.h" +#include "DragController.h" +#include "FileSystem.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HistoryItem.h" +#include "InspectorController.h" +#include "Logging.h" +#include "ProgressTracker.h" +#include "RenderWidget.h" +#include "SelectionController.h" +#include "Settings.h" +#include "StringHash.h" +#include "TextResourceDecoder.h" +#include "Widget.h" +#include <kjs/collector.h> +#include <kjs/JSLock.h> +#include <wtf/HashMap.h> + +using namespace KJS; + +namespace WebCore { + +static HashSet<Page*>* allPages; +static HashMap<String, HashSet<Page*>*>* frameNamespaces; + +#ifndef NDEBUG +WTFLogChannel LogWebCorePageLeaks = { 0x00000000, "", WTFLogChannelOn }; + +struct PageCounter { + static int count; + ~PageCounter() + { + if (count) + LOG(WebCorePageLeaks, "LEAK: %d Page\n", count); + } +}; +int PageCounter::count = 0; +static PageCounter pageCounter; +#endif + +Page::Page(ChromeClient* chromeClient, ContextMenuClient* contextMenuClient, EditorClient* editorClient, DragClient* dragClient, InspectorClient* inspectorClient) + : m_chrome(new Chrome(this, chromeClient)) + , m_dragCaretController(new SelectionController(0, true)) + , m_dragController(new DragController(this, dragClient)) + , m_focusController(new FocusController(this)) + , m_contextMenuController(new ContextMenuController(this, contextMenuClient)) + , m_inspectorController(new InspectorController(this, inspectorClient)) + , m_settings(new Settings(this)) + , m_progress(new ProgressTracker) + , m_backForwardList(BackForwardList::create(this)) + , m_editorClient(editorClient) + , m_frameCount(0) + , m_tabKeyCyclesThroughElements(true) + , m_defersLoading(false) + , m_inLowQualityInterpolationMode(false) + , m_parentInspectorController(0) + , m_didLoadUserStyleSheet(false) + , m_userStyleSheetModificationTime(0) +{ + if (!allPages) { + allPages = new HashSet<Page*>; + setFocusRingColorChangeFunction(setNeedsReapplyStyles); + } + + ASSERT(!allPages->contains(this)); + allPages->add(this); + +#ifndef NDEBUG + ++PageCounter::count; +#endif +} + +Page::~Page() +{ + m_mainFrame->setView(0); + setGroupName(String()); + allPages->remove(this); + + for (Frame* frame = mainFrame(); frame; frame = frame->tree()->traverseNext()) + frame->pageDestroyed(); + m_editorClient->pageDestroyed(); + m_inspectorController->pageDestroyed(); + + m_backForwardList->close(); + +#ifndef NDEBUG + --PageCounter::count; + + // Cancel keepAlive timers, to ensure we release all Frames before exiting. + // It's safe to do this because we prohibit closing a Page while JavaScript + // is executing. + Frame::cancelAllKeepAlive(); +#endif +} + +void Page::setMainFrame(PassRefPtr<Frame> mainFrame) +{ + ASSERT(!m_mainFrame); // Should only be called during initialization + m_mainFrame = mainFrame; +} + +BackForwardList* Page::backForwardList() +{ + return m_backForwardList.get(); +} + +bool Page::goBack() +{ + HistoryItem* item = m_backForwardList->backItem(); + + if (item) { + goToItem(item, FrameLoadTypeBack); + return true; + } + return false; +} + +bool Page::goForward() +{ + HistoryItem* item = m_backForwardList->forwardItem(); + + if (item) { + goToItem(item, FrameLoadTypeForward); + return true; + } + return false; +} + +void Page::goToItem(HistoryItem* item, FrameLoadType type) +{ + // Abort any current load if we're going to a history item + m_mainFrame->loader()->stopAllLoaders(); + m_mainFrame->loader()->goToItem(item, type); +} + +void Page::setGroupName(const String& name) +{ + if (frameNamespaces && !m_groupName.isEmpty()) { + HashSet<Page*>* oldNamespace = frameNamespaces->get(m_groupName); + if (oldNamespace) { + oldNamespace->remove(this); + if (oldNamespace->isEmpty()) { + frameNamespaces->remove(m_groupName); + delete oldNamespace; + } + } + } + m_groupName = name; + if (!name.isEmpty()) { + if (!frameNamespaces) + frameNamespaces = new HashMap<String, HashSet<Page*>*>; + HashSet<Page*>* newNamespace = frameNamespaces->get(name); + if (!newNamespace) { + newNamespace = new HashSet<Page*>; + frameNamespaces->add(name, newNamespace); + } + newNamespace->add(this); + } +} + +const HashSet<Page*>* Page::frameNamespace() const +{ + return (frameNamespaces && !m_groupName.isEmpty()) ? frameNamespaces->get(m_groupName) : 0; +} + +const HashSet<Page*>* Page::frameNamespace(const String& groupName) +{ + return (frameNamespaces && !groupName.isEmpty()) ? frameNamespaces->get(groupName) : 0; +} + +void Page::setNeedsReapplyStyles() +{ + if (!allPages) + return; + HashSet<Page*>::iterator end = allPages->end(); + for (HashSet<Page*>::iterator it = allPages->begin(); it != end; ++it) + for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) + frame->setNeedsReapplyStyles(); +} + +static Frame* incrementFrame(Frame* curr, bool forward, bool wrapFlag) +{ + return forward + ? curr->tree()->traverseNextWithWrap(wrapFlag) + : curr->tree()->traversePreviousWithWrap(wrapFlag); +} + +bool Page::findString(const String& target, TextCaseSensitivity caseSensitivity, FindDirection direction, bool shouldWrap) +{ + if (target.isEmpty() || !mainFrame()) + return false; + + Frame* frame = focusController()->focusedOrMainFrame(); + Frame* startFrame = frame; + do { + if (frame->findString(target, direction == FindDirectionForward, caseSensitivity == TextCaseSensitive, false, true)) { + if (frame != startFrame) + startFrame->selectionController()->clear(); + focusController()->setFocusedFrame(frame); + return true; + } + frame = incrementFrame(frame, direction == FindDirectionForward, shouldWrap); + } while (frame && frame != startFrame); + + // Search contents of startFrame, on the other side of the selection that we did earlier. + // We cheat a bit and just research with wrap on + if (shouldWrap && !startFrame->selectionController()->isNone()) { + bool found = startFrame->findString(target, direction == FindDirectionForward, caseSensitivity == TextCaseSensitive, true, true); + focusController()->setFocusedFrame(frame); + return found; + } + + return false; +} + +unsigned int Page::markAllMatchesForText(const String& target, TextCaseSensitivity caseSensitivity, bool shouldHighlight, unsigned limit) +{ + if (target.isEmpty() || !mainFrame()) + return 0; + + unsigned matches = 0; + + Frame* frame = mainFrame(); + do { + frame->setMarkedTextMatchesAreHighlighted(shouldHighlight); + matches += frame->markAllMatchesForText(target, caseSensitivity == TextCaseSensitive, (limit == 0) ? 0 : (limit - matches)); + frame = incrementFrame(frame, true, false); + } while (frame); + + return matches; +} + +void Page::unmarkAllTextMatches() +{ + if (!mainFrame()) + return; + + Frame* frame = mainFrame(); + do { + if (Document* document = frame->document()) + document->removeMarkers(DocumentMarker::TextMatch); + frame = incrementFrame(frame, true, false); + } while (frame); +} + +const Selection& Page::selection() const +{ + return focusController()->focusedOrMainFrame()->selectionController()->selection(); +} + +void Page::setDefersLoading(bool defers) +{ + if (defers == m_defersLoading) + return; + + m_defersLoading = defers; + for (Frame* frame = mainFrame(); frame; frame = frame->tree()->traverseNext()) + frame->loader()->setDefersLoading(defers); +} + +void Page::clearUndoRedoOperations() +{ + m_editorClient->clearUndoRedoOperations(); +} + +bool Page::inLowQualityImageInterpolationMode() const +{ + return m_inLowQualityInterpolationMode; +} + +void Page::setInLowQualityImageInterpolationMode(bool mode) +{ + m_inLowQualityInterpolationMode = mode; +} + +void Page::userStyleSheetLocationChanged() +{ +#if !FRAME_LOADS_USER_STYLESHEET + // FIXME: We should provide a way to load other types of URLs than just + // file: (e.g., http:, data:). + if (m_settings->userStyleSheetLocation().isLocalFile()) + m_userStyleSheetPath = m_settings->userStyleSheetLocation().fileSystemPath(); + else + m_userStyleSheetPath = String(); + + m_didLoadUserStyleSheet = false; + m_userStyleSheet = String(); + m_userStyleSheetModificationTime = 0; +#endif +} + +const String& Page::userStyleSheet() const +{ + if (m_userStyleSheetPath.isEmpty()) { + ASSERT(m_userStyleSheet.isEmpty()); + return m_userStyleSheet; + } + + time_t modTime; + if (!getFileModificationTime(m_userStyleSheetPath, modTime)) { + // The stylesheet either doesn't exist, was just deleted, or is + // otherwise unreadable. If we've read the stylesheet before, we should + // throw away that data now as it no longer represents what's on disk. + m_userStyleSheet = String(); + return m_userStyleSheet; + } + + // If the stylesheet hasn't changed since the last time we read it, we can + // just return the old data. + if (m_didLoadUserStyleSheet && modTime <= m_userStyleSheetModificationTime) + return m_userStyleSheet; + + m_didLoadUserStyleSheet = true; + m_userStyleSheet = String(); + m_userStyleSheetModificationTime = modTime; + + // FIXME: It would be better to load this asynchronously to avoid blocking + // the process, but we will first need to create an asynchronous loading + // mechanism that is not tied to a particular Frame. We will also have to + // determine what our behavior should be before the stylesheet is loaded + // and what should happen when it finishes loading, especially with respect + // to when the load event fires, when Document::close is called, and when + // layout/paint are allowed to happen. + RefPtr<SharedBuffer> data = SharedBuffer::createWithContentsOfFile(m_userStyleSheetPath); + if (!data) + return m_userStyleSheet; + + m_userStyleSheet = TextResourceDecoder("text/css").decode(data->data(), data->size()); + + return m_userStyleSheet; +} + +} // namespace WebCore diff --git a/WebCore/page/Page.h b/WebCore/page/Page.h new file mode 100644 index 0000000..b685e3f --- /dev/null +++ b/WebCore/page/Page.h @@ -0,0 +1,173 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef Page_h +#define Page_h + +#include "BackForwardList.h" +#include "Chrome.h" +#include "ContextMenuController.h" +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> + +#if PLATFORM(WIN) || (PLATFORM(WX) && PLATFORM(WIN_OS)) +typedef struct HINSTANCE__* HINSTANCE; +#endif + +typedef enum TextCaseSensitivity { + TextCaseSensitive, + TextCaseInsensitive +}; + +typedef enum FindDirection { + FindDirectionForward, + FindDirectionBackward +}; + +namespace WebCore { + + class Chrome; + class ChromeClient; + class ContextMenuClient; + class ContextMenuController; + class DragClient; + class DragController; + class EditorClient; + class FocusController; + class Frame; + class InspectorClient; + class InspectorController; + class Node; + class ProgressTracker; + class Selection; + class SelectionController; + class Settings; + + class Page : Noncopyable { + public: + static void setNeedsReapplyStyles(); + static const HashSet<Page*>* frameNamespace(const String&); + + Page(ChromeClient*, ContextMenuClient*, EditorClient*, DragClient*, InspectorClient*); + ~Page(); + + EditorClient* editorClient() const { return m_editorClient; } + + void setMainFrame(PassRefPtr<Frame>); + Frame* mainFrame() const { return m_mainFrame.get(); } + + BackForwardList* backForwardList(); + + // FIXME: The following three methods don't fall under the responsibilities of the Page object + // They seem to fit a hypothetical Page-controller object that would be akin to the + // Frame-FrameLoader relationship. They have to live here now, but should move somewhere that + // makes more sense when that class exists. + bool goBack(); + bool goForward(); + void goToItem(HistoryItem*, FrameLoadType); + + void setGroupName(const String&); + String groupName() const { return m_groupName; } + + const HashSet<Page*>* frameNamespace() const; + + void incrementFrameCount() { ++m_frameCount; } + void decrementFrameCount() { --m_frameCount; } + int frameCount() const { return m_frameCount; } + + Chrome* chrome() const { return m_chrome.get(); } + SelectionController* dragCaretController() const { return m_dragCaretController.get(); } + DragController* dragController() const { return m_dragController.get(); } + FocusController* focusController() const { return m_focusController.get(); } + ContextMenuController* contextMenuController() const { return m_contextMenuController.get(); } + InspectorController* inspectorController() const { return m_inspectorController.get(); } + Settings* settings() const { return m_settings.get(); } + ProgressTracker* progress() const { return m_progress.get(); } + + void setParentInspectorController(InspectorController* controller) { m_parentInspectorController = controller; } + InspectorController* parentInspectorController() const { return m_parentInspectorController; } + + void setTabKeyCyclesThroughElements(bool b) { m_tabKeyCyclesThroughElements = b; } + bool tabKeyCyclesThroughElements() const { return m_tabKeyCyclesThroughElements; } + + bool findString(const String&, TextCaseSensitivity, FindDirection, bool shouldWrap); + unsigned int markAllMatchesForText(const String&, TextCaseSensitivity, bool shouldHighlight, unsigned); + void unmarkAllTextMatches(); + + const Selection& selection() const; + + void setDefersLoading(bool); + bool defersLoading() const { return m_defersLoading; } + + void clearUndoRedoOperations(); + + bool inLowQualityImageInterpolationMode() const; + void setInLowQualityImageInterpolationMode(bool = true); + + void userStyleSheetLocationChanged(); + const String& userStyleSheet() const; + +#if PLATFORM(WIN) || (PLATFORM(WX) && PLATFORM(WIN_OS)) + // The global DLL or application instance used for all windows. + static void setInstanceHandle(HINSTANCE instanceHandle) { s_instanceHandle = instanceHandle; } + static HINSTANCE instanceHandle() { return s_instanceHandle; } +#endif + + private: + OwnPtr<Chrome> m_chrome; + OwnPtr<SelectionController> m_dragCaretController; + OwnPtr<DragController> m_dragController; + OwnPtr<FocusController> m_focusController; + OwnPtr<ContextMenuController> m_contextMenuController; + OwnPtr<InspectorController> m_inspectorController; + OwnPtr<Settings> m_settings; + OwnPtr<ProgressTracker> m_progress; + + RefPtr<BackForwardList> m_backForwardList; + RefPtr<Frame> m_mainFrame; + RefPtr<Node> m_focusedNode; + + EditorClient* m_editorClient; + + int m_frameCount; + String m_groupName; + + bool m_tabKeyCyclesThroughElements; + bool m_defersLoading; + + bool m_inLowQualityInterpolationMode; + + InspectorController* m_parentInspectorController; + + String m_userStyleSheetPath; + mutable String m_userStyleSheet; + mutable bool m_didLoadUserStyleSheet; + mutable time_t m_userStyleSheetModificationTime; + +#if PLATFORM(WIN) || (PLATFORM(WX) && defined(__WXMSW__)) + static HINSTANCE s_instanceHandle; +#endif + }; + +} // namespace WebCore + +#endif // Page_h diff --git a/WebCore/page/Plugin.h b/WebCore/page/Plugin.h new file mode 100644 index 0000000..1e53184 --- /dev/null +++ b/WebCore/page/Plugin.h @@ -0,0 +1,42 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef Plugin_h +#define Plugin_h + +#include <wtf/RefCounted.h> + +namespace WebCore { + + class Widget; + + class Plugin : public RefCounted<Plugin> { + public: + static PassRefPtr<Plugin> create(Widget* view) { return adoptRef(new Plugin(view)); } + Widget* view() const { return m_view; } + + private: + Plugin(Widget* view) : m_view(view) { } + Widget* m_view; + }; + +} // namespace WebCore + +#endif // Plugin_h diff --git a/WebCore/page/Screen.cpp b/WebCore/page/Screen.cpp new file mode 100644 index 0000000..396a6f4 --- /dev/null +++ b/WebCore/page/Screen.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include "config.h" +#include "Screen.h" + +#include "FloatRect.h" +#include "Frame.h" +#include "FrameView.h" +#include "PlatformScreen.h" +#include "Widget.h" + +namespace WebCore { + +Screen::Screen(Frame* frame) + : m_frame(frame) +{ +} + +void Screen::disconnectFrame() +{ + m_frame = 0; +} + +unsigned Screen::height() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenRect(m_frame->view()).height()); +} + +unsigned Screen::width() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenRect(m_frame->view()).width()); +} + +unsigned Screen::colorDepth() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenDepth(m_frame->view())); +} + +unsigned Screen::pixelDepth() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenDepth(m_frame->view())); +} + +unsigned Screen::availLeft() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenAvailableRect(m_frame->view()).x()); +} + +unsigned Screen::availTop() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenAvailableRect(m_frame->view()).y()); +} + +unsigned Screen::availHeight() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenAvailableRect(m_frame->view()).height()); +} + +unsigned Screen::availWidth() const +{ + if (!m_frame) + return 0; + return static_cast<unsigned>(screenAvailableRect(m_frame->view()).width()); +} + +} // namespace WebCore diff --git a/WebCore/page/Screen.h b/WebCore/page/Screen.h new file mode 100644 index 0000000..288b1ac --- /dev/null +++ b/WebCore/page/Screen.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef Screen_h +#define Screen_h + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + + class Frame; + + class Screen : public RefCounted<Screen> { + public: + static PassRefPtr<Screen> create(Frame *frame) { return adoptRef(new Screen(frame)); } + void disconnectFrame(); + + unsigned height() const; + unsigned width() const; + unsigned colorDepth() const; + unsigned pixelDepth() const; + unsigned availLeft() const; + unsigned availTop() const; + unsigned availHeight() const; + unsigned availWidth() const; + + private: + Screen(Frame*); + + Frame* m_frame; + }; + +} // namespace WebCore + +#endif // Screen_h diff --git a/WebCore/page/Screen.idl b/WebCore/page/Screen.idl new file mode 100644 index 0000000..ca7d20d --- /dev/null +++ b/WebCore/page/Screen.idl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +module window { + + interface Screen { + readonly attribute unsigned long height; + readonly attribute unsigned long width; + readonly attribute unsigned long colorDepth; + readonly attribute unsigned long pixelDepth; + readonly attribute unsigned long availLeft; + readonly attribute unsigned long availTop; + readonly attribute unsigned long availHeight; + readonly attribute unsigned long availWidth; + }; + +} diff --git a/WebCore/page/Settings.cpp b/WebCore/page/Settings.cpp new file mode 100644 index 0000000..5253117 --- /dev/null +++ b/WebCore/page/Settings.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Settings.h" + +#include "Frame.h" +#include "FrameTree.h" +#include "Page.h" +#include "PageCache.h" +#include "HistoryItem.h" + +#if ENABLE(DATABASE) +#include "DatabaseTracker.h" +#endif + +namespace WebCore { + +static void setNeedsReapplyStylesInAllFrames(Page* page) +{ + for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) + frame->setNeedsReapplyStyles(); +} + +Settings::Settings(Page* page) + : m_page(page) + , m_editableLinkBehavior(EditableLinkDefaultBehavior) + , m_minimumFontSize(0) + , m_minimumLogicalFontSize(0) + , m_defaultFontSize(0) + , m_defaultFixedFontSize(0) + , m_isJavaEnabled(false) + , m_loadsImagesAutomatically(false) + , m_privateBrowsingEnabled(false) + , m_arePluginsEnabled(false) + , m_isJavaScriptEnabled(false) + , m_javaScriptCanOpenWindowsAutomatically(false) + , m_shouldPrintBackgrounds(false) + , m_textAreasAreResizable(false) + , m_usesDashboardBackwardCompatibilityMode(false) + , m_needsAdobeFrameReloadingQuirk(false) + , m_needsKeyboardEventDisambiguationQuirks(false) + , m_isDOMPasteAllowed(false) + , m_shrinksStandaloneImagesToFit(true) + , m_usesPageCache(false) + , m_showsURLsInToolTips(false) + , m_forceFTPDirectoryListings(false) + , m_developerExtrasEnabled(false) + , m_authorAndUserStylesEnabled(true) + , m_needsSiteSpecificQuirks(false) + , m_fontRenderingMode(0) +{ + // A Frame may not have been created yet, so we initialize the AtomicString + // hash before trying to use it. + AtomicString::init(); +} + +void Settings::setStandardFontFamily(const AtomicString& standardFontFamily) +{ + if (standardFontFamily == m_standardFontFamily) + return; + + m_standardFontFamily = standardFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setFixedFontFamily(const AtomicString& fixedFontFamily) +{ + if (m_fixedFontFamily == fixedFontFamily) + return; + + m_fixedFontFamily = fixedFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setSerifFontFamily(const AtomicString& serifFontFamily) +{ + if (m_serifFontFamily == serifFontFamily) + return; + + m_serifFontFamily = serifFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setSansSerifFontFamily(const AtomicString& sansSerifFontFamily) +{ + if (m_sansSerifFontFamily == sansSerifFontFamily) + return; + + m_sansSerifFontFamily = sansSerifFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setCursiveFontFamily(const AtomicString& cursiveFontFamily) +{ + if (m_cursiveFontFamily == cursiveFontFamily) + return; + + m_cursiveFontFamily = cursiveFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setFantasyFontFamily(const AtomicString& fantasyFontFamily) +{ + if (m_fantasyFontFamily == fantasyFontFamily) + return; + + m_fantasyFontFamily = fantasyFontFamily; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setMinimumFontSize(int minimumFontSize) +{ + if (m_minimumFontSize == minimumFontSize) + return; + + m_minimumFontSize = minimumFontSize; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setMinimumLogicalFontSize(int minimumLogicalFontSize) +{ + if (m_minimumLogicalFontSize == minimumLogicalFontSize) + return; + + m_minimumLogicalFontSize = minimumLogicalFontSize; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setDefaultFontSize(int defaultFontSize) +{ + if (m_defaultFontSize == defaultFontSize) + return; + + m_defaultFontSize = defaultFontSize; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setDefaultFixedFontSize(int defaultFontSize) +{ + if (m_defaultFixedFontSize == defaultFontSize) + return; + + m_defaultFixedFontSize = defaultFontSize; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setLoadsImagesAutomatically(bool loadsImagesAutomatically) +{ + m_loadsImagesAutomatically = loadsImagesAutomatically; +} + +void Settings::setJavaScriptEnabled(bool isJavaScriptEnabled) +{ + m_isJavaScriptEnabled = isJavaScriptEnabled; +} + +void Settings::setJavaEnabled(bool isJavaEnabled) +{ + m_isJavaEnabled = isJavaEnabled; +} + +void Settings::setPluginsEnabled(bool arePluginsEnabled) +{ + m_arePluginsEnabled = arePluginsEnabled; +} + +void Settings::setPrivateBrowsingEnabled(bool privateBrowsingEnabled) +{ + m_privateBrowsingEnabled = privateBrowsingEnabled; +} + +void Settings::setJavaScriptCanOpenWindowsAutomatically(bool javaScriptCanOpenWindowsAutomatically) +{ + m_javaScriptCanOpenWindowsAutomatically = javaScriptCanOpenWindowsAutomatically; +} + +void Settings::setDefaultTextEncodingName(const String& defaultTextEncodingName) +{ + m_defaultTextEncodingName = defaultTextEncodingName; +} + +void Settings::setUserStyleSheetLocation(const KURL& userStyleSheetLocation) +{ + if (m_userStyleSheetLocation == userStyleSheetLocation) + return; + + m_userStyleSheetLocation = userStyleSheetLocation; + + m_page->userStyleSheetLocationChanged(); + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setShouldPrintBackgrounds(bool shouldPrintBackgrounds) +{ + m_shouldPrintBackgrounds = shouldPrintBackgrounds; +} + +void Settings::setTextAreasAreResizable(bool textAreasAreResizable) +{ + if (m_textAreasAreResizable == textAreasAreResizable) + return; + + m_textAreasAreResizable = textAreasAreResizable; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setEditableLinkBehavior(EditableLinkBehavior editableLinkBehavior) +{ + m_editableLinkBehavior = editableLinkBehavior; +} + +void Settings::setUsesDashboardBackwardCompatibilityMode(bool usesDashboardBackwardCompatibilityMode) +{ + m_usesDashboardBackwardCompatibilityMode = usesDashboardBackwardCompatibilityMode; +} + +// FIXME: This quirk is needed because of Radar 4674537 and 5211271. We need to phase it out once Adobe +// can fix the bug from their end. +void Settings::setNeedsAdobeFrameReloadingQuirk(bool shouldNotReloadIFramesForUnchangedSRC) +{ + m_needsAdobeFrameReloadingQuirk = shouldNotReloadIFramesForUnchangedSRC; +} + +// This is a quirk we are pro-actively applying to old applications. It changes keyboard event dispatching, +// making keyIdentifier available on keypress events, making charCode available on keydown/keyup events, +// and getting keypress dispatched in more cases. +void Settings::setNeedsKeyboardEventDisambiguationQuirks(bool needsQuirks) +{ + m_needsKeyboardEventDisambiguationQuirks = needsQuirks; +} + +void Settings::setDOMPasteAllowed(bool DOMPasteAllowed) +{ + m_isDOMPasteAllowed = DOMPasteAllowed; +} + +void Settings::setUsesPageCache(bool usesPageCache) +{ + if (m_usesPageCache == usesPageCache) + return; + + m_usesPageCache = usesPageCache; + if (!m_usesPageCache) { + HistoryItemVector& historyItems = m_page->backForwardList()->entries(); + for (unsigned i = 0; i < historyItems.size(); i++) + pageCache()->remove(historyItems[i].get()); + pageCache()->releaseAutoreleasedPagesNow(); + } +} + +void Settings::setShrinksStandaloneImagesToFit(bool shrinksStandaloneImagesToFit) +{ + m_shrinksStandaloneImagesToFit = shrinksStandaloneImagesToFit; +} + +void Settings::setShowsURLsInToolTips(bool showsURLsInToolTips) +{ + m_showsURLsInToolTips = showsURLsInToolTips; +} + +void Settings::setFTPDirectoryTemplatePath(const String& path) +{ + m_ftpDirectoryTemplatePath = path; +} + +void Settings::setForceFTPDirectoryListings(bool force) +{ + m_forceFTPDirectoryListings = force; +} + +void Settings::setDeveloperExtrasEnabled(bool developerExtrasEnabled) +{ + m_developerExtrasEnabled = developerExtrasEnabled; +} + +void Settings::setAuthorAndUserStylesEnabled(bool authorAndUserStylesEnabled) +{ + if (m_authorAndUserStylesEnabled == authorAndUserStylesEnabled) + return; + + m_authorAndUserStylesEnabled = authorAndUserStylesEnabled; + setNeedsReapplyStylesInAllFrames(m_page); +} + +void Settings::setFontRenderingMode(FontRenderingMode mode) +{ + if (fontRenderingMode() == mode) + return; + m_fontRenderingMode = mode; + setNeedsReapplyStylesInAllFrames(m_page); +} + +FontRenderingMode Settings::fontRenderingMode() const +{ + return static_cast<FontRenderingMode>(m_fontRenderingMode); +} + +void Settings::setNeedsSiteSpecificQuirks(bool needsQuirks) +{ + m_needsSiteSpecificQuirks = needsQuirks; +} + +} // namespace WebCore diff --git a/WebCore/page/Settings.h b/WebCore/page/Settings.h new file mode 100644 index 0000000..704b486 --- /dev/null +++ b/WebCore/page/Settings.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003, 2006, 2007, 2008 Apple Inc. All rights reserved. + * (C) 2006 Graham Dennis (graham.dennis@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef Settings_h +#define Settings_h + +#include "AtomicString.h" +#include "FontDescription.h" +#include "KURL.h" + +namespace WebCore { + + class Page; + + enum EditableLinkBehavior { + EditableLinkDefaultBehavior = 0, + EditableLinkAlwaysLive, + EditableLinkOnlyLiveWithShiftKey, + EditableLinkLiveWhenNotFocused, + EditableLinkNeverLive + }; + + class Settings { + public: + Settings(Page*); + + void setStandardFontFamily(const AtomicString&); + const AtomicString& standardFontFamily() const { return m_standardFontFamily; } + + void setFixedFontFamily(const AtomicString&); + const AtomicString& fixedFontFamily() const { return m_fixedFontFamily; } + + void setSerifFontFamily(const AtomicString&); + const AtomicString& serifFontFamily() const { return m_serifFontFamily; } + + void setSansSerifFontFamily(const AtomicString&); + const AtomicString& sansSerifFontFamily() const { return m_sansSerifFontFamily; } + + void setCursiveFontFamily(const AtomicString&); + const AtomicString& cursiveFontFamily() const { return m_cursiveFontFamily; } + + void setFantasyFontFamily(const AtomicString&); + const AtomicString& fantasyFontFamily() const { return m_fantasyFontFamily; } + + void setMinimumFontSize(int); + int minimumFontSize() const { return m_minimumFontSize; } + + void setMinimumLogicalFontSize(int); + int minimumLogicalFontSize() const { return m_minimumLogicalFontSize; } + + void setDefaultFontSize(int); + int defaultFontSize() const { return m_defaultFontSize; } + + void setDefaultFixedFontSize(int); + int defaultFixedFontSize() const { return m_defaultFixedFontSize; } + + void setLoadsImagesAutomatically(bool); + bool loadsImagesAutomatically() const { return m_loadsImagesAutomatically; } + + void setJavaScriptEnabled(bool); + bool isJavaScriptEnabled() const { return m_isJavaScriptEnabled; } + + void setJavaScriptCanOpenWindowsAutomatically(bool); + bool JavaScriptCanOpenWindowsAutomatically() const { return m_javaScriptCanOpenWindowsAutomatically; } + + void setJavaEnabled(bool); + bool isJavaEnabled() const { return m_isJavaEnabled; } + + void setPluginsEnabled(bool); + bool arePluginsEnabled() const { return m_arePluginsEnabled; } + + void setPrivateBrowsingEnabled(bool); + bool privateBrowsingEnabled() const { return m_privateBrowsingEnabled; } + + void setDefaultTextEncodingName(const String&); + const String& defaultTextEncodingName() const { return m_defaultTextEncodingName; } + + void setUserStyleSheetLocation(const KURL&); + const KURL& userStyleSheetLocation() const { return m_userStyleSheetLocation; } + + void setShouldPrintBackgrounds(bool); + bool shouldPrintBackgrounds() const { return m_shouldPrintBackgrounds; } + + void setTextAreasAreResizable(bool); + bool textAreasAreResizable() const { return m_textAreasAreResizable; } + + void setEditableLinkBehavior(EditableLinkBehavior); + EditableLinkBehavior editableLinkBehavior() const { return m_editableLinkBehavior; } + + void setUsesDashboardBackwardCompatibilityMode(bool); + bool usesDashboardBackwardCompatibilityMode() const { return m_usesDashboardBackwardCompatibilityMode; } + + void setNeedsAdobeFrameReloadingQuirk(bool); + bool needsAcrobatFrameReloadingQuirk() const { return m_needsAdobeFrameReloadingQuirk; } + + void setNeedsKeyboardEventDisambiguationQuirks(bool); + bool needsKeyboardEventDisambiguationQuirks() const { return m_needsKeyboardEventDisambiguationQuirks; } + + void setDOMPasteAllowed(bool); + bool isDOMPasteAllowed() const { return m_isDOMPasteAllowed; } + + void setUsesPageCache(bool); + bool usesPageCache() const { return m_usesPageCache; } + + void setShrinksStandaloneImagesToFit(bool); + bool shrinksStandaloneImagesToFit() const { return m_shrinksStandaloneImagesToFit; } + + void setShowsURLsInToolTips(bool); + bool showsURLsInToolTips() const { return m_showsURLsInToolTips; } + + void setFTPDirectoryTemplatePath(const String&); + const String& ftpDirectoryTemplatePath() const { return m_ftpDirectoryTemplatePath; } + + void setForceFTPDirectoryListings(bool); + bool forceFTPDirectoryListings() const { return m_forceFTPDirectoryListings; } + + void setDeveloperExtrasEnabled(bool); + bool developerExtrasEnabled() const { return m_developerExtrasEnabled; } + + void setAuthorAndUserStylesEnabled(bool); + bool authorAndUserStylesEnabled() const { return m_authorAndUserStylesEnabled; } + + void setFontRenderingMode(FontRenderingMode mode); + FontRenderingMode fontRenderingMode() const; + + void setNeedsSiteSpecificQuirks(bool); + bool needsSiteSpecificQuirks() const { return m_needsSiteSpecificQuirks; } + + private: + Page* m_page; + + String m_defaultTextEncodingName; + String m_ftpDirectoryTemplatePath; + KURL m_userStyleSheetLocation; + AtomicString m_standardFontFamily; + AtomicString m_fixedFontFamily; + AtomicString m_serifFontFamily; + AtomicString m_sansSerifFontFamily; + AtomicString m_cursiveFontFamily; + AtomicString m_fantasyFontFamily; + EditableLinkBehavior m_editableLinkBehavior; + int m_minimumFontSize; + int m_minimumLogicalFontSize; + int m_defaultFontSize; + int m_defaultFixedFontSize; + bool m_isJavaEnabled : 1; + bool m_loadsImagesAutomatically : 1; + bool m_privateBrowsingEnabled : 1; + bool m_arePluginsEnabled : 1; + bool m_isJavaScriptEnabled : 1; + bool m_javaScriptCanOpenWindowsAutomatically : 1; + bool m_shouldPrintBackgrounds : 1; + bool m_textAreasAreResizable : 1; + bool m_usesDashboardBackwardCompatibilityMode : 1; + bool m_needsAdobeFrameReloadingQuirk : 1; + bool m_needsKeyboardEventDisambiguationQuirks : 1; + bool m_isDOMPasteAllowed : 1; + bool m_shrinksStandaloneImagesToFit : 1; + bool m_usesPageCache: 1; + bool m_showsURLsInToolTips : 1; + bool m_forceFTPDirectoryListings : 1; + bool m_developerExtrasEnabled : 1; + bool m_authorAndUserStylesEnabled : 1; + bool m_needsSiteSpecificQuirks : 1; + unsigned m_fontRenderingMode : 1; + }; + +} // namespace WebCore + +#endif // Settings_h diff --git a/WebCore/page/WindowFeatures.cpp b/WebCore/page/WindowFeatures.cpp new file mode 100644 index 0000000..c499a4a --- /dev/null +++ b/WebCore/page/WindowFeatures.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2000 Harri Porten (porten@kde.org) + * Copyright (C) 2006 Jon Shier (jshier@iastate.edu) + * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reseved. + * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "config.h" +#include "WindowFeatures.h" + +#include "PlatformString.h" +#include "StringHash.h" +#include <wtf/Assertions.h> +#include <wtf/HashMap.h> +#include <wtf/MathExtras.h> + +namespace WebCore { + +// Though isspace() considers \t and \v to be whitespace, Win IE doesn't. +static bool isSeparator(UChar c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0'; +} + +WindowFeatures::WindowFeatures(const String& features) + : xSet(false) + , ySet(false) + , widthSet(false) + , heightSet(false) + , fullscreen(false) + , dialog(false) +{ + /* + The IE rule is: all features except for channelmode and fullscreen default to YES, but + if the user specifies a feature string, all features default to NO. (There is no public + standard that applies to this method.) + + <http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/open_0.asp> + We always allow a window to be resized, which is consistent with Firefox. + */ + + if (features.length() == 0) { + menuBarVisible = true; + statusBarVisible = true; + toolBarVisible = true; + locationBarVisible = true; + scrollbarsVisible = true; + resizable = true; + return; + } + + menuBarVisible = false; + statusBarVisible = false; + toolBarVisible = false; + locationBarVisible = false; + scrollbarsVisible = false; + resizable = true; + + // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior. + int keyBegin, keyEnd; + int valueBegin, valueEnd; + + int i = 0; + int length = features.length(); + String buffer = features.lower(); + while (i < length) { + // skip to first non-separator, but don't skip past the end of the string + while (isSeparator(buffer[i])) { + if (i >= length) + break; + i++; + } + keyBegin = i; + + // skip to first separator + while (!isSeparator(buffer[i])) + i++; + keyEnd = i; + + // skip to first '=', but don't skip past a ',' or the end of the string + while (buffer[i] != '=') { + if (buffer[i] == ',' || i >= length) + break; + i++; + } + + // skip to first non-separator, but don't skip past a ',' or the end of the string + while (isSeparator(buffer[i])) { + if (buffer[i] == ',' || i >= length) + break; + i++; + } + valueBegin = i; + + // skip to first separator + while (!isSeparator(buffer[i])) + i++; + valueEnd = i; + + ASSERT(i <= length); + + String keyString(buffer.substring(keyBegin, keyEnd - keyBegin)); + String valueString(buffer.substring(valueBegin, valueEnd - valueBegin)); + setWindowFeature(keyString, valueString); + } +} + +void WindowFeatures::setWindowFeature(const String& keyString, const String& valueString) +{ + int value; + + // Listing a key with no value is shorthand for key=yes + if (valueString.length() == 0 || valueString == "yes") + value = 1; + else + value = valueString.toInt(); + + // We ignore a keyString of "resizable", which is consistent with Firefox. + if (keyString == "left" || keyString == "screenx") { + xSet = true; + x = value; + } else if (keyString == "top" || keyString == "screeny") { + ySet = true; + y = value; + } else if (keyString == "width" || keyString == "innerwidth") { + widthSet = true; + width = value; + } else if (keyString == "height" || keyString == "innerheight") { + heightSet = true; + height = value; + } else if (keyString == "menubar") + menuBarVisible = value; + else if (keyString == "toolbar") + toolBarVisible = value; + else if (keyString == "location") + locationBarVisible = value; + else if (keyString == "status") + statusBarVisible = value; + else if (keyString == "fullscreen") + fullscreen = value; + else if (keyString == "scrollbars") + scrollbarsVisible = value; +} + +bool WindowFeatures::boolFeature(const HashMap<String, String>& features, const char* key, bool defaultValue) +{ + HashMap<String, String>::const_iterator it = features.find(key); + if (it == features.end()) + return defaultValue; + const String& value = it->second; + return value.isNull() || value == "1" || value == "yes" || value == "on"; +} + +float WindowFeatures::floatFeature(const HashMap<String, String>& features, const char* key, float min, float max, float defaultValue) +{ + HashMap<String, String>::const_iterator it = features.find(key); + if (it == features.end()) + return defaultValue; + // FIXME: Can't distinguish "0q" from string with no digits in it -- both return d == 0 and ok == false. + // Would be good to tell them apart somehow since string with no digits should be default value and + // "0q" should be minimum value. + bool ok; + double d = it->second.toDouble(&ok); + if ((d == 0 && !ok) || isnan(d)) + return defaultValue; + if (d < min || max <= min) + return min; + if (d > max) + return max; + return static_cast<int>(d); +} + +} // namespace WebCore diff --git a/WebCore/page/WindowFeatures.h b/WebCore/page/WindowFeatures.h new file mode 100644 index 0000000..a12cf05 --- /dev/null +++ b/WebCore/page/WindowFeatures.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WindowFeatures_h +#define WindowFeatures_h + +#include "PlatformString.h" +#include <wtf/HashMap.h> + +namespace WebCore { + + struct WindowFeatures { + WindowFeatures() + : xSet(false) + , ySet(false) + , widthSet(false) + , heightSet(false) + , menuBarVisible(true) + , statusBarVisible(true) + , toolBarVisible(true) + , locationBarVisible(true) + , scrollbarsVisible(true) + , resizable(true) + , fullscreen(false) + , dialog(false) + { + } + + WindowFeatures(const String& features); + + void setWindowFeature(const String& keyString, const String& valueString); + + static bool boolFeature(const HashMap<String, String>& features, const char* key, bool defaultValue = false); + static float floatFeature(const HashMap<String, String>& features, const char* key, float min, float max, float defaultValue); + + float x; + bool xSet; + float y; + bool ySet; + float width; + bool widthSet; + float height; + bool heightSet; + + bool menuBarVisible; + bool statusBarVisible; + bool toolBarVisible; + bool locationBarVisible; + bool scrollbarsVisible; + bool resizable; + + bool fullscreen; + bool dialog; + }; + +} // namespace WebCore + +#endif // WindowFeatures_h diff --git a/WebCore/page/gtk/DragControllerGtk.cpp b/WebCore/page/gtk/DragControllerGtk.cpp new file mode 100644 index 0000000..62f4421 --- /dev/null +++ b/WebCore/page/gtk/DragControllerGtk.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragController.h" + +#include "DragData.h" +#include "Frame.h" +#include "FrameView.h" +#include "Page.h" + +namespace WebCore { + +// FIXME: These values are straight out of DragControllerMac, so probably have +// little correlation with Gdk standards... +const int DragController::LinkDragBorderInset = 2; +const int DragController::MaxOriginalImageArea = 1500 * 1500; +const int DragController::DragIconRightInset = 7; +const int DragController::DragIconBottomInset = 3; + +const float DragController::DragImageAlpha = 0.75f; + +bool DragController::isCopyKeyDown() +{ + return false; +} + +DragOperation DragController::dragOperation(DragData* dragData) +{ + //FIXME: This logic is incomplete + if (dragData->containsURL()) + return DragOperationCopy; + + return DragOperationNone; +} + +const IntSize& DragController::maxDragImageSize() +{ + static const IntSize maxDragImageSize(400, 400); + + return maxDragImageSize; +} + +} diff --git a/WebCore/page/gtk/EventHandlerGtk.cpp b/WebCore/page/gtk/EventHandlerGtk.cpp new file mode 100644 index 0000000..8215a4e --- /dev/null +++ b/WebCore/page/gtk/EventHandlerGtk.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2006 Zack Rusin <zack@kde.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventHandler.h" + +#include "ClipboardGtk.h" +#include "EventNames.h" +#include "FloatPoint.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameView.h" +#include "KeyboardEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "NotImplemented.h" +#include "Page.h" +#include "PlatformScrollBar.h" +#include "PlatformWheelEvent.h" +#include "RenderWidget.h" + +namespace WebCore { + +using namespace EventNames; + +bool EventHandler::tabsToAllControls(KeyboardEvent* event) const +{ + // We always allow tabs to all controls + return true; +} + +void EventHandler::focusDocumentView() +{ + if (Page* page = m_frame->page()) + page->focusController()->setFocusedFrame(m_frame); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event) +{ + // Figure out which view to send the event to. + RenderObject* target = event.targetNode() ? event.targetNode()->renderer() : 0; + if (!target || !target->isWidget()) + return false; + + return passMouseDownEventToWidget(static_cast<RenderWidget*>(target)->widget()); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget) +{ + return passMouseDownEventToWidget(renderWidget->widget()); +} + +bool EventHandler::passMouseDownEventToWidget(Widget* widget) +{ + notImplemented(); + return false; +} + +bool EventHandler::eventActivatedView(const PlatformMouseEvent&) const +{ + //GTK+ activation is not necessarily tied to mouse events, so it may + //not make sense to implement this + + notImplemented(); + return false; +} + +bool EventHandler::passWheelEventToWidget(PlatformWheelEvent& event, Widget* widget) +{ + ASSERT(widget); + if (!widget->isFrameView()) + return false; + + return static_cast<FrameView*>(widget)->frame()->eventHandler()->handleWheelEvent(event); +} + +Clipboard* EventHandler::createDraggingClipboard() const +{ + return new ClipboardGtk(ClipboardWritable, true); +} + +bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMousePressEvent(mev.event()); + return true; +} + +bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode) +{ + subframe->eventHandler()->handleMouseMoveEvent(mev.event(), hoveredNode); + return true; +} + +bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMouseReleaseEvent(mev.event()); + return true; +} + +bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar* scrollbar) +{ + notImplemented(); + return false; +} + +} diff --git a/WebCore/page/gtk/FrameGtk.cpp b/WebCore/page/gtk/FrameGtk.cpp new file mode 100644 index 0000000..b2afcbd --- /dev/null +++ b/WebCore/page/gtk/FrameGtk.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com + * Copyright (C) 2007 Holger Hans Peter Freyther + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Frame.h" +#include "NotImplemented.h" + + +namespace WebCore { + +KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(Widget*) +{ + notImplemented(); + return 0; +} + +void Frame::clearPlatformScriptObjects() +{ + notImplemented(); +} + +DragImageRef Frame::dragImageForSelection() +{ + notImplemented(); + return 0; +} + +void Frame::dashboardRegionsChanged() +{ + notImplemented(); +} + +} diff --git a/WebCore/page/inspector/ConsolePanel.js b/WebCore/page/inspector/ConsolePanel.js new file mode 100644 index 0000000..57bf331 --- /dev/null +++ b/WebCore/page/inspector/ConsolePanel.js @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ConsolePanel = function() +{ + WebInspector.Panel.call(this); + + this.messages = []; + + this.commandHistory = []; + this.commandOffset = 0; + + this.messageList = document.createElement("ol"); + this.messageList.className = "console-message-list"; + this.element.appendChild(this.messageList); + + this.messageList.addEventListener("click", this.messageListClicked.bind(this), true); + + this.consolePrompt = document.createElement("textarea"); + this.consolePrompt.className = "console-prompt"; + this.element.appendChild(this.consolePrompt); + + this.consolePrompt.addEventListener("keydown", this.promptKeyDown.bind(this), false); + + var clearButtonText = WebInspector.UIString("Clear"); + this.clearMessagesElement = document.createElement("button"); + this.clearMessagesElement.appendChild(document.createTextNode(clearButtonText)); + this.clearMessagesElement.title = clearButtonText; + this.clearMessagesElement.addEventListener("click", this.clearButtonClicked.bind(this), false); +} + +WebInspector.ConsolePanel.prototype = { + show: function() + { + WebInspector.Panel.prototype.show.call(this); + WebInspector.consoleListItem.select(); + + this.clearMessagesElement.removeStyleClass("hidden"); + if (!this.clearMessagesElement.parentNode) + document.getElementById("toolbarButtons").appendChild(this.clearMessagesElement); + }, + + hide: function() + { + WebInspector.Panel.prototype.hide.call(this); + WebInspector.consoleListItem.deselect(); + this.clearMessagesElement.addStyleClass("hidden"); + }, + + addMessage: function(msg) + { + if (msg.url in WebInspector.resourceURLMap) { + msg.resource = WebInspector.resourceURLMap[msg.url]; + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + ++msg.resource.warnings; + msg.resource.panel.addMessageToSource(msg); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + ++msg.resource.errors; + msg.resource.panel.addMessageToSource(msg); + break; + } + } + this.messages.push(msg); + + var item = msg.toListItem(); + item.message = msg; + this.messageList.appendChild(item); + item.scrollIntoView(false); + }, + + clearMessages: function() + { + for (var i = 0; i < this.messages.length; ++i) { + var resource = this.messages[i].resource; + if (!resource) + continue; + + resource.errors = 0; + resource.warnings = 0; + } + + this.messages = []; + this.messageList.removeChildren(); + }, + + clearButtonClicked: function() + { + this.clearMessages(); + }, + + messageListClicked: function(event) + { + var link = event.target.firstParentOrSelfWithNodeName("a"); + if (link && link.representedNode) { + WebInspector.updateFocusedNode(link.representedNode); + return; + } + + var item = event.target.firstParentOrSelfWithNodeName("li"); + if (!item) + return; + + var resource = item.message.resource; + if (!resource) + return; + + if (link && link.hasStyleClass("console-message-url")) { + WebInspector.navigateToResource(resource); + resource.panel.showSourceLine(item.message.line); + } + + event.stopPropagation(); + event.preventDefault(); + }, + + promptKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Enter": + this._onEnterPressed(event); + break; + case "Up": + this._onUpPressed(event); + break; + case "Down": + this._onDownPressed(event); + break; + } + }, + + _onEnterPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + var str = this.consolePrompt.value; + if (!str.length) + return; + + this.commandHistory.push(str); + this.commandOffset = 0; + + this.consolePrompt.value = ""; + + var result; + var exception = false; + try { + // This with block is needed to work around http://bugs.webkit.org/show_bug.cgi?id=11399 + with (InspectorController.inspectedWindow()) { + result = eval(str); + } + } catch(e) { + result = e; + exception = true; + } + + var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log; + + this.addMessage(new WebInspector.ConsoleCommand(str, this._format(result))); + }, + + _onUpPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.commandOffset == this.commandHistory.length) + return; + + if (this.commandOffset == 0) + this.tempSavedCommand = this.consolePrompt.value; + + ++this.commandOffset; + this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset]; + this.consolePrompt.moveCursorToEnd(); + }, + + _onDownPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.commandOffset == 0) + return; + + --this.commandOffset; + + if (this.commandOffset == 0) { + this.consolePrompt.value = this.tempSavedCommand; + this.consolePrompt.moveCursorToEnd(); + delete this.tempSavedCommand; + return; + } + + this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset]; + this.consolePrompt.moveCursorToEnd(); + }, + + _format: function(output) + { + var type = Object.type(output); + if (type === "object") { + if (output instanceof Node) + type = "node"; + } + + // We don't perform any special formatting on these types, so we just + // pass them through the simple _formatvalue function. + var undecoratedTypes = { + "undefined": 1, + "null": 1, + "boolean": 1, + "number": 1, + "date": 1, + "function": 1, + }; + + var formatter; + if (type in undecoratedTypes) + formatter = "_formatvalue"; + else { + formatter = "_format" + type; + if (!(formatter in this)) { + formatter = "_formatobject"; + type = "object"; + } + } + + var span = document.createElement("span"); + span.addStyleClass("console-formatted-" + type); + this[formatter](output, span); + return span; + }, + + _formatvalue: function(val, elem) + { + elem.appendChild(document.createTextNode(val)); + }, + + _formatstring: function(str, elem) + { + elem.appendChild(document.createTextNode("\"" + str + "\"")); + }, + + _formatregexp: function(re, elem) + { + var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); + elem.appendChild(document.createTextNode(formatted)); + }, + + _formatarray: function(arr, elem) + { + elem.appendChild(document.createTextNode("[")); + for (var i = 0; i < arr.length; ++i) { + elem.appendChild(this._format(arr[i])); + if (i < arr.length - 1) + elem.appendChild(document.createTextNode(", ")); + } + elem.appendChild(document.createTextNode("]")); + }, + + _formatnode: function(node, elem) + { + var anchor = document.createElement("a"); + anchor.innerHTML = node.titleInfo().title; + anchor.representedNode = node; + elem.appendChild(anchor); + }, + + _formatobject: function(obj, elem) + { + elem.appendChild(document.createTextNode(Object.describe(obj))); + }, +} + +WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.ConsoleMessage = function(source, level, message, line, url) +{ + this.source = source; + this.level = level; + this.message = message; + this.line = line; + this.url = url; +} + +WebInspector.ConsoleMessage.prototype = { + get shortURL() + { + if (this.resource) + return this.resource.displayName; + return this.url; + }, + + toListItem: function() + { + var item = document.createElement("li"); + item.className = "console-message"; + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + item.className += " console-html-source"; + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + item.className += " console-xml-source"; + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + item.className += " console-js-source"; + break; + case WebInspector.ConsoleMessage.MessageSource.CSS: + item.className += " console-css-source"; + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + item.className += " console-other-source"; + break; + } + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + item.className += " console-tip-level"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + item.className += " console-log-level"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + item.className += " console-warning-level"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + item.className += " console-error-level"; + } + + var messageDiv = document.createElement("div"); + messageDiv.className = "console-message-message"; + messageDiv.textContent = this.message; + item.appendChild(messageDiv); + + if (this.url && this.url !== "undefined") { + var urlElement = document.createElement("a"); + urlElement.className = "console-message-url"; + + if (this.line > 0) + urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line); + else + urlElement.textContent = this.url; + + item.appendChild(urlElement); + } + + return item; + }, + + toString: function() + { + var sourceString; + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + sourceString = "HTML"; + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + sourceString = "XML"; + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + sourceString = "JS"; + break; + case WebInspector.ConsoleMessage.MessageSource.CSS: + sourceString = "CSS"; + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + sourceString = "Other"; + break; + } + + var levelString; + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + levelString = "Tip"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + levelString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + levelString = "Warning"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + levelString = "Error"; + break; + } + + return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line; + } +} + +// Note: Keep these constants in sync with the ones in Chrome.h +WebInspector.ConsoleMessage.MessageSource = { + HTML: 0, + XML: 1, + JS: 2, + CSS: 3, + Other: 4, +}; + +WebInspector.ConsoleMessage.MessageLevel = { + Tip: 0, + Log: 1, + Warning: 2, + Error: 3, +}; + +WebInspector.ConsoleCommand = function(input, output) +{ + this.input = input; + this.output = output; +} + +WebInspector.ConsoleCommand.prototype = { + toListItem: function() + { + var item = document.createElement("li"); + item.className = "console-command"; + + var inputDiv = document.createElement("div"); + inputDiv.className = "console-command-input"; + inputDiv.textContent = this.input; + item.appendChild(inputDiv); + + var outputDiv = document.createElement("div"); + outputDiv.className = "console-command-output"; + outputDiv.appendChild(this.output); + item.appendChild(outputDiv); + + return item; + } +} diff --git a/WebCore/page/inspector/Database.js b/WebCore/page/inspector/Database.js new file mode 100644 index 0000000..36e89da --- /dev/null +++ b/WebCore/page/inspector/Database.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Database = function(database, domain, name, version) +{ + this.database = database; + this.domain = domain; + this.name = name; + this.version = version; + + this.listItem = new WebInspector.ResourceTreeElement(this); + this.updateTitle(); + + this.category.addResource(this); +} + +WebInspector.Database.prototype = { + get database() + { + return this._database; + }, + + set database(x) + { + if (this._database === x) + return; + this._database = x; + }, + + get name() + { + return this._name; + }, + + set name(x) + { + if (this._name === x) + return; + this._name = x; + this.updateTitleSoon(); + }, + + get version() + { + return this._version; + }, + + set version(x) + { + if (this._version === x) + return; + this._version = x; + }, + + get domain() + { + return this._domain; + }, + + set domain(x) + { + if (this._domain === x) + return; + this._domain = x; + this.updateTitleSoon(); + }, + + get category() + { + return WebInspector.resourceCategories.databases; + }, + + updateTitle: function() + { + delete this.updateTitleTimeout; + + var title = this.name; + + var info = ""; + if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain))) + info = this.domain; + + var fullTitle = "<span class=\"title" + (info && info.length ? "" : " only") + "\">" + title.escapeHTML() + "</span>"; + if (info && info.length) + fullTitle += "<span class=\"info\">" + info.escapeHTML() + "</span>"; + fullTitle += "<div class=\"icon database\"></div>"; + + this.listItem.title = fullTitle; + }, + + get panel() + { + if (!this._panel) + this._panel = new WebInspector.DatabasePanel(this); + return this._panel; + }, + + // Inherit the other functions from the Resource prototype. + updateTitleSoon: WebInspector.Resource.prototype.updateTitleSoon, + select: WebInspector.Resource.prototype.select, + deselect: WebInspector.Resource.prototype.deselect, + attach: WebInspector.Resource.prototype.attach, + detach: WebInspector.Resource.prototype.detach +} diff --git a/WebCore/page/inspector/DatabasePanel.js b/WebCore/page/inspector/DatabasePanel.js new file mode 100644 index 0000000..9101b9c --- /dev/null +++ b/WebCore/page/inspector/DatabasePanel.js @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DatabasePanel = function(database, views) +{ + var allViews = [{ title: WebInspector.UIString("Query"), name: "query" }, { title: WebInspector.UIString("Browse"), name: "browse" }]; + if (views) + allViews = allViews.concat(views); + + WebInspector.ResourcePanel.call(this, database, allViews); + + this.currentView = this.views.browse; + + this.queryPromptElement = document.createElement("textarea"); + this.queryPromptElement.className = "database-prompt"; + this.element.appendChild(this.queryPromptElement); + + this.queryPromptElement.addEventListener("keydown", this.queryInputKeyDown.bind(this), false); + + this.queryPromptHistory = []; + this.queryPromptHistoryOffset = 0; + + var queryView = this.views.query; + + queryView.commandListElement = document.createElement("ol"); + queryView.commandListElement.className = "database-command-list"; + queryView.contentElement.appendChild(queryView.commandListElement); + + var panel = this; + queryView.show = function() + { + panel.queryPromptElement.focus(); + this.commandListElement.scrollTop = this.previousScrollTop; + }; + + queryView.hide = function() + { + this.previousScrollTop = this.commandListElement.scrollTop; + }; + + var browseView = this.views.browse; + + browseView.reloadTableElement = document.createElement("button"); + browseView.reloadTableElement.appendChild(document.createElement("img")); + browseView.reloadTableElement.className = "database-table-reload"; + browseView.reloadTableElement.title = WebInspector.UIString("Reload"); + browseView.reloadTableElement.addEventListener("click", this.reloadClicked.bind(this), false); + + browseView.show = function() + { + panel.updateTableList(); + panel.queryPromptElement.focus(); + + this.tableSelectElement.removeStyleClass("hidden"); + if (!this.tableSelectElement.parentNode) + document.getElementById("toolbarButtons").appendChild(this.tableSelectElement); + + this.reloadTableElement.removeStyleClass("hidden"); + if (!this.reloadTableElement.parentNode) + document.getElementById("toolbarButtons").appendChild(this.reloadTableElement); + + this.contentElement.scrollTop = this.previousScrollTop; + }; + + browseView.hide = function() + { + this.tableSelectElement.addStyleClass("hidden"); + this.reloadTableElement.addStyleClass("hidden"); + this.previousScrollTop = this.contentElement.scrollTop; + }; +} + +// FIXME: The function and local variables are a workaround for http://bugs.webkit.org/show_bug.cgi?id=15574. +WebInspector.DatabasePanel.prototype = (function() { +var document = window.document; +var Math = window.Math; +return { + show: function() + { + WebInspector.ResourcePanel.prototype.show.call(this); + this.queryPromptElement.focus(); + }, + + get currentTable() + { + return this._currentTable; + }, + + set currentTable(x) + { + if (this._currentTable === x) + return; + + this._currentTable = x; + + if (x) { + var browseView = this.views.browse; + if (browseView.tableSelectElement) { + var length = browseView.tableSelectElement.options.length; + for (var i = 0; i < length; ++i) { + var option = browseView.tableSelectElement.options[i]; + if (option.value === x) { + browseView.tableSelectElement.selectedIndex = i; + break; + } + } + } + + this.updateTableBrowser(); + } + }, + + reloadClicked: function() + { + this.updateTableList(); + this.updateTableBrowser(); + }, + + updateTableList: function() + { + var browseView = this.views.browse; + if (!browseView.tableSelectElement) { + browseView.tableSelectElement = document.createElement("select"); + browseView.tableSelectElement.className = "database-table-select hidden"; + + var panel = this; + var changeTableFunction = function() + { + var index = browseView.tableSelectElement.selectedIndex; + if (index != -1) + panel.currentTable = browseView.tableSelectElement.options[index].value; + else + panel.currentTable = null; + }; + + browseView.tableSelectElement.addEventListener("change", changeTableFunction, false); + } + + browseView.tableSelectElement.removeChildren(); + + var selectedTableName = this.currentTable; + var tableNames = InspectorController.databaseTableNames(this.resource.database).sort(); + + var length = tableNames.length; + for (var i = 0; i < length; ++i) { + var option = document.createElement("option"); + option.value = tableNames[i]; + option.text = tableNames[i]; + browseView.tableSelectElement.appendChild(option); + + if (tableNames[i] === selectedTableName) + browseView.tableSelectElement.selectedIndex = i; + } + + if (!selectedTableName && length) + this.currentTable = tableNames[0]; + }, + + updateTableBrowser: function() + { + if (!this.currentTable) { + this.views.browse.contentElement.removeChildren(); + return; + } + + var panel = this; + var query = "SELECT * FROM " + this.currentTable; + this.resource.database.transaction(function(tx) + { + tx.executeSql(query, [], function(tx, result) { panel.browseQueryFinished(result) }, function(tx, error) { panel.browseQueryError(error) }); + }, function(tx, error) { panel.browseQueryError(error) }); + }, + + browseQueryFinished: function(result) + { + this.views.browse.contentElement.removeChildren(); + + var table = this._tableForResult(result); + if (!table) { + var emptyMsgElement = document.createElement("div"); + emptyMsgElement.className = "database-table-empty"; + emptyMsgElement.textContent = WebInspector.UIString("The “%s”\ntable is empty.", this.currentTable); + this.views.browse.contentElement.appendChild(emptyMsgElement); + return; + } + + var rowCount = table.getElementsByTagName("tr").length; + var columnCount = table.getElementsByTagName("tr").item(0).getElementsByTagName("th").length; + + var tr = document.createElement("tr"); + tr.className = "database-result-filler-row"; + table.appendChild(tr); + + if (!(rowCount % 2)) + tr.addStyleClass("alternate"); + + for (var i = 0; i < columnCount; ++i) { + var td = document.createElement("td"); + tr.appendChild(td); + } + + table.addStyleClass("database-browse-table"); + this.views.browse.contentElement.appendChild(table); + }, + + browseQueryError: function(error) + { + this.views.browse.contentElement.removeChildren(); + + var errorMsgElement = document.createElement("div"); + errorMsgElement.className = "database-table-error"; + errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.currentTable); + this.views.browse.contentElement.appendChild(errorMsgElement); + }, + + queryInputKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Enter": + this._onQueryInputEnterPressed(event); + break; + case "Up": + this._onQueryInputUpPressed(event); + break; + case "Down": + this._onQueryInputDownPressed(event); + break; + } + }, + + appendQueryResult: function(query, result, resultClassName) + { + var commandItem = document.createElement("li"); + commandItem.className = "database-command"; + + var queryDiv = document.createElement("div"); + queryDiv.className = "database-command-query"; + queryDiv.textContent = query; + commandItem.appendChild(queryDiv); + + var resultDiv = document.createElement("div"); + resultDiv.className = "database-command-result"; + commandItem.appendChild(resultDiv); + + if (resultClassName) + resultDiv.addStyleClass(resultClassName); + + if (typeof result === "string" || result instanceof String) + resultDiv.textContent = result; + else if (result && result.nodeName) + resultDiv.appendChild(result); + + this.views.query.commandListElement.appendChild(commandItem); + commandItem.scrollIntoView(false); + }, + + queryFinished: function(query, result) + { + this.appendQueryResult(query, this._tableForResult(result)); + }, + + queryError: function(query, error) + { + if (this.currentView !== this.views.query) + this.currentView = this.views.query; + + if (error.code == 1) + var message = error.message; + else if (error.code == 2) + var message = WebInspector.UIString("Database no longer has expected version."); + else + var message = WebInspector.UIString("An unexpected error %s occured.", error.code); + + this.appendQueryResult(query, message, "error"); + }, + + _onQueryInputEnterPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + var query = this.queryPromptElement.value; + if (!query.length) + return; + + var panel = this; + this.resource.database.transaction(function(tx) + { + tx.executeSql(query, [], function(tx, result) { panel.queryFinished(query, result) }, function(tx, error) { panel.queryError(query, error) }); + }, function(tx, error) { panel.queryError(query, error) }); + + this.queryPromptHistory.push(query); + this.queryPromptHistoryOffset = 0; + + this.queryPromptElement.value = ""; + + if (query.match(/^select /i)) { + if (this.currentView !== this.views.query) + this.currentView = this.views.query; + } else { + if (query.match(/^create /i) || query.match(/^drop table /i)) + this.updateTableList(); + + // FIXME: we should only call updateTableBrowser() is we know the current table was modified + this.updateTableBrowser(); + } + }, + + _onQueryInputUpPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.queryPromptHistoryOffset == this.queryPromptHistory.length) + return; + + if (this.queryPromptHistoryOffset == 0) + this.tempSavedQuery = this.queryPromptElement.value; + + ++this.queryPromptHistoryOffset; + this.queryPromptElement.value = this.queryPromptHistory[this.queryPromptHistory.length - this.queryPromptHistoryOffset]; + this.queryPromptElement.moveCursorToEnd(); + }, + + _onQueryInputDownPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.queryPromptHistoryOffset == 0) + return; + + --this.queryPromptHistoryOffset; + + if (this.queryPromptHistoryOffset == 0) { + this.queryPromptElement.value = this.tempSavedQuery; + this.queryPromptElement.moveCursorToEnd(); + delete this.tempSavedQuery; + return; + } + + this.queryPromptElement.value = this.queryPromptHistory[this.queryPromptHistory.length - this.queryPromptHistoryOffset]; + this.queryPromptElement.moveCursorToEnd(); + }, + + _tableForResult: function(result) + { + if (!result.rows.length) + return null; + + var rows = result.rows; + var length = rows.length; + var columnWidths = []; + + var table = document.createElement("table"); + table.className = "database-result-table"; + + var headerRow = document.createElement("tr"); + table.appendChild(headerRow); + + var j = 0; + for (var column in rows.item(0)) { + var th = document.createElement("th"); + headerRow.appendChild(th); + + var div = document.createElement("div"); + div.textContent = column; + div.title = column; + th.appendChild(div); + + columnWidths[j++] = column.length; + } + + for (var i = 0; i < length; ++i) { + var row = rows.item(i); + var tr = document.createElement("tr"); + if (i % 2) + tr.className = "alternate"; + table.appendChild(tr); + + var j = 0; + for (var column in row) { + var td = document.createElement("td"); + tr.appendChild(td); + + var text = row[column]; + var div = document.createElement("div"); + div.textContent = text; + div.title = text; + td.appendChild(div); + + if (text.length > columnWidths[j]) + columnWidths[j] = text.length; + ++j; + } + } + + var totalColumnWidths = 0; + length = columnWidths.length; + for (var i = 0; i < length; ++i) + totalColumnWidths += columnWidths[i]; + + // Calculate the percentage width for the columns. + var minimumPrecent = 5; + var recoupPercent = 0; + for (var i = 0; i < length; ++i) { + columnWidths[i] = Math.round((columnWidths[i] / totalColumnWidths) * 100); + if (columnWidths[i] < minimumPrecent) { + recoupPercent += (minimumPrecent - columnWidths[i]); + columnWidths[i] = minimumPrecent; + } + } + + // Enforce the minimum percentage width. + while (recoupPercent > 0) { + for (var i = 0; i < length; ++i) { + if (columnWidths[i] > minimumPrecent) { + --columnWidths[i]; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + length = headerRow.childNodes.length; + for (var i = 0; i < length; ++i) { + var th = headerRow.childNodes[i]; + th.style.width = columnWidths[i] + "%"; + } + + return table; + } +} +})(); + +WebInspector.DatabasePanel.prototype.__proto__ = WebInspector.ResourcePanel.prototype; diff --git a/WebCore/page/inspector/DocumentPanel.js b/WebCore/page/inspector/DocumentPanel.js new file mode 100644 index 0000000..8528196 --- /dev/null +++ b/WebCore/page/inspector/DocumentPanel.js @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DocumentPanel = function(resource, views) +{ + var allViews = [{ title: WebInspector.UIString("DOM"), name: "dom" }]; + if (views) + allViews = allViews.concat(views); + + WebInspector.SourcePanel.call(this, resource, allViews); + + var panel = this; + var domView = this.views.dom; + domView.hide = function() { InspectorController.hideDOMNodeHighlight() }; + domView.show = function() { + InspectorController.highlightDOMNode(panel.focusedDOMNode); + panel.updateBreadcrumb(); + panel.updateTreeSelection(); + }; + + domView.sideContentElement = document.createElement("div"); + domView.sideContentElement.className = "content side"; + + domView.treeContentElement = document.createElement("div"); + domView.treeContentElement.className = "content tree outline-disclosure"; + + domView.treeListElement = document.createElement("ol"); + domView.treeOutline = new TreeOutline(domView.treeListElement); + domView.treeOutline.panel = this; + + domView.crumbsElement = document.createElement("div"); + domView.crumbsElement.className = "crumbs"; + + domView.innerCrumbsElement = document.createElement("div"); + domView.crumbsElement.appendChild(domView.innerCrumbsElement); + + domView.sidebarPanes = {}; + domView.sidebarPanes.styles = new WebInspector.StylesSidebarPane(); + domView.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); + domView.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); + + domView.sidebarPanes.styles.onexpand = function() { panel.updateStyles() }; + domView.sidebarPanes.metrics.onexpand = function() { panel.updateMetrics() }; + domView.sidebarPanes.properties.onexpand = function() { panel.updateProperties() }; + + domView.sidebarPanes.styles.expanded = true; + + domView.sidebarElement = document.createElement("div"); + domView.sidebarElement.className = "sidebar"; + + domView.sidebarElement.appendChild(domView.sidebarPanes.styles.element); + domView.sidebarElement.appendChild(domView.sidebarPanes.metrics.element); + domView.sidebarElement.appendChild(domView.sidebarPanes.properties.element); + + domView.sideContentElement.appendChild(domView.treeContentElement); + domView.sideContentElement.appendChild(domView.crumbsElement); + domView.treeContentElement.appendChild(domView.treeListElement); + + domView.sidebarResizeElement = document.createElement("div"); + domView.sidebarResizeElement.className = "sidebar-resizer-vertical sidebar-resizer-vertical-right"; + domView.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false); + + domView.contentElement.appendChild(domView.sideContentElement); + domView.contentElement.appendChild(domView.sidebarElement); + domView.contentElement.appendChild(domView.sidebarResizeElement); + + this.rootDOMNode = this.resource.documentNode; +} + +WebInspector.DocumentPanel.prototype = { + resize: function() + { + this.updateTreeSelection(); + this.updateBreadcrumbSizes(); + }, + + updateTreeSelection: function() + { + if (!this.views.dom.treeOutline || !this.views.dom.treeOutline.selectedTreeElement) + return; + var element = this.views.dom.treeOutline.selectedTreeElement; + element.updateSelection(); + }, + + get rootDOMNode() + { + return this._rootDOMNode; + }, + + set rootDOMNode(x) + { + if (this._rootDOMNode === x) + return; + + this._rootDOMNode = x; + + this.updateBreadcrumb(); + this.updateTreeOutline(); + }, + + get focusedDOMNode() + { + return this._focusedDOMNode; + }, + + set focusedDOMNode(x) + { + if (this._focusedDOMNode === x) { + var nodeItem = this.revealNode(x); + if (nodeItem) + nodeItem.select(); + return; + } + + this._focusedDOMNode = x; + + this.updateBreadcrumb(); + + for (var pane in this.views.dom.sidebarPanes) + this.views.dom.sidebarPanes[pane].needsUpdate = true; + + this.updateStyles(); + this.updateMetrics(); + this.updateProperties(); + + InspectorController.highlightDOMNode(x); + + var nodeItem = this.revealNode(x); + if (nodeItem) + nodeItem.select(); + }, + + revealNode: function(node) + { + var nodeItem = this.views.dom.treeOutline.findTreeElement(node, function(a, b) { return isAncestorNode.call(a, b); }, function(a) { return a.parentNode; }); + if (!nodeItem) + return; + + nodeItem.reveal(); + return nodeItem; + }, + + updateTreeOutline: function() + { + this.views.dom.treeOutline.removeChildrenRecursive(); + + if (!this.rootDOMNode) + return; + + // FIXME: this could use findTreeElement to reuse a tree element if it already exists + var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild); + while (node) { + this.views.dom.treeOutline.appendChild(new WebInspector.DOMNodeTreeElement(node)); + node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling; + } + + this.updateTreeSelection(); + }, + + updateBreadcrumb: function() + { + if (!this.visible) + return; + + var crumbs = this.views.dom.innerCrumbsElement; + + var handled = false; + var foundRoot = false; + var crumb = crumbs.firstChild; + while (crumb) { + if (crumb.representedObject === this.rootDOMNode) + foundRoot = true; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + else + crumb.removeStyleClass("dimmed"); + + if (crumb.representedObject === this.focusedDOMNode) { + crumb.addStyleClass("selected"); + handled = true; + } else { + crumb.removeStyleClass("selected"); + } + + crumb = crumb.nextSibling; + } + + if (handled) { + // We don't need to rebuild the crumbs, but we need to adjust sizes + // to reflect the new focused or root node. + this.updateBreadcrumbSizes(); + return; + } + + crumbs.removeChildren(); + + var panel = this; + var selectCrumbFunction = function(event) { + var crumb = event.currentTarget; + if (crumb.hasStyleClass("collapsed")) { + // Clicking a collapsed crumb will expose the hidden crumbs. + if (crumb === panel.views.dom.innerCrumbsElement.firstChild) { + // If the focused crumb is the first child, pick the farthest crumb + // that is still hidden. This allows the user to expose every crumb. + var currentCrumb = crumb; + while (currentCrumb) { + var hidden = currentCrumb.hasStyleClass("hidden"); + var collapsed = currentCrumb.hasStyleClass("collapsed"); + if (!hidden && !collapsed) + break; + crumb = currentCrumb; + currentCrumb = currentCrumb.nextSibling; + } + } + + panel.updateBreadcrumbSizes(crumb); + } else { + // Clicking a dimmed crumb or double clicking (event.detail >= 2) + // will change the root node in addition to the focused node. + if (event.detail >= 2 || crumb.hasStyleClass("dimmed")) + panel.rootDOMNode = crumb.representedObject.parentNode; + panel.focusedDOMNode = crumb.representedObject; + } + + event.preventDefault(); + }; + + var mouseOverCrumbFunction = function(event) { + panel.mouseOverCrumb = true; + + if ("mouseOutTimeout" in panel) { + clearTimeout(panel.mouseOutTimeout); + delete panel.mouseOutTimeout; + } + }; + + var mouseOutCrumbFunction = function(event) { + delete panel.mouseOverCrumb; + + if ("mouseOutTimeout" in panel) { + clearTimeout(panel.mouseOutTimeout); + delete panel.mouseOutTimeout; + } + + var timeoutFunction = function() { + if (!panel.mouseOverCrumb) + panel.updateBreadcrumbSizes(); + }; + + panel.mouseOutTimeout = setTimeout(timeoutFunction, 500); + }; + + foundRoot = false; + var current = this.focusedDOMNode; + while (current) { + if (current.nodeType === Node.DOCUMENT_NODE) + break; + + if (current === this.rootDOMNode) + foundRoot = true; + + var crumb = document.createElement("span"); + crumb.className = "crumb"; + crumb.representedObject = current; + crumb.addEventListener("mousedown", selectCrumbFunction, false); + crumb.addEventListener("mouseover", mouseOverCrumbFunction, false); + crumb.addEventListener("mouseout", mouseOutCrumbFunction, false); + + var crumbTitle; + switch (current.nodeType) { + case Node.ELEMENT_NODE: + crumbTitle = current.nodeName.toLowerCase(); + + var nameElement = document.createElement("span"); + nameElement.textContent = crumbTitle; + crumb.appendChild(nameElement); + + var idAttribute = current.getAttribute("id"); + if (idAttribute) { + var idElement = document.createElement("span"); + crumb.appendChild(idElement); + + var part = "#" + idAttribute; + crumbTitle += part; + idElement.appendChild(document.createTextNode(part)); + + // Mark the name as extra, since the ID is more important. + nameElement.className = "extra"; + } + + var classAttribute = current.getAttribute("class"); + if (classAttribute) { + var classes = classAttribute.split(/\s+/); + var foundClasses = {}; + + if (classes.length) { + var classesElement = document.createElement("span"); + classesElement.className = "extra"; + crumb.appendChild(classesElement); + + for (var i = 0; i < classes.length; ++i) { + var className = classes[i]; + if (className && !(className in foundClasses)) { + var part = "." + className; + crumbTitle += part; + classesElement.appendChild(document.createTextNode(part)); + foundClasses[className] = true; + } + } + } + } + + break; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(current)) + crumbTitle = WebInspector.UIString("(whitespace)"); + else + crumbTitle = WebInspector.UIString("(text)"); + break + + case Node.COMMENT_NODE: + crumbTitle = "<!-->"; + break; + + default: + crumbTitle = current.nodeName.toLowerCase(); + } + + if (!crumb.childNodes.length) { + var nameElement = document.createElement("span"); + nameElement.textContent = crumbTitle; + crumb.appendChild(nameElement); + } + + crumb.title = crumbTitle; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + if (current === this.focusedDOMNode) + crumb.addStyleClass("selected"); + if (!crumbs.childNodes.length) + crumb.addStyleClass("end"); + if (current.parentNode.nodeType === Node.DOCUMENT_NODE) + crumb.addStyleClass("start"); + + crumbs.appendChild(crumb); + current = current.parentNode; + } + + this.updateBreadcrumbSizes(); + }, + + updateBreadcrumbSizes: function(focusedCrumb) + { + if (!this.visible) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet, so we need to update later. + setTimeout(this.updateBreadcrumbSizes.bind(this), 0); + return; + } + + var crumbs = this.views.dom.innerCrumbsElement; + if (!crumbs.childNodes.length) + return; // No crumbs, do nothing. + + var crumbsContainer = this.views.dom.crumbsElement; + if (crumbsContainer.offsetWidth <= 0 || crumbs.offsetWidth <= 0) + return; + + // A Zero index is the right most child crumb in the breadcrumb. + var selectedIndex = 0; + var focusedIndex = 0; + var selectedCrumb; + + var i = 0; + var crumb = crumbs.firstChild; + while (crumb) { + // Find the selected crumb and index. + if (!selectedCrumb && crumb.hasStyleClass("selected")) { + selectedCrumb = crumb; + selectedIndex = i; + } + + // Find the focused crumb index. + if (crumb === focusedCrumb) + focusedIndex = i; + + // Remove any styles that affect size before + // deciding to shorten any crumbs. + if (crumb !== crumbs.lastChild) + crumb.removeStyleClass("start"); + if (crumb !== crumbs.firstChild) + crumb.removeStyleClass("end"); + + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + crumb.removeStyleClass("hidden"); + + crumb = crumb.nextSibling; + ++i; + } + + // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs(). + // The order of the crumbs in the document is opposite of the visual order. + crumbs.firstChild.addStyleClass("end"); + crumbs.lastChild.addStyleClass("start"); + + function crumbsAreSmallerThanContainer() + { + // There is some fixed extra space that is not returned in the crumbs' offsetWidth. + // This padding is added to the crumbs' offsetWidth when comparing to the crumbsContainer. + var rightPadding = 9; + return ((crumbs.offsetWidth + rightPadding) < crumbsContainer.offsetWidth); + } + + if (crumbsAreSmallerThanContainer()) + return; // No need to compact the crumbs, they all fit at full size. + + var BothSides = 0; + var AncestorSide = -1; + var ChildSide = 1; + + function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb) + { + if (!significantCrumb) + significantCrumb = (focusedCrumb || selectedCrumb); + + if (significantCrumb === selectedCrumb) + var significantIndex = selectedIndex; + else if (significantCrumb === focusedCrumb) + var significantIndex = focusedIndex; + else { + var significantIndex = 0; + for (var i = 0; i < crumbs.childNodes.length; ++i) { + if (crumbs.childNodes[i] === significantCrumb) { + significantIndex = i; + break; + } + } + } + + function shrinkCrumbAtIndex(index) + { + var shrinkCrumb = crumbs.childNodes[index]; + if (shrinkCrumb && shrinkCrumb !== significantCrumb) + shrinkingFunction(shrinkCrumb); + if (crumbsAreSmallerThanContainer()) + return true; // No need to compact the crumbs more. + return false; + } + + // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs + // fit in the crumbsContainer or we run out of crumbs to shrink. + if (direction) { + // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb. + var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); + while (index !== significantIndex) { + if (shrinkCrumbAtIndex(index)) + return true; + index += (direction > 0 ? 1 : -1); + } + } else { + // Crumbs are shrunk in order of descending distance from the signifcant crumb, + // with a tie going to child crumbs. + var startIndex = 0; + var endIndex = crumbs.childNodes.length - 1; + while (startIndex != significantIndex || endIndex != significantIndex) { + var startDistance = significantIndex - startIndex; + var endDistance = endIndex - significantIndex; + if (startDistance >= endDistance) + var index = startIndex++; + else + var index = endIndex--; + if (shrinkCrumbAtIndex(index)) + return true; + } + } + + // We are not small enough yet, return false so the caller knows. + return false; + } + + function coalesceCollapsedCrumbs() + { + var crumb = crumbs.firstChild; + var collapsedRun = false; + var newStartNeeded = false; + var newEndNeeded = false; + while (crumb) { + var hidden = crumb.hasStyleClass("hidden"); + if (!hidden) { + var collapsed = crumb.hasStyleClass("collapsed"); + if (collapsedRun && collapsed) { + crumb.addStyleClass("hidden"); + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + + if (crumb.hasStyleClass("start")) { + crumb.removeStyleClass("start"); + newStartNeeded = true; + } + + if (crumb.hasStyleClass("end")) { + crumb.removeStyleClass("end"); + newEndNeeded = true; + } + + continue; + } + + collapsedRun = collapsed; + + if (newEndNeeded) { + newEndNeeded = false; + crumb.addStyleClass("end"); + } + } else + collapsedRun = true; + crumb = crumb.nextSibling; + } + + if (newStartNeeded) { + crumb = crumbs.lastChild; + while (crumb) { + if (!crumb.hasStyleClass("hidden")) { + crumb.addStyleClass("start"); + break; + } + crumb = crumb.previousSibling; + } + } + } + + function compact(crumb) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("compact"); + } + + function collapse(crumb, dontCoalesce) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("collapsed"); + crumb.removeStyleClass("compact"); + if (!dontCoalesce) + coalesceCollapsedCrumbs(); + } + + function compactDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + compact(crumb); + } + + function collapseDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + collapse(crumb); + } + + if (!focusedCrumb) { + // When not focused on a crumb we can be biased and collapse less important + // crumbs that the user might not care much about. + + // Compact child crumbs. + if (makeCrumbsSmaller(compact, ChildSide)) + return; + + // Collapse child crumbs. + if (makeCrumbsSmaller(collapse, ChildSide)) + return; + + // Compact dimmed ancestor crumbs. + if (makeCrumbsSmaller(compactDimmed, AncestorSide)) + return; + + // Collapse dimmed ancestor crumbs. + if (makeCrumbsSmaller(collapseDimmed, AncestorSide)) + return; + } + + // Compact ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide))) + return; + + // Collapse ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide))) + return; + + if (!selectedCrumb) + return; + + // Compact the selected crumb. + compact(selectedCrumb); + if (crumbsAreSmallerThanContainer()) + return; + + // Collapse the selected crumb as a last resort. Pass true to prevent coalescing. + collapse(selectedCrumb, true); + }, + + updateStyles: function() + { + var stylesSidebarPane = this.views.dom.sidebarPanes.styles; + if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate) + return; + + stylesSidebarPane.update(this.focusedDOMNode); + stylesSidebarPane.needsUpdate = false; + }, + + updateMetrics: function() + { + var metricsSidebarPane = this.views.dom.sidebarPanes.metrics; + if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate) + return; + + metricsSidebarPane.update(this.focusedDOMNode); + metricsSidebarPane.needsUpdate = false; + }, + + updateProperties: function() + { + var propertiesSidebarPane = this.views.dom.sidebarPanes.properties; + if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate) + return; + + propertiesSidebarPane.update(this.focusedDOMNode); + propertiesSidebarPane.needsUpdate = false; + }, + + handleKeyEvent: function(event) + { + if (this.views.dom.treeOutline && this.currentView && this.currentView === this.views.dom) + this.views.dom.treeOutline.handleKeyEvent(event); + }, + + handleCopyEvent: function(event) + { + if (this.currentView !== this.views.dom) + return; + + // Don't prevent the normal copy if the user has a selection. + if (!window.getSelection().isCollapsed) + return; + + switch (this.focusedDOMNode.nodeType) { + case Node.ELEMENT_NODE: + var data = this.focusedDOMNode.outerHTML; + break; + + case Node.COMMENT_NODE: + var data = "<!--" + this.focusedDOMNode.nodeValue + "-->"; + break; + + default: + case Node.TEXT_NODE: + var data = this.focusedDOMNode.nodeValue; + } + + event.clipboardData.clearData(); + event.preventDefault(); + + if (data) + event.clipboardData.setData("text/plain", data); + }, + + rightSidebarResizerDragStart: function(event) + { + if (this.sidebarDragEventListener || this.sidebarDragEndEventListener) + return this.rightSidebarResizerDragEnd(event); + + this.sidebarDragEventListener = this.rightSidebarResizerDrag.bind(this); + this.sidebarDragEndEventListener = this.rightSidebarResizerDragEnd.bind(this); + WebInspector.elementDragStart(this.views.dom.sidebarElement, this.sidebarDragEventListener, this.sidebarDragEndEventListener, event, "col-resize"); + }, + + rightSidebarResizerDragEnd: function(event) + { + WebInspector.elementDragEnd(this.views.dom.sidebarElement, this.sidebarDragEventListener, this.sidebarDragEndEventListener, event); + delete this.sidebarDragEventListener; + delete this.sidebarDragEndEventListener; + }, + + rightSidebarResizerDrag: function(event) + { + var x = event.pageX; + + var leftSidebarWidth = document.getElementById("sidebar").offsetWidth; + var newWidth = Number.constrain(window.innerWidth - x, 100, window.innerWidth - leftSidebarWidth - 100); + + this.views.dom.sidebarElement.style.width = newWidth + "px"; + this.views.dom.sideContentElement.style.right = newWidth + "px"; + this.views.dom.sidebarResizeElement.style.right = (newWidth - 3) + "px"; + + this.updateTreeSelection(); + this.updateBreadcrumbSizes(); + + event.preventDefault(); + } +} + +WebInspector.DocumentPanel.prototype.__proto__ = WebInspector.SourcePanel.prototype; + +WebInspector.DOMNodeTreeElement = function(node) +{ + var hasChildren = (Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes()); + var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL); + + if (titleInfo.hasChildren) + this.whitespaceIgnored = Preferences.ignoreWhitespace; + + TreeElement.call(this, titleInfo.title, node, titleInfo.hasChildren); +} + +WebInspector.DOMNodeTreeElement.prototype = { + updateSelection: function() + { + var listItemElement = this.listItemElement; + if (!listItemElement) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet, so we need to update later. + setTimeout(this.updateSelection.bind(this), 0); + return; + } + + if (!this.selectionElement) { + this.selectionElement = document.createElement("div"); + this.selectionElement.className = "selection selected"; + listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild); + } + + this.selectionElement.style.height = listItemElement.offsetHeight + "px"; + }, + + onattach: function() + { + this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false); + }, + + onpopulate: function() + { + if (this.children.length || this.whitespaceIgnored !== Preferences.ignoreWhitespace) + return; + + this.removeChildren(); + this.whitespaceIgnored = Preferences.ignoreWhitespace; + + var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.representedObject) : this.representedObject.firstChild); + while (node) { + this.appendChild(new WebInspector.DOMNodeTreeElement(node)); + node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling; + } + + if (this.representedObject.nodeType == Node.ELEMENT_NODE) { + var title = "<span class=\"webkit-html-tag close\"></" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; + var item = new TreeElement(title, this.representedObject, false); + item.selectable = false; + this.appendChild(item); + } + }, + + onexpand: function() + { + this.treeOutline.panel.updateTreeSelection(); + }, + + oncollapse: function() + { + this.treeOutline.panel.updateTreeSelection(); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + }, + + onselect: function() + { + this.treeOutline.panel.focusedDOMNode = this.representedObject; + this.updateSelection(); + }, + + onmousedown: function(event) + { + // Prevent selecting the nearest word on double click. + if (event.detail >= 2) + event.preventDefault(); + }, + + ondblclick: function() + { + var panel = this.treeOutline.panel; + panel.rootDOMNode = this.representedObject.parentNode; + panel.focusedDOMNode = this.representedObject; + + if (this.hasChildren && !this.expanded) + this.expand(); + } +} + +WebInspector.DOMNodeTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/page/inspector/FontPanel.js b/WebCore/page/inspector/FontPanel.js new file mode 100644 index 0000000..c1ffd2f --- /dev/null +++ b/WebCore/page/inspector/FontPanel.js @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.FontPanel = function(resource) +{ + WebInspector.ResourcePanel.call(this, resource); + + this.element.addStyleClass("font"); + + var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier; + + this.fontPreviewElement = document.createElement("div"); + this.fontPreviewElement.className = "preview"; + this.element.appendChild(this.fontPreviewElement); + + this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null); + this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890"; + + this.updateFontPreviewSize(); +} + +WebInspector.FontPanel.prototype = { + show: function() + { + WebInspector.ResourcePanel.prototype.show.call(this); + this.updateFontPreviewSize(); + }, + + resize: function() + { + this.updateFontPreviewSize(); + }, + + updateFontPreviewSize: function () + { + if (!this.fontPreviewElement || !this.visible) + return; + + this.fontPreviewElement.removeStyleClass("preview"); + + var measureFontSize = 50; + this.fontPreviewElement.style.setProperty("position", "absolute", null); + this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null); + this.fontPreviewElement.style.removeProperty("height"); + + var height = this.fontPreviewElement.offsetHeight; + var width = this.fontPreviewElement.offsetWidth; + + var containerHeight = this.element.offsetHeight; + var containerWidth = this.element.offsetWidth; + + if (!height || !width || !containerHeight || !containerWidth) { + this.fontPreviewElement.style.removeProperty("font-size"); + this.fontPreviewElement.style.removeProperty("position"); + this.fontPreviewElement.addStyleClass("preview"); + return; + } + + var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1; + var realLineHeight = Math.floor(height / lineCount); + var fontSizeLineRatio = measureFontSize / realLineHeight; + var widthRatio = containerWidth / width; + var heightRatio = containerHeight / height; + + if (heightRatio < widthRatio) + var finalFontSize = Math.floor(realLineHeight * heightRatio * fontSizeLineRatio) - 1; + else + var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1; + + this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null); + this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null); + this.fontPreviewElement.style.removeProperty("position"); + + this.fontPreviewElement.addStyleClass("preview"); + } +} + +WebInspector.FontPanel.prototype.__proto__ = WebInspector.ResourcePanel.prototype; diff --git a/WebCore/page/inspector/ImagePanel.js b/WebCore/page/inspector/ImagePanel.js new file mode 100644 index 0000000..402b754 --- /dev/null +++ b/WebCore/page/inspector/ImagePanel.js @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ImagePanel = function(resource) +{ + WebInspector.ResourcePanel.call(this, resource); + + this.element.addStyleClass("image"); + + var container = document.createElement("div"); + container.className = "image"; + this.element.appendChild(container); + + this.imagePreviewElement = document.createElement("img"); + this.imagePreviewElement.setAttribute("src", this.resource.url); + + container.appendChild(this.imagePreviewElement); + + container = document.createElement("div"); + container.className = "info"; + this.element.appendChild(container); + + var imageNameElement = document.createElement("h1"); + imageNameElement.className = "title"; + imageNameElement.textContent = this.resource.displayName; + container.appendChild(imageNameElement); + + var infoListElement = document.createElement("dl"); + infoListElement.className = "infoList"; + + var imageProperties = [ + { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) }, + { name: WebInspector.UIString("File size"), value: WebInspector.UIString("%.2fKB", (this.resource.contentLength / 1024)) }, + { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType } + ]; + + var listHTML = ''; + for (var i = 0; i < imageProperties.length; ++i) + listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>"; + + infoListElement.innerHTML = listHTML; + container.appendChild(infoListElement); +} + +WebInspector.ImagePanel.prototype = { + +} + +WebInspector.ImagePanel.prototype.__proto__ = WebInspector.ResourcePanel.prototype; diff --git a/WebCore/page/inspector/Images/alternateTableRows.png b/WebCore/page/inspector/Images/alternateTableRows.png Binary files differnew file mode 100644 index 0000000..7706f50 --- /dev/null +++ b/WebCore/page/inspector/Images/alternateTableRows.png diff --git a/WebCore/page/inspector/Images/attachedShadow.png b/WebCore/page/inspector/Images/attachedShadow.png Binary files differnew file mode 100644 index 0000000..b0b687c --- /dev/null +++ b/WebCore/page/inspector/Images/attachedShadow.png diff --git a/WebCore/page/inspector/Images/backNormal.png b/WebCore/page/inspector/Images/backNormal.png Binary files differnew file mode 100644 index 0000000..cff21a5 --- /dev/null +++ b/WebCore/page/inspector/Images/backNormal.png diff --git a/WebCore/page/inspector/Images/bottomShadow.png b/WebCore/page/inspector/Images/bottomShadow.png Binary files differnew file mode 100644 index 0000000..61699c4 --- /dev/null +++ b/WebCore/page/inspector/Images/bottomShadow.png diff --git a/WebCore/page/inspector/Images/breadcrumbBackground.png b/WebCore/page/inspector/Images/breadcrumbBackground.png Binary files differnew file mode 100644 index 0000000..516452b --- /dev/null +++ b/WebCore/page/inspector/Images/breadcrumbBackground.png diff --git a/WebCore/page/inspector/Images/checker.png b/WebCore/page/inspector/Images/checker.png Binary files differnew file mode 100644 index 0000000..8349908 --- /dev/null +++ b/WebCore/page/inspector/Images/checker.png diff --git a/WebCore/page/inspector/Images/console.png b/WebCore/page/inspector/Images/console.png Binary files differnew file mode 100644 index 0000000..c85fed0 --- /dev/null +++ b/WebCore/page/inspector/Images/console.png diff --git a/WebCore/page/inspector/Images/darkShadow.png b/WebCore/page/inspector/Images/darkShadow.png Binary files differnew file mode 100644 index 0000000..2761b7d --- /dev/null +++ b/WebCore/page/inspector/Images/darkShadow.png diff --git a/WebCore/page/inspector/Images/database.png b/WebCore/page/inspector/Images/database.png Binary files differnew file mode 100644 index 0000000..2c13789 --- /dev/null +++ b/WebCore/page/inspector/Images/database.png diff --git a/WebCore/page/inspector/Images/databaseBrowserViewNormal.png b/WebCore/page/inspector/Images/databaseBrowserViewNormal.png Binary files differnew file mode 100644 index 0000000..0d55522 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseBrowserViewNormal.png diff --git a/WebCore/page/inspector/Images/databaseBrowserViewNormalSelected.png b/WebCore/page/inspector/Images/databaseBrowserViewNormalSelected.png Binary files differnew file mode 100644 index 0000000..af32982 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseBrowserViewNormalSelected.png diff --git a/WebCore/page/inspector/Images/databaseBrowserViewSmall.png b/WebCore/page/inspector/Images/databaseBrowserViewSmall.png Binary files differnew file mode 100644 index 0000000..fa33eec --- /dev/null +++ b/WebCore/page/inspector/Images/databaseBrowserViewSmall.png diff --git a/WebCore/page/inspector/Images/databaseBrowserViewSmallSelected.png b/WebCore/page/inspector/Images/databaseBrowserViewSmallSelected.png Binary files differnew file mode 100644 index 0000000..6880954 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseBrowserViewSmallSelected.png diff --git a/WebCore/page/inspector/Images/databaseQueryViewNormal.png b/WebCore/page/inspector/Images/databaseQueryViewNormal.png Binary files differnew file mode 100644 index 0000000..326e8c3 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseQueryViewNormal.png diff --git a/WebCore/page/inspector/Images/databaseQueryViewNormalSelected.png b/WebCore/page/inspector/Images/databaseQueryViewNormalSelected.png Binary files differnew file mode 100644 index 0000000..8ffda5c --- /dev/null +++ b/WebCore/page/inspector/Images/databaseQueryViewNormalSelected.png diff --git a/WebCore/page/inspector/Images/databaseQueryViewSmall.png b/WebCore/page/inspector/Images/databaseQueryViewSmall.png Binary files differnew file mode 100644 index 0000000..d11a127 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseQueryViewSmall.png diff --git a/WebCore/page/inspector/Images/databaseQueryViewSmallSelected.png b/WebCore/page/inspector/Images/databaseQueryViewSmallSelected.png Binary files differnew file mode 100644 index 0000000..35be952 --- /dev/null +++ b/WebCore/page/inspector/Images/databaseQueryViewSmallSelected.png diff --git a/WebCore/page/inspector/Images/disclosureDownPressed.png b/WebCore/page/inspector/Images/disclosureDownPressed.png Binary files differnew file mode 100644 index 0000000..32ac517 --- /dev/null +++ b/WebCore/page/inspector/Images/disclosureDownPressed.png diff --git a/WebCore/page/inspector/Images/disclosureRightDown.png b/WebCore/page/inspector/Images/disclosureRightDown.png Binary files differnew file mode 100644 index 0000000..104ea86 --- /dev/null +++ b/WebCore/page/inspector/Images/disclosureRightDown.png diff --git a/WebCore/page/inspector/Images/disclosureRightPressed.png b/WebCore/page/inspector/Images/disclosureRightPressed.png Binary files differnew file mode 100644 index 0000000..41c9b20 --- /dev/null +++ b/WebCore/page/inspector/Images/disclosureRightPressed.png diff --git a/WebCore/page/inspector/Images/document.png b/WebCore/page/inspector/Images/document.png Binary files differnew file mode 100644 index 0000000..fa9c3f5 --- /dev/null +++ b/WebCore/page/inspector/Images/document.png diff --git a/WebCore/page/inspector/Images/domViewNormal.png b/WebCore/page/inspector/Images/domViewNormal.png Binary files differnew file mode 100644 index 0000000..6cd4ac4 --- /dev/null +++ b/WebCore/page/inspector/Images/domViewNormal.png diff --git a/WebCore/page/inspector/Images/domViewNormalSelected.png b/WebCore/page/inspector/Images/domViewNormalSelected.png Binary files differnew file mode 100644 index 0000000..5831819 --- /dev/null +++ b/WebCore/page/inspector/Images/domViewNormalSelected.png diff --git a/WebCore/page/inspector/Images/domViewSmall.png b/WebCore/page/inspector/Images/domViewSmall.png Binary files differnew file mode 100644 index 0000000..e9fb0da --- /dev/null +++ b/WebCore/page/inspector/Images/domViewSmall.png diff --git a/WebCore/page/inspector/Images/domViewSmallSelected.png b/WebCore/page/inspector/Images/domViewSmallSelected.png Binary files differnew file mode 100644 index 0000000..517479d --- /dev/null +++ b/WebCore/page/inspector/Images/domViewSmallSelected.png diff --git a/WebCore/page/inspector/Images/downTriangle.png b/WebCore/page/inspector/Images/downTriangle.png Binary files differnew file mode 100644 index 0000000..18a2a89 --- /dev/null +++ b/WebCore/page/inspector/Images/downTriangle.png diff --git a/WebCore/page/inspector/Images/errorIcon.png b/WebCore/page/inspector/Images/errorIcon.png Binary files differnew file mode 100644 index 0000000..d6ec461 --- /dev/null +++ b/WebCore/page/inspector/Images/errorIcon.png diff --git a/WebCore/page/inspector/Images/errorMediumIcon.png b/WebCore/page/inspector/Images/errorMediumIcon.png Binary files differnew file mode 100644 index 0000000..6ca32bb --- /dev/null +++ b/WebCore/page/inspector/Images/errorMediumIcon.png diff --git a/WebCore/page/inspector/Images/folder.png b/WebCore/page/inspector/Images/folder.png Binary files differnew file mode 100644 index 0000000..9bd7d9e --- /dev/null +++ b/WebCore/page/inspector/Images/folder.png diff --git a/WebCore/page/inspector/Images/forwardNormal.png b/WebCore/page/inspector/Images/forwardNormal.png Binary files differnew file mode 100644 index 0000000..5cb2c1b --- /dev/null +++ b/WebCore/page/inspector/Images/forwardNormal.png diff --git a/WebCore/page/inspector/Images/glossyHeader.png b/WebCore/page/inspector/Images/glossyHeader.png Binary files differnew file mode 100644 index 0000000..8c80b6b --- /dev/null +++ b/WebCore/page/inspector/Images/glossyHeader.png diff --git a/WebCore/page/inspector/Images/glossyHeaderPressed.png b/WebCore/page/inspector/Images/glossyHeaderPressed.png Binary files differnew file mode 100644 index 0000000..6b0dd60 --- /dev/null +++ b/WebCore/page/inspector/Images/glossyHeaderPressed.png diff --git a/WebCore/page/inspector/Images/goArrow.png b/WebCore/page/inspector/Images/goArrow.png Binary files differnew file mode 100644 index 0000000..f318a56 --- /dev/null +++ b/WebCore/page/inspector/Images/goArrow.png diff --git a/WebCore/page/inspector/Images/gradient.png b/WebCore/page/inspector/Images/gradient.png Binary files differnew file mode 100644 index 0000000..aa8568b --- /dev/null +++ b/WebCore/page/inspector/Images/gradient.png diff --git a/WebCore/page/inspector/Images/gradientHighlight.png b/WebCore/page/inspector/Images/gradientHighlight.png Binary files differnew file mode 100644 index 0000000..93bc9c1 --- /dev/null +++ b/WebCore/page/inspector/Images/gradientHighlight.png diff --git a/WebCore/page/inspector/Images/gradientHighlightBottom.png b/WebCore/page/inspector/Images/gradientHighlightBottom.png Binary files differnew file mode 100644 index 0000000..89b0b67 --- /dev/null +++ b/WebCore/page/inspector/Images/gradientHighlightBottom.png diff --git a/WebCore/page/inspector/Images/hideStatusWidget.png b/WebCore/page/inspector/Images/hideStatusWidget.png Binary files differnew file mode 100644 index 0000000..52ecece --- /dev/null +++ b/WebCore/page/inspector/Images/hideStatusWidget.png diff --git a/WebCore/page/inspector/Images/hideStatusWidgetPressed.png b/WebCore/page/inspector/Images/hideStatusWidgetPressed.png Binary files differnew file mode 100644 index 0000000..f041662 --- /dev/null +++ b/WebCore/page/inspector/Images/hideStatusWidgetPressed.png diff --git a/WebCore/page/inspector/Images/network.png b/WebCore/page/inspector/Images/network.png Binary files differnew file mode 100644 index 0000000..bc215bc --- /dev/null +++ b/WebCore/page/inspector/Images/network.png diff --git a/WebCore/page/inspector/Images/paneBottomGrow.png b/WebCore/page/inspector/Images/paneBottomGrow.png Binary files differnew file mode 100644 index 0000000..d55b865 --- /dev/null +++ b/WebCore/page/inspector/Images/paneBottomGrow.png diff --git a/WebCore/page/inspector/Images/paneBottomGrowActive.png b/WebCore/page/inspector/Images/paneBottomGrowActive.png Binary files differnew file mode 100644 index 0000000..ef3f259 --- /dev/null +++ b/WebCore/page/inspector/Images/paneBottomGrowActive.png diff --git a/WebCore/page/inspector/Images/paneGrowHandleLine.png b/WebCore/page/inspector/Images/paneGrowHandleLine.png Binary files differnew file mode 100644 index 0000000..4eaf61b --- /dev/null +++ b/WebCore/page/inspector/Images/paneGrowHandleLine.png diff --git a/WebCore/page/inspector/Images/paneHeader.png b/WebCore/page/inspector/Images/paneHeader.png Binary files differnew file mode 100644 index 0000000..cc66afa --- /dev/null +++ b/WebCore/page/inspector/Images/paneHeader.png diff --git a/WebCore/page/inspector/Images/paneHeaderActive.png b/WebCore/page/inspector/Images/paneHeaderActive.png Binary files differnew file mode 100644 index 0000000..08205fb --- /dev/null +++ b/WebCore/page/inspector/Images/paneHeaderActive.png diff --git a/WebCore/page/inspector/Images/plainDocument.png b/WebCore/page/inspector/Images/plainDocument.png Binary files differnew file mode 100644 index 0000000..2e92b71 --- /dev/null +++ b/WebCore/page/inspector/Images/plainDocument.png diff --git a/WebCore/page/inspector/Images/popupArrows.png b/WebCore/page/inspector/Images/popupArrows.png Binary files differnew file mode 100644 index 0000000..b980e46 --- /dev/null +++ b/WebCore/page/inspector/Images/popupArrows.png diff --git a/WebCore/page/inspector/Images/popupArrowsBlack.png b/WebCore/page/inspector/Images/popupArrowsBlack.png Binary files differnew file mode 100644 index 0000000..9c9b061 --- /dev/null +++ b/WebCore/page/inspector/Images/popupArrowsBlack.png diff --git a/WebCore/page/inspector/Images/reload.png b/WebCore/page/inspector/Images/reload.png Binary files differnew file mode 100644 index 0000000..9797d26 --- /dev/null +++ b/WebCore/page/inspector/Images/reload.png diff --git a/WebCore/page/inspector/Images/rightTriangle.png b/WebCore/page/inspector/Images/rightTriangle.png Binary files differnew file mode 100644 index 0000000..9f519f2 --- /dev/null +++ b/WebCore/page/inspector/Images/rightTriangle.png diff --git a/WebCore/page/inspector/Images/segment.png b/WebCore/page/inspector/Images/segment.png Binary files differnew file mode 100644 index 0000000..bcef26a --- /dev/null +++ b/WebCore/page/inspector/Images/segment.png diff --git a/WebCore/page/inspector/Images/segmentEnd.png b/WebCore/page/inspector/Images/segmentEnd.png Binary files differnew file mode 100644 index 0000000..374a53d --- /dev/null +++ b/WebCore/page/inspector/Images/segmentEnd.png diff --git a/WebCore/page/inspector/Images/segmentHover.png b/WebCore/page/inspector/Images/segmentHover.png Binary files differnew file mode 100644 index 0000000..f54c029 --- /dev/null +++ b/WebCore/page/inspector/Images/segmentHover.png diff --git a/WebCore/page/inspector/Images/segmentHoverEnd.png b/WebCore/page/inspector/Images/segmentHoverEnd.png Binary files differnew file mode 100644 index 0000000..e377c2d --- /dev/null +++ b/WebCore/page/inspector/Images/segmentHoverEnd.png diff --git a/WebCore/page/inspector/Images/segmentSelected.png b/WebCore/page/inspector/Images/segmentSelected.png Binary files differnew file mode 100644 index 0000000..a7f3a8f --- /dev/null +++ b/WebCore/page/inspector/Images/segmentSelected.png diff --git a/WebCore/page/inspector/Images/segmentSelectedEnd.png b/WebCore/page/inspector/Images/segmentSelectedEnd.png Binary files differnew file mode 100644 index 0000000..be2c61b --- /dev/null +++ b/WebCore/page/inspector/Images/segmentSelectedEnd.png diff --git a/WebCore/page/inspector/Images/showStatusWidget.png b/WebCore/page/inspector/Images/showStatusWidget.png Binary files differnew file mode 100644 index 0000000..051d883 --- /dev/null +++ b/WebCore/page/inspector/Images/showStatusWidget.png diff --git a/WebCore/page/inspector/Images/showStatusWidgetPressed.png b/WebCore/page/inspector/Images/showStatusWidgetPressed.png Binary files differnew file mode 100644 index 0000000..5622c9c --- /dev/null +++ b/WebCore/page/inspector/Images/showStatusWidgetPressed.png diff --git a/WebCore/page/inspector/Images/sidbarItemBackground.png b/WebCore/page/inspector/Images/sidbarItemBackground.png Binary files differnew file mode 100644 index 0000000..5b94167 --- /dev/null +++ b/WebCore/page/inspector/Images/sidbarItemBackground.png diff --git a/WebCore/page/inspector/Images/sidebarActionWidget.png b/WebCore/page/inspector/Images/sidebarActionWidget.png Binary files differnew file mode 100644 index 0000000..daa1aff --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarActionWidget.png diff --git a/WebCore/page/inspector/Images/sidebarActionWidgetPressed.png b/WebCore/page/inspector/Images/sidebarActionWidgetPressed.png Binary files differnew file mode 100644 index 0000000..794b187 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarActionWidgetPressed.png diff --git a/WebCore/page/inspector/Images/sidebarAttachWidget.png b/WebCore/page/inspector/Images/sidebarAttachWidget.png Binary files differnew file mode 100644 index 0000000..5f13f36 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarAttachWidget.png diff --git a/WebCore/page/inspector/Images/sidebarAttachWidgetPressed.png b/WebCore/page/inspector/Images/sidebarAttachWidgetPressed.png Binary files differnew file mode 100644 index 0000000..f70dd32 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarAttachWidgetPressed.png diff --git a/WebCore/page/inspector/Images/sidebarDetachWidget.png b/WebCore/page/inspector/Images/sidebarDetachWidget.png Binary files differnew file mode 100644 index 0000000..2443c21 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarDetachWidget.png diff --git a/WebCore/page/inspector/Images/sidebarDetachWidgetPressed.png b/WebCore/page/inspector/Images/sidebarDetachWidgetPressed.png Binary files differnew file mode 100644 index 0000000..e13f4c1 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarDetachWidgetPressed.png diff --git a/WebCore/page/inspector/Images/sidebarResizeWidget.png b/WebCore/page/inspector/Images/sidebarResizeWidget.png Binary files differnew file mode 100644 index 0000000..7c1ff70 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarResizeWidget.png diff --git a/WebCore/page/inspector/Images/sidebarSelection.png b/WebCore/page/inspector/Images/sidebarSelection.png Binary files differnew file mode 100644 index 0000000..fccfa0f --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelection.png diff --git a/WebCore/page/inspector/Images/sidebarSelectionBlurred.png b/WebCore/page/inspector/Images/sidebarSelectionBlurred.png Binary files differnew file mode 100644 index 0000000..1e49cdf --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelectionBlurred.png diff --git a/WebCore/page/inspector/Images/sidebarSelectionBlurredTall.png b/WebCore/page/inspector/Images/sidebarSelectionBlurredTall.png Binary files differnew file mode 100644 index 0000000..33a7cfc --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelectionBlurredTall.png diff --git a/WebCore/page/inspector/Images/sidebarSelectionGray.png b/WebCore/page/inspector/Images/sidebarSelectionGray.png Binary files differnew file mode 100644 index 0000000..f4faf2c --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelectionGray.png diff --git a/WebCore/page/inspector/Images/sidebarSelectionGrayTall.png b/WebCore/page/inspector/Images/sidebarSelectionGrayTall.png Binary files differnew file mode 100644 index 0000000..01379d2 --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelectionGrayTall.png diff --git a/WebCore/page/inspector/Images/sidebarSelectionTall.png b/WebCore/page/inspector/Images/sidebarSelectionTall.png Binary files differnew file mode 100644 index 0000000..0bb56da --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarSelectionTall.png diff --git a/WebCore/page/inspector/Images/sidebarStatusAreaBackground.png b/WebCore/page/inspector/Images/sidebarStatusAreaBackground.png Binary files differnew file mode 100644 index 0000000..0127d9d --- /dev/null +++ b/WebCore/page/inspector/Images/sidebarStatusAreaBackground.png diff --git a/WebCore/page/inspector/Images/sourceViewNormal.png b/WebCore/page/inspector/Images/sourceViewNormal.png Binary files differnew file mode 100644 index 0000000..2b23460 --- /dev/null +++ b/WebCore/page/inspector/Images/sourceViewNormal.png diff --git a/WebCore/page/inspector/Images/sourceViewNormalSelected.png b/WebCore/page/inspector/Images/sourceViewNormalSelected.png Binary files differnew file mode 100644 index 0000000..0c24b1c --- /dev/null +++ b/WebCore/page/inspector/Images/sourceViewNormalSelected.png diff --git a/WebCore/page/inspector/Images/sourceViewSmall.png b/WebCore/page/inspector/Images/sourceViewSmall.png Binary files differnew file mode 100644 index 0000000..82d06ef --- /dev/null +++ b/WebCore/page/inspector/Images/sourceViewSmall.png diff --git a/WebCore/page/inspector/Images/sourceViewSmallSelected.png b/WebCore/page/inspector/Images/sourceViewSmallSelected.png Binary files differnew file mode 100644 index 0000000..fe0351e --- /dev/null +++ b/WebCore/page/inspector/Images/sourceViewSmallSelected.png diff --git a/WebCore/page/inspector/Images/splitviewDimple.png b/WebCore/page/inspector/Images/splitviewDimple.png Binary files differnew file mode 100644 index 0000000..584ffd4 --- /dev/null +++ b/WebCore/page/inspector/Images/splitviewDimple.png diff --git a/WebCore/page/inspector/Images/splitviewDividerBackground.png b/WebCore/page/inspector/Images/splitviewDividerBackground.png Binary files differnew file mode 100644 index 0000000..1120a7f --- /dev/null +++ b/WebCore/page/inspector/Images/splitviewDividerBackground.png diff --git a/WebCore/page/inspector/Images/tab.png b/WebCore/page/inspector/Images/tab.png Binary files differnew file mode 100644 index 0000000..2f5000a --- /dev/null +++ b/WebCore/page/inspector/Images/tab.png diff --git a/WebCore/page/inspector/Images/tabSelected.png b/WebCore/page/inspector/Images/tabSelected.png Binary files differnew file mode 100644 index 0000000..76c2f66 --- /dev/null +++ b/WebCore/page/inspector/Images/tabSelected.png diff --git a/WebCore/page/inspector/Images/timelinePillBlue.png b/WebCore/page/inspector/Images/timelinePillBlue.png Binary files differnew file mode 100644 index 0000000..c897faa --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillBlue.png diff --git a/WebCore/page/inspector/Images/timelinePillGray.png b/WebCore/page/inspector/Images/timelinePillGray.png Binary files differnew file mode 100644 index 0000000..2128896 --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillGray.png diff --git a/WebCore/page/inspector/Images/timelinePillGreen.png b/WebCore/page/inspector/Images/timelinePillGreen.png Binary files differnew file mode 100644 index 0000000..9b66125 --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillGreen.png diff --git a/WebCore/page/inspector/Images/timelinePillOrange.png b/WebCore/page/inspector/Images/timelinePillOrange.png Binary files differnew file mode 100644 index 0000000..dd944fb --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillOrange.png diff --git a/WebCore/page/inspector/Images/timelinePillPurple.png b/WebCore/page/inspector/Images/timelinePillPurple.png Binary files differnew file mode 100644 index 0000000..21b96f7 --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillPurple.png diff --git a/WebCore/page/inspector/Images/timelinePillRed.png b/WebCore/page/inspector/Images/timelinePillRed.png Binary files differnew file mode 100644 index 0000000..f5e213b --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillRed.png diff --git a/WebCore/page/inspector/Images/timelinePillYellow.png b/WebCore/page/inspector/Images/timelinePillYellow.png Binary files differnew file mode 100644 index 0000000..ae2a5a2 --- /dev/null +++ b/WebCore/page/inspector/Images/timelinePillYellow.png diff --git a/WebCore/page/inspector/Images/tipBalloon.png b/WebCore/page/inspector/Images/tipBalloon.png Binary files differnew file mode 100644 index 0000000..4cdf738 --- /dev/null +++ b/WebCore/page/inspector/Images/tipBalloon.png diff --git a/WebCore/page/inspector/Images/tipBalloonBottom.png b/WebCore/page/inspector/Images/tipBalloonBottom.png Binary files differnew file mode 100644 index 0000000..3317a5a --- /dev/null +++ b/WebCore/page/inspector/Images/tipBalloonBottom.png diff --git a/WebCore/page/inspector/Images/tipIcon.png b/WebCore/page/inspector/Images/tipIcon.png Binary files differnew file mode 100644 index 0000000..8ca6124 --- /dev/null +++ b/WebCore/page/inspector/Images/tipIcon.png diff --git a/WebCore/page/inspector/Images/tipIconPressed.png b/WebCore/page/inspector/Images/tipIconPressed.png Binary files differnew file mode 100644 index 0000000..443e410 --- /dev/null +++ b/WebCore/page/inspector/Images/tipIconPressed.png diff --git a/WebCore/page/inspector/Images/toggleDown.png b/WebCore/page/inspector/Images/toggleDown.png Binary files differnew file mode 100644 index 0000000..9e73bfb --- /dev/null +++ b/WebCore/page/inspector/Images/toggleDown.png diff --git a/WebCore/page/inspector/Images/toggleUp.png b/WebCore/page/inspector/Images/toggleUp.png Binary files differnew file mode 100644 index 0000000..6b62aee --- /dev/null +++ b/WebCore/page/inspector/Images/toggleUp.png diff --git a/WebCore/page/inspector/Images/toolbarBackground.png b/WebCore/page/inspector/Images/toolbarBackground.png Binary files differnew file mode 100644 index 0000000..257d63d --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarBackground.png diff --git a/WebCore/page/inspector/Images/toolbarBackgroundInactive.png b/WebCore/page/inspector/Images/toolbarBackgroundInactive.png Binary files differnew file mode 100644 index 0000000..a62b8df --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarBackgroundInactive.png diff --git a/WebCore/page/inspector/Images/toolbarButtonNormal.png b/WebCore/page/inspector/Images/toolbarButtonNormal.png Binary files differnew file mode 100644 index 0000000..bd16c12 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonNormal.png diff --git a/WebCore/page/inspector/Images/toolbarButtonNormalInactive.png b/WebCore/page/inspector/Images/toolbarButtonNormalInactive.png Binary files differnew file mode 100644 index 0000000..3c9d5bc --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonNormalInactive.png diff --git a/WebCore/page/inspector/Images/toolbarButtonNormalPressed.png b/WebCore/page/inspector/Images/toolbarButtonNormalPressed.png Binary files differnew file mode 100644 index 0000000..aca9b02 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonNormalPressed.png diff --git a/WebCore/page/inspector/Images/toolbarButtonNormalSelected.png b/WebCore/page/inspector/Images/toolbarButtonNormalSelected.png Binary files differnew file mode 100644 index 0000000..41013b3 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonNormalSelected.png diff --git a/WebCore/page/inspector/Images/toolbarButtonNormalSelectedInactive.png b/WebCore/page/inspector/Images/toolbarButtonNormalSelectedInactive.png Binary files differnew file mode 100644 index 0000000..f6fdb08 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonNormalSelectedInactive.png diff --git a/WebCore/page/inspector/Images/toolbarButtonSmall.png b/WebCore/page/inspector/Images/toolbarButtonSmall.png Binary files differnew file mode 100644 index 0000000..04398fd --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonSmall.png diff --git a/WebCore/page/inspector/Images/toolbarButtonSmallInactive.png b/WebCore/page/inspector/Images/toolbarButtonSmallInactive.png Binary files differnew file mode 100644 index 0000000..a6928be --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonSmallInactive.png diff --git a/WebCore/page/inspector/Images/toolbarButtonSmallPressed.png b/WebCore/page/inspector/Images/toolbarButtonSmallPressed.png Binary files differnew file mode 100644 index 0000000..0d83a38 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonSmallPressed.png diff --git a/WebCore/page/inspector/Images/toolbarButtonSmallSelected.png b/WebCore/page/inspector/Images/toolbarButtonSmallSelected.png Binary files differnew file mode 100644 index 0000000..d5433cb --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonSmallSelected.png diff --git a/WebCore/page/inspector/Images/toolbarButtonSmallSelectedInactive.png b/WebCore/page/inspector/Images/toolbarButtonSmallSelectedInactive.png Binary files differnew file mode 100644 index 0000000..41a3249 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarButtonSmallSelectedInactive.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonNormal.png b/WebCore/page/inspector/Images/toolbarPopupButtonNormal.png Binary files differnew file mode 100644 index 0000000..438b5ea --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonNormal.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonNormalInactive.png b/WebCore/page/inspector/Images/toolbarPopupButtonNormalInactive.png Binary files differnew file mode 100644 index 0000000..0e8f6a2 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonNormalInactive.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonNormalPressed.png b/WebCore/page/inspector/Images/toolbarPopupButtonNormalPressed.png Binary files differnew file mode 100644 index 0000000..23bcafd --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonNormalPressed.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonSmall.png b/WebCore/page/inspector/Images/toolbarPopupButtonSmall.png Binary files differnew file mode 100644 index 0000000..efa68a3 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonSmall.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonSmallInactive.png b/WebCore/page/inspector/Images/toolbarPopupButtonSmallInactive.png Binary files differnew file mode 100644 index 0000000..55e3c0c --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonSmallInactive.png diff --git a/WebCore/page/inspector/Images/toolbarPopupButtonSmallPressed.png b/WebCore/page/inspector/Images/toolbarPopupButtonSmallPressed.png Binary files differnew file mode 100644 index 0000000..56e574c --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarPopupButtonSmallPressed.png diff --git a/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormal.png b/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormal.png Binary files differnew file mode 100644 index 0000000..bf6de38 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormal.png diff --git a/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormalInactive.png b/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormalInactive.png Binary files differnew file mode 100644 index 0000000..c61541c --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarSplitButtonDividerNormalInactive.png diff --git a/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmall.png b/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmall.png Binary files differnew file mode 100644 index 0000000..9f248f6 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmall.png diff --git a/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmallInactive.png b/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmallInactive.png Binary files differnew file mode 100644 index 0000000..7365c34 --- /dev/null +++ b/WebCore/page/inspector/Images/toolbarSplitButtonDividerSmallInactive.png diff --git a/WebCore/page/inspector/Images/treeDownTriangleBlack.png b/WebCore/page/inspector/Images/treeDownTriangleBlack.png Binary files differnew file mode 100644 index 0000000..0821112 --- /dev/null +++ b/WebCore/page/inspector/Images/treeDownTriangleBlack.png diff --git a/WebCore/page/inspector/Images/treeDownTriangleWhite.png b/WebCore/page/inspector/Images/treeDownTriangleWhite.png Binary files differnew file mode 100644 index 0000000..1667b51 --- /dev/null +++ b/WebCore/page/inspector/Images/treeDownTriangleWhite.png diff --git a/WebCore/page/inspector/Images/treeLeftTriangleBlack.png b/WebCore/page/inspector/Images/treeLeftTriangleBlack.png Binary files differnew file mode 100644 index 0000000..81bc7e0 --- /dev/null +++ b/WebCore/page/inspector/Images/treeLeftTriangleBlack.png diff --git a/WebCore/page/inspector/Images/treeRightTriangleBlack.png b/WebCore/page/inspector/Images/treeRightTriangleBlack.png Binary files differnew file mode 100644 index 0000000..90de820 --- /dev/null +++ b/WebCore/page/inspector/Images/treeRightTriangleBlack.png diff --git a/WebCore/page/inspector/Images/treeRightTriangleWhite.png b/WebCore/page/inspector/Images/treeRightTriangleWhite.png Binary files differnew file mode 100644 index 0000000..2b6a82f --- /dev/null +++ b/WebCore/page/inspector/Images/treeRightTriangleWhite.png diff --git a/WebCore/page/inspector/Images/warningIcon.png b/WebCore/page/inspector/Images/warningIcon.png Binary files differnew file mode 100644 index 0000000..f37727e --- /dev/null +++ b/WebCore/page/inspector/Images/warningIcon.png diff --git a/WebCore/page/inspector/Images/warningMediumIcon.png b/WebCore/page/inspector/Images/warningMediumIcon.png Binary files differnew file mode 100644 index 0000000..291e111 --- /dev/null +++ b/WebCore/page/inspector/Images/warningMediumIcon.png diff --git a/WebCore/page/inspector/Images/warningsErrors.png b/WebCore/page/inspector/Images/warningsErrors.png Binary files differnew file mode 100644 index 0000000..878b593 --- /dev/null +++ b/WebCore/page/inspector/Images/warningsErrors.png diff --git a/WebCore/page/inspector/MetricsSidebarPane.js b/WebCore/page/inspector/MetricsSidebarPane.js new file mode 100644 index 0000000..e805560 --- /dev/null +++ b/WebCore/page/inspector/MetricsSidebarPane.js @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.MetricsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics")); +} + +WebInspector.MetricsSidebarPane.prototype = { + update: function(node) + { + var body = this.bodyElement; + + body.removeChildren(); + + if (!node) + return; + + var style; + if (node.nodeType === Node.ELEMENT_NODE) + style = node.ownerDocument.defaultView.getComputedStyle(node); + if (!style) + return; + + var metricsElement = document.createElement("div"); + metricsElement.className = "metrics"; + + function boxPartValue(style, name, suffix) + { + var value = style.getPropertyValue(name + suffix); + if (value === "" || value === "0px") + value = "\u2012"; + return value.replace(/px$/, ""); + } + + // Display types for which margin is ignored. + var noMarginDisplayType = { + "table-cell": true, + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + // Display types for which padding is ignored. + var noPaddingDisplayType = { + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + var boxes = ["content", "padding", "border", "margin"]; + var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin")]; + var previousBox; + for (var i = 0; i < boxes.length; ++i) { + var name = boxes[i]; + + if (name === "margin" && noMarginDisplayType[style.display]) + continue; + if (name === "padding" && noPaddingDisplayType[style.display]) + continue; + + var boxElement = document.createElement("div"); + boxElement.className = name; + + if (name === "content") { + var width = style.width.replace(/px$/, ""); + var height = style.height.replace(/px$/, ""); + boxElement.textContent = width + " \u00D7 " + height; + } else { + var suffix = (name === "border" ? "-width" : ""); + + var labelElement = document.createElement("div"); + labelElement.className = "label"; + labelElement.textContent = boxLabels[i]; + boxElement.appendChild(labelElement); + + var topElement = document.createElement("div"); + topElement.className = "top"; + topElement.textContent = boxPartValue(style, name + "-top", suffix); + boxElement.appendChild(topElement); + + var leftElement = document.createElement("div"); + leftElement.className = "left"; + leftElement.textContent = boxPartValue(style, name + "-left", suffix); + boxElement.appendChild(leftElement); + + if (previousBox) + boxElement.appendChild(previousBox); + + var rightElement = document.createElement("div"); + rightElement.className = "right"; + rightElement.textContent = boxPartValue(style, name + "-right", suffix); + boxElement.appendChild(rightElement); + + var bottomElement = document.createElement("div"); + bottomElement.className = "bottom"; + bottomElement.textContent = boxPartValue(style, name + "-bottom", suffix); + boxElement.appendChild(bottomElement); + } + + previousBox = boxElement; + } + + metricsElement.appendChild(previousBox); + body.appendChild(metricsElement); + } +} + +WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/page/inspector/NetworkPanel.js b/WebCore/page/inspector/NetworkPanel.js new file mode 100644 index 0000000..2f000ee --- /dev/null +++ b/WebCore/page/inspector/NetworkPanel.js @@ -0,0 +1,1036 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.NetworkPanel = function() +{ + WebInspector.Panel.call(this); + + this.timelineEntries = []; + + this.timelineElement = document.createElement("div"); + this.timelineElement.className = "network-timeline"; + this.element.appendChild(this.timelineElement); + + this.summaryElement = document.createElement("div"); + this.summaryElement.className = "network-summary"; + this.element.appendChild(this.summaryElement); + + this.dividersElement = document.createElement("div"); + this.dividersElement.className = "network-dividers"; + this.timelineElement.appendChild(this.dividersElement); + + this.resourcesElement = document.createElement("div"); + this.resourcesElement.className = "network-resources"; + this.resourcesElement.addEventListener("click", this.resourcesClicked.bind(this), false); + this.timelineElement.appendChild(this.resourcesElement); + + var graphArea = document.createElement("div"); + graphArea.className = "network-graph-area"; + this.summaryElement.appendChild(graphArea); + + this.graphLabelElement = document.createElement("div"); + this.graphLabelElement.className = "network-graph-label"; + graphArea.appendChild(this.graphLabelElement); + + this.graphModeSelectElement = document.createElement("select"); + this.graphModeSelectElement.className = "network-graph-mode"; + this.graphModeSelectElement.addEventListener("change", this.changeGraphMode.bind(this), false); + this.graphLabelElement.appendChild(this.graphModeSelectElement); + this.graphLabelElement.appendChild(document.createElement("br")); + + var sizeOptionElement = document.createElement("option"); + sizeOptionElement.calculator = new WebInspector.TransferSizeCalculator(); + sizeOptionElement.textContent = sizeOptionElement.calculator.title; + this.graphModeSelectElement.appendChild(sizeOptionElement); + + var timeOptionElement = document.createElement("option"); + timeOptionElement.calculator = new WebInspector.TransferTimeCalculator(); + timeOptionElement.textContent = timeOptionElement.calculator.title; + this.graphModeSelectElement.appendChild(timeOptionElement); + + var graphSideElement = document.createElement("div"); + graphSideElement.className = "network-graph-side"; + graphArea.appendChild(graphSideElement); + + this.summaryGraphElement = document.createElement("canvas"); + this.summaryGraphElement.setAttribute("width", "450"); + this.summaryGraphElement.setAttribute("height", "38"); + this.summaryGraphElement.className = "network-summary-graph"; + graphSideElement.appendChild(this.summaryGraphElement); + + this.legendElement = document.createElement("div"); + this.legendElement.className = "network-graph-legend"; + graphSideElement.appendChild(this.legendElement); + + this.drawSummaryGraph(); // draws an empty graph + + this.needsRefresh = true; +} + +WebInspector.NetworkPanel.prototype = { + show: function() + { + WebInspector.Panel.prototype.show.call(this); + WebInspector.networkListItem.select(); + this.refreshIfNeeded(); + }, + + hide: function() + { + WebInspector.Panel.prototype.hide.call(this); + WebInspector.networkListItem.deselect(); + }, + + resize: function() + { + this.updateTimelineDividersIfNeeded(); + }, + + resourcesClicked: function(event) + { + // If the click wasn't inside a network resource row, ignore it. + var resourceElement = event.target.firstParentOrSelfWithClass("network-resource"); + if (!resourceElement) + return; + + // If the click was within the network info element, ignore it. + var networkInfo = event.target.firstParentOrSelfWithClass("network-info"); + if (networkInfo) + return; + + // If the click was within the tip balloon element, hide it. + var balloon = event.target.firstParentOrSelfWithClass("tip-balloon"); + if (balloon) { + resourceElement.timelineEntry.showingTipBalloon = false; + return; + } + + resourceElement.timelineEntry.toggleShowingInfo(); + }, + + changeGraphMode: function(event) + { + this.updateSummaryGraph(); + }, + + get calculator() + { + return this.graphModeSelectElement.options[this.graphModeSelectElement.selectedIndex].calculator; + }, + + get totalDuration() + { + return this.latestEndTime - this.earliestStartTime; + }, + + get needsRefresh() + { + return this._needsRefresh; + }, + + set needsRefresh(x) + { + if (this._needsRefresh === x) + return; + this._needsRefresh = x; + if (x && this.visible) + this.refresh(); + }, + + refreshIfNeeded: function() + { + if (this.needsRefresh) + this.refresh(); + }, + + refresh: function() + { + this.needsRefresh = false; + + // calling refresh will call updateTimelineBoundriesIfNeeded, which can clear needsRefresh for future entries, + // so find all the entries that needs refresh first, then loop back trough them to call refresh + var entriesNeedingRefresh = []; + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) { + var entry = this.timelineEntries[i]; + if (entry.needsRefresh || entry.infoNeedsRefresh) + entriesNeedingRefresh.push(entry); + } + + entriesLength = entriesNeedingRefresh.length; + for (var i = 0; i < entriesLength; ++i) + entriesNeedingRefresh[i].refresh(false, true, true); + + this.updateTimelineDividersIfNeeded(); + this.sortTimelineEntriesIfNeeded(); + this.updateSummaryGraph(); + }, + + makeLegendElement: function(label, value, color) + { + var legendElement = document.createElement("label"); + legendElement.className = "network-graph-legend-item"; + + if (color) { + var swatch = document.createElement("canvas"); + swatch.className = "network-graph-legend-swatch"; + swatch.setAttribute("width", "13"); + swatch.setAttribute("height", "24"); + + legendElement.appendChild(swatch); + + this.drawSwatch(swatch, color); + } + + var labelElement = document.createElement("div"); + labelElement.className = "network-graph-legend-label"; + legendElement.appendChild(labelElement); + + var headerElement = document.createElement("div"); + var headerElement = document.createElement("div"); + headerElement.className = "network-graph-legend-header"; + headerElement.textContent = label; + labelElement.appendChild(headerElement); + + var valueElement = document.createElement("div"); + valueElement.className = "network-graph-legend-value"; + valueElement.textContent = value; + labelElement.appendChild(valueElement); + + return legendElement; + }, + + sortTimelineEntriesSoonIfNeeded: function() + { + if ("sortTimelineEntriesTimeout" in this) + return; + this.sortTimelineEntriesTimeout = setTimeout(this.sortTimelineEntriesIfNeeded.bind(this), 500); + }, + + sortTimelineEntriesIfNeeded: function() + { + if ("sortTimelineEntriesTimeout" in this) { + clearTimeout(this.sortTimelineEntriesTimeout); + delete this.sortTimelineEntriesTimeout; + } + + this.timelineEntries.sort(WebInspector.NetworkPanel.timelineEntryCompare); + + var nextSibling = null; + for (var i = (this.timelineEntries.length - 1); i >= 0; --i) { + var entry = this.timelineEntries[i]; + if (entry.resourceElement.nextSibling !== nextSibling) + this.resourcesElement.insertBefore(entry.resourceElement, nextSibling); + nextSibling = entry.resourceElement; + } + }, + + updateTimelineBoundriesIfNeeded: function(resource, immediate) + { + var didUpdate = false; + if (resource.startTime !== -1 && (this.earliestStartTime === undefined || resource.startTime < this.earliestStartTime)) { + this.earliestStartTime = resource.startTime; + didUpdate = true; + } + + if (resource.endTime !== -1 && (this.latestEndTime === undefined || resource.endTime > this.latestEndTime)) { + this.latestEndTime = resource.endTime; + didUpdate = true; + } + + if (didUpdate) { + if (immediate) { + this.refreshAllTimelineEntries(true, true, immediate); + this.updateTimelineDividersIfNeeded(); + } else { + this.refreshAllTimelineEntriesSoon(true, true, immediate); + this.updateTimelineDividersSoonIfNeeded(); + } + } + + return didUpdate; + }, + + updateTimelineDividersSoonIfNeeded: function() + { + if ("updateTimelineDividersTimeout" in this) + return; + this.updateTimelineDividersTimeout = setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 500); + }, + + updateTimelineDividersIfNeeded: function() + { + if ("updateTimelineDividersTimeout" in this) { + clearTimeout(this.updateTimelineDividersTimeout); + delete this.updateTimelineDividersTimeout; + } + + if (!this.visible) { + this.needsRefresh = true; + return; + } + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet, so we need to update later. + setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 0); + return; + } + + var dividerCount = Math.round(this.dividersElement.offsetWidth / 64); + var timeSlice = this.totalDuration / dividerCount; + + if (this.lastDividerTimeSlice === timeSlice) + return; + + this.lastDividerTimeSlice = timeSlice; + + this.dividersElement.removeChildren(); + + for (var i = 1; i <= dividerCount; ++i) { + var divider = document.createElement("div"); + divider.className = "network-divider"; + if (i === dividerCount) + divider.addStyleClass("last"); + divider.style.left = ((i / dividerCount) * 100) + "%"; + + var label = document.createElement("div"); + label.className = "network-divider-label"; + label.textContent = Number.secondsToString(timeSlice * i); + divider.appendChild(label); + + this.dividersElement.appendChild(divider); + } + }, + + refreshAllTimelineEntriesSoon: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if ("refreshAllTimelineEntriesTimeout" in this) + return; + this.refreshAllTimelineEntriesTimeout = setTimeout(this.refreshAllTimelineEntries.bind(this), 500, skipBoundryUpdate, skipTimelineSort, immediate); + }, + + refreshAllTimelineEntries: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if ("refreshAllTimelineEntriesTimeout" in this) { + clearTimeout(this.refreshAllTimelineEntriesTimeout); + delete this.refreshAllTimelineEntriesTimeout; + } + + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) + this.timelineEntries[i].refresh(skipBoundryUpdate, skipTimelineSort, immediate); + }, + + fadeOutRect: function(ctx, x, y, w, h, a1, a2) + { + ctx.save(); + + var gradient = ctx.createLinearGradient(x, y, x, y + h); + gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")"); + gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)"); + + ctx.globalCompositeOperation = "destination-out"; + + ctx.fillStyle = gradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + }, + + drawSwatch: function(canvas, color) + { + var ctx = canvas.getContext("2d"); + + function drawSwatchSquare() { + ctx.fillStyle = color; + ctx.fillRect(0, 0, 13, 13); + + var gradient = ctx.createLinearGradient(0, 0, 13, 13); + gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)"); + gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + gradient = ctx.createLinearGradient(13, 13, 0, 0); + gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.6)"; + ctx.strokeRect(0.5, 0.5, 12, 12); + } + + ctx.clearRect(0, 0, 13, 24); + + drawSwatchSquare(); + + ctx.save(); + + ctx.translate(0, 25); + ctx.scale(1, -1); + + drawSwatchSquare(); + + ctx.restore(); + + this.fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0); + }, + + drawSummaryGraph: function(segments) + { + if (!this.summaryGraphElement) + return; + + if (!segments || !segments.length) + segments = [{color: "white", value: 1}]; + + // Calculate the total of all segments. + var total = 0; + for (var i = 0; i < segments.length; ++i) + total += segments[i].value; + + // Calculate the percentage of each segment, rounded to the nearest percent. + var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) }); + + // Calculate the total percentage. + var percentTotal = 0; + for (var i = 0; i < percents.length; ++i) + percentTotal += percents[i]; + + // Make sure our percentage total is not greater-than 100, it can be greater + // if we rounded up for a few segments. + while (percentTotal > 100) { + for (var i = 0; i < percents.length && percentTotal > 100; ++i) { + if (percents[i] > 1) { + --percents[i]; + --percentTotal; + } + } + } + + // Make sure our percentage total is not less-than 100, it can be less + // if we rounded down for a few segments. + while (percentTotal < 100) { + for (var i = 0; i < percents.length && percentTotal < 100; ++i) { + ++percents[i]; + ++percentTotal; + } + } + + var ctx = this.summaryGraphElement.getContext("2d"); + + var x = 0; + var y = 0; + var w = 450; + var h = 19; + var r = (h / 2); + + function drawPillShadow() + { + // This draws a line with a shadow that is offset away from the line. The line is stroked + // twice with different X shadow offsets to give more feathered edges. Later we erase the + // line with destination-out 100% transparent black, leaving only the shadow. This only + // works if nothing has been drawn into the canvas yet. + + ctx.beginPath(); + ctx.moveTo(x + 4, y + h - 3 - 0.5); + ctx.lineTo(x + w - 4, y + h - 3 - 0.5); + ctx.closePath(); + + ctx.save(); + + ctx.shadowBlur = 2; + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowOffsetX = 3; + ctx.shadowOffsetY = 5; + + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.shadowOffsetX = -3; + + ctx.stroke(); + + ctx.restore(); + + ctx.save(); + + ctx.globalCompositeOperation = "destination-out"; + ctx.strokeStyle = "rgba(0, 0, 0, 1)"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.restore(); + } + + function drawPill() + { + // Make a rounded rect path. + ctx.beginPath(); + ctx.moveTo(x, y + r); + ctx.lineTo(x, y + h - r); + ctx.quadraticCurveTo(x, y + h, x + r, y + h); + ctx.lineTo(x + w - r, y + h); + ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r); + ctx.lineTo(x + w, y + r); + ctx.quadraticCurveTo(x + w, y, x + w - r, y); + ctx.lineTo(x + r, y); + ctx.quadraticCurveTo(x, y, x, y + r); + ctx.closePath(); + + // Clip to the rounded rect path. + ctx.save(); + ctx.clip(); + + // Fill the segments with the associated color. + var previousSegmentsWidth = 0; + for (var i = 0; i < segments.length; ++i) { + var segmentWidth = Math.round(w * percents[i] / 100); + ctx.fillStyle = segments[i].color; + ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h); + previousSegmentsWidth += segmentWidth; + } + + // Draw the segment divider lines. + ctx.lineWidth = 1; + for (var i = 1; i < 20; ++i) { + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"; + ctx.stroke(); + } + + // Draw the pill shading. + var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5)); + lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)"); + lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)"); + lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h); + darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)"); + darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)"); + darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)"); + + ctx.fillStyle = darkGradient; + ctx.fillRect(x, y, w, h); + + ctx.fillStyle = lightGradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + } + + ctx.clearRect(x, y, w, (h * 2)); + + drawPillShadow(); + drawPill(); + + ctx.save(); + + ctx.translate(0, (h * 2) + 1); + ctx.scale(1, -1); + + drawPill(); + + ctx.restore(); + + this.fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0); + }, + + updateSummaryGraphSoon: function() + { + if ("updateSummaryGraphTimeout" in this) + return; + this.updateSummaryGraphTimeout = setTimeout(this.updateSummaryGraph.bind(this), 500); + }, + + updateSummaryGraph: function() + { + if ("updateSummaryGraphTimeout" in this) { + clearTimeout(this.updateSummaryGraphTimeout); + delete this.updateSummaryGraphTimeout; + } + + var graphInfo = this.calculator.computeValues(this.timelineEntries); + + var categoryOrder = ["documents", "stylesheets", "images", "scripts", "fonts", "other"]; + var categoryColors = {documents: {r: 47, g: 102, b: 236}, stylesheets: {r: 157, g: 231, b: 119}, images: {r: 164, g: 60, b: 255}, scripts: {r: 255, g: 121, b: 0}, fonts: {r: 231, g: 231, b: 10}, other: {r: 186, g: 186, b: 186}}; + var fillSegments = []; + + this.legendElement.removeChildren(); + + if (this.totalLegendLabel) + this.totalLegendLabel.parentNode.removeChild(this.totalLegendLabel); + + this.totalLegendLabel = this.makeLegendElement(this.calculator.totalTitle, this.calculator.formatValue(graphInfo.total)); + this.totalLegendLabel.addStyleClass("network-graph-legend-total"); + this.graphLabelElement.appendChild(this.totalLegendLabel); + + for (var i = 0; i < categoryOrder.length; ++i) { + var category = categoryOrder[i]; + var size = graphInfo.categoryValues[category]; + if (!size) + continue; + + var color = categoryColors[category]; + var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")"; + + var fillSegment = {color: colorString, value: size}; + fillSegments.push(fillSegment); + + var legendLabel = this.makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString); + this.legendElement.appendChild(legendLabel); + } + + this.drawSummaryGraph(fillSegments); + }, + + clearTimeline: function() + { + delete this.earliestStartTime; + delete this.latestEndTime; + + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) + delete this.timelineEntries[i].resource.networkTimelineEntry; + + this.timelineEntries = []; + this.resourcesElement.removeChildren(); + + this.drawSummaryGraph(); // draws an empty graph + }, + + addResourceToTimeline: function(resource) + { + var timelineEntry = new WebInspector.NetworkTimelineEntry(this, resource); + this.timelineEntries.push(timelineEntry); + this.resourcesElement.appendChild(timelineEntry.resourceElement); + + timelineEntry.refresh(); + this.updateSummaryGraphSoon(); + } +} + +WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.NetworkPanel.timelineEntryCompare = function(a, b) +{ + if (a.resource.startTime < b.resource.startTime) + return -1; + if (a.resource.startTime > b.resource.startTime) + return 1; + if (a.resource.endTime < b.resource.endTime) + return -1; + if (a.resource.endTime > b.resource.endTime) + return 1; + return 0; +} + +WebInspector.NetworkTimelineEntry = function(panel, resource) +{ + this.panel = panel; + this.resource = resource; + resource.networkTimelineEntry = this; + + this.resourceElement = document.createElement("div"); + this.resourceElement.className = "network-resource"; + this.resourceElement.timelineEntry = this; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "network-title"; + this.resourceElement.appendChild(this.titleElement); + + this.fileElement = document.createElement("div"); + this.fileElement.className = "network-file"; + this.fileElement.innerHTML = WebInspector.linkifyURL(resource.url, resource.displayName); + this.titleElement.appendChild(this.fileElement); + + this.tipButtonElement = document.createElement("button"); + this.tipButtonElement.className = "tip-button"; + this.showingTipButton = this.resource.tips.length; + this.fileElement.insertBefore(this.tipButtonElement, this.fileElement.firstChild); + + this.tipButtonElement.addEventListener("click", this.toggleTipBalloon.bind(this), false ); + + this.areaElement = document.createElement("div"); + this.areaElement.className = "network-area"; + this.titleElement.appendChild(this.areaElement); + + this.barElement = document.createElement("div"); + this.areaElement.appendChild(this.barElement); + + this.infoElement = document.createElement("div"); + this.infoElement.className = "network-info hidden"; + this.resourceElement.appendChild(this.infoElement); +} + +WebInspector.NetworkTimelineEntry.prototype = { + refresh: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if (!this.panel.visible) { + this.needsRefresh = true; + this.panel.needsRefresh = true; + return; + } + + delete this.needsRefresh; + + if (!skipBoundryUpdate) { + if (this.panel.updateTimelineBoundriesIfNeeded(this.resource, immediate)) + return; // updateTimelineBoundriesIfNeeded calls refresh() on all entries, so we can just return + } + + if (!skipTimelineSort) { + if (immediate) + this.panel.sortTimelineEntriesIfNeeded(); + else + this.panel.sortTimelineEntriesSoonIfNeeded(); + } + + if (this.resource.startTime !== -1) { + var percentStart = ((this.resource.startTime - this.panel.earliestStartTime) / this.panel.totalDuration) * 100; + this.barElement.style.left = percentStart + "%"; + } else { + this.barElement.style.left = null; + } + + if (this.resource.endTime !== -1) { + var percentEnd = ((this.panel.latestEndTime - this.resource.endTime) / this.panel.totalDuration) * 100; + this.barElement.style.right = percentEnd + "%"; + } else { + this.barElement.style.right = "0px"; + } + + this.barElement.className = "network-bar network-category-" + this.resource.category.name; + + if (this.infoNeedsRefresh) + this.refreshInfo(); + }, + + refreshInfo: function() + { + if (!this.showingInfo) { + this.infoNeedsRefresh = true; + return; + } + + if (!this.panel.visible) { + this.panel.needsRefresh = true; + this.infoNeedsRefresh = true; + return; + } + + this.infoNeedsRefresh = false; + + this.infoElement.removeChildren(); + + var sections = [ + {title: WebInspector.UIString("Request"), info: this.resource.sortedRequestHeaders}, + {title: WebInspector.UIString("Response"), info: this.resource.sortedResponseHeaders} + ]; + + function createSectionTable(section) + { + if (!section.info.length) + return; + + var table = document.createElement("table"); + this.infoElement.appendChild(table); + + var heading = document.createElement("th"); + heading.textContent = section.title; + + var row = table.createTHead().insertRow(-1).appendChild(heading); + var body = document.createElement("tbody"); + table.appendChild(body); + + section.info.forEach(function(header) { + var row = body.insertRow(-1); + var th = document.createElement("th"); + th.textContent = header.header; + row.appendChild(th); + row.insertCell(-1).textContent = header.value; + }); + } + + sections.forEach(createSectionTable, this); + }, + + refreshInfoIfNeeded: function() + { + if (this.infoNeedsRefresh === false) + return; + + this.refreshInfo(); + }, + + toggleShowingInfo: function() + { + this.showingInfo = !this.showingInfo; + }, + + get showingInfo() + { + return this._showingInfo; + }, + + set showingInfo(x) + { + if (this._showingInfo === x) + return; + + this._showingInfo = x; + + var element = this.infoElement; + if (x) { + element.removeStyleClass("hidden"); + element.style.setProperty("overflow", "hidden"); + this.refreshInfoIfNeeded(); + WebInspector.animateStyle([{element: element, start: {height: 0}, end: {height: element.offsetHeight}}], 250, function() { element.style.removeProperty("height"); element.style.removeProperty("overflow") }); + } else { + element.style.setProperty("overflow", "hidden"); + WebInspector.animateStyle([{element: element, end: {height: 0}}], 250, function() { element.addStyleClass("hidden"); element.style.removeProperty("height") }); + } + }, + + get showingTipButton() + { + return !this.tipButtonElement.hasStyleClass("hidden"); + }, + + set showingTipButton(x) + { + if (x) + this.tipButtonElement.removeStyleClass("hidden"); + else + this.tipButtonElement.addStyleClass("hidden"); + }, + + toggleTipBalloon: function(event) + { + this.showingTipBalloon = !this.showingTipBalloon; + event.stopPropagation(); + }, + + get showingTipBalloon() + { + return this._showingTipBalloon; + }, + + set showingTipBalloon(x) + { + if (this._showingTipBalloon === x) + return; + + this._showingTipBalloon = x; + + if (x) { + if (!this.tipBalloonElement) { + this.tipBalloonElement = document.createElement("div"); + this.tipBalloonElement.className = "tip-balloon"; + this.titleElement.appendChild(this.tipBalloonElement); + + this.tipBalloonContentElement = document.createElement("div"); + this.tipBalloonContentElement.className = "tip-balloon-content"; + this.tipBalloonElement.appendChild(this.tipBalloonContentElement); + var tipText = ""; + for (var id in this.resource.tips) + tipText += this.resource.tips[id].message + "\n"; + this.tipBalloonContentElement.textContent = tipText; + } + + this.tipBalloonElement.removeStyleClass("hidden"); + WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 160, opacity: 0}, end: {left: 145, opacity: 1}}], 250); + } else { + var element = this.tipBalloonElement; + WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 145, opacity: 1}, end: {left: 160, opacity: 0}}], 250, function() { element.addStyleClass("hidden") }); + } + } +} + +WebInspector.TimelineValueCalculator = function() +{ +} + +WebInspector.TimelineValueCalculator.prototype = { + computeValues: function(entries) + { + var total = 0; + var categoryValues = {}; + + function compute(entry) + { + var value = this._value(entry); + if (value === undefined) + return; + + if (!(entry.resource.category.name in categoryValues)) + categoryValues[entry.resource.category.name] = 0; + categoryValues[entry.resource.category.name] += value; + total += value; + } + entries.forEach(compute, this); + + return {categoryValues: categoryValues, total: total}; + }, + + _value: function(entry) + { + return 0; + }, + + get title() + { + return ""; + }, + + formatValue: function(value) + { + return value.toString(); + } +} + +WebInspector.TransferTimeCalculator = function() +{ + WebInspector.TimelineValueCalculator.call(this); +} + +WebInspector.TransferTimeCalculator.prototype = { + computeValues: function(entries) + { + var entriesByCategory = {}; + entries.forEach(function(entry) { + if (!(entry.resource.category.name in entriesByCategory)) + entriesByCategory[entry.resource.category.name] = []; + entriesByCategory[entry.resource.category.name].push(entry); + }); + + var earliestStart; + var latestEnd; + var categoryValues = {}; + for (var category in entriesByCategory) { + entriesByCategory[category].sort(WebInspector.NetworkPanel.timelineEntryCompare); + categoryValues[category] = 0; + + var segment = {start: -1, end: -1}; + entriesByCategory[category].forEach(function(entry) { + if (entry.resource.startTime == -1 || entry.resource.endTime == -1) + return; + + if (earliestStart === undefined) + earliestStart = entry.resource.startTime; + else + earliestStart = Math.min(earliestStart, entry.resource.startTime); + + if (latestEnd === undefined) + latestEnd = entry.resource.endTime; + else + latestEnd = Math.max(latestEnd, entry.resource.endTime); + + if (entry.resource.startTime <= segment.end) { + segment.end = Math.max(segment.end, entry.resource.endTime); + return; + } + + categoryValues[category] += segment.end - segment.start; + + segment.start = entry.resource.startTime; + segment.end = entry.resource.endTime; + }); + + // Add the last segment + categoryValues[category] += segment.end - segment.start; + } + + return {categoryValues: categoryValues, total: latestEnd - earliestStart}; + }, + + get title() + { + return WebInspector.UIString("Transfer Time"); + }, + + get totalTitle() + { + return WebInspector.UIString("Total Time"); + }, + + formatValue: function(value) + { + return Number.secondsToString(value); + } +} + +WebInspector.TransferTimeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype; + +WebInspector.TransferSizeCalculator = function() +{ + WebInspector.TimelineValueCalculator.call(this); +} + +WebInspector.TransferSizeCalculator.prototype = { + _value: function(entry) + { + return entry.resource.contentLength; + }, + + get title() + { + return WebInspector.UIString("Transfer Size"); + }, + + get totalTitle() + { + return WebInspector.UIString("Total Size"); + }, + + formatValue: function(value) + { + return Number.bytesToString(value); + } +} + +WebInspector.TransferSizeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype; diff --git a/WebCore/page/inspector/Panel.js b/WebCore/page/inspector/Panel.js new file mode 100644 index 0000000..7631fa3 --- /dev/null +++ b/WebCore/page/inspector/Panel.js @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Panel = function(views) +{ + this._visible = false; + + this.element = document.createElement("div"); + this.element.className = "panel"; + + this.views = {}; + this.viewButtons = []; + + if (views) { + var selectViewFunction = function(event) + { + var clickedView = event.currentTarget.view; + clickedView.panel.currentView = clickedView; + }; + + for (var i = 0; i < views.length; ++i) { + var view = views[i]; + view.panel = this; + + view.buttonElement = document.createElement("button"); + view.buttonElement.title = view.title; + view.buttonElement.addEventListener("click", selectViewFunction, false); + view.buttonElement.appendChild(document.createElement("img")); + view.buttonElement.view = view; + + view.contentElement = document.createElement("div"); + view.contentElement.className = "content " + view.name; + + this.views[view.name] = view; + this.viewButtons.push(view.buttonElement); + this.element.appendChild(view.contentElement); + } + } +} + +WebInspector.Panel.prototype = { + show: function() + { + this._visible = true; + if (!this.element.parentNode) + this.attach(); + this.element.addStyleClass("selected"); + this.updateToolbar(); + if (this.currentView && this.currentView.show) + this.currentView.show(); + }, + + hide: function() + { + if (this.currentView && this.currentView.hide) + this.currentView.hide(); + document.getElementById("toolbarButtons").removeChildren(); + this.element.removeStyleClass("selected"); + this._visible = false; + }, + + updateToolbar: function() + { + var buttonContainer = document.getElementById("toolbarButtons"); + buttonContainer.removeChildren(); + + var buttons = this.viewButtons; + if (buttons.length < 2) + return; + + for (var i = 0; i < buttons.length; ++i) { + var button = buttons[i]; + + if (i === 0) + button.addStyleClass("first"); + else if (i === (buttons.length - 1)) + button.addStyleClass("last"); + + if (i) { + var divider = document.createElement("img"); + divider.className = "split-button-divider"; + buttonContainer.appendChild(divider); + } + + button.addStyleClass("split-button"); + button.addStyleClass("view-button-" + button.title.toLowerCase()); + + buttonContainer.appendChild(button); + } + }, + + attach: function() + { + document.getElementById("panels").appendChild(this.element); + }, + + detach: function() + { + if (WebInspector.currentPanel === this) + WebInspector.currentPanel = null; + if (this.element && this.element.parentNode) + this.element.parentNode.removeChild(this.element); + }, + + get currentView() + { + return this._currentView; + }, + + set currentView(x) + { + if (typeof x === "string" || x instanceof String) + x = this.views[x]; + + if (this._currentView === x) + return; + + if (this !== x.panel) { + console.error("Set currentView to a view " + x.title + " whose panel is not this panel"); + return; + } + + if (this._currentView) { + this._currentView.buttonElement.removeStyleClass("selected"); + this._currentView.contentElement.removeStyleClass("selected"); + if (this._currentView.hide) + this._currentView.hide(); + } + + this._currentView = x; + + if (x) { + x.buttonElement.addStyleClass("selected"); + x.contentElement.addStyleClass("selected"); + if (x.show) + x.show(); + } + }, + + get visible() + { + return this._visible; + }, + + set visible(x) + { + if (this._visible === x) + return; + + if (x) + this.show(); + else + this.hide(); + } +} diff --git a/WebCore/page/inspector/PropertiesSection.js b/WebCore/page/inspector/PropertiesSection.js new file mode 100644 index 0000000..b2a3473 --- /dev/null +++ b/WebCore/page/inspector/PropertiesSection.js @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.PropertiesSection = function(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "section"; + + this.headerElement = document.createElement("div"); + this.headerElement.className = "header"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.headerElement.appendChild(this.titleElement); + this.headerElement.appendChild(this.subtitleElement); + this.headerElement.addEventListener("click", this.toggleExpanded.bind(this), false); + + this.propertiesElement = document.createElement("ol"); + this.propertiesElement.className = "properties"; + this.propertiesTreeOutline = new TreeOutline(this.propertiesElement); + this.propertiesTreeOutline.section = this; + + this.element.appendChild(this.headerElement); + this.element.appendChild(this.propertiesElement); + + this.title = title; + this.subtitle = subtitle; + this.expanded = false; +} + +WebInspector.PropertiesSection.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.innerHTML = x; + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + get populated() + { + return this._populated; + }, + + set populated(x) + { + this._populated = x; + if (!x && this.onpopulate && this._expanded) { + this.onpopulate(this); + this._populated = true; + } + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + + if (!this._populated && this.onpopulate) { + this.onpopulate(this); + this._populated = true; + } + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + } +} diff --git a/WebCore/page/inspector/PropertiesSidebarPane.js b/WebCore/page/inspector/PropertiesSidebarPane.js new file mode 100644 index 0000000..0266d53 --- /dev/null +++ b/WebCore/page/inspector/PropertiesSidebarPane.js @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.PropertiesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties")); +} + +WebInspector.PropertiesSidebarPane.prototype = { + update: function(object) + { + var body = this.bodyElement; + + body.removeChildren(); + + this.sections = []; + + if (!object) + return; + + for (var prototype = object; prototype; prototype = prototype.__proto__) { + var section = new WebInspector.ObjectPropertiesSection(prototype); + this.sections.push(section); + body.appendChild(section.element); + } + } +} + +WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.ObjectPropertiesSection = function(object) +{ + var title = Object.describe(object); + var subtitle; + if (title.match(/Prototype$/)) { + title = title.replace(/Prototype$/, ""); + subtitle = WebInspector.UIString("Prototype"); + } + + this.object = object; + + WebInspector.PropertiesSection.call(this, title, subtitle); +} + +WebInspector.ObjectPropertiesSection.prototype = { + onpopulate: function() + { + var properties = Object.sortedProperties(this.object); + for (var i = 0; i < properties.length; ++i) { + var propertyName = properties[i]; + if (!this.object.hasOwnProperty(propertyName) || propertyName === "__treeElementIdentifier") + continue; + this.propertiesTreeOutline.appendChild(new WebInspector.ObjectPropertyTreeElement(this.object, propertyName)); + } + } +} + +WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.ObjectPropertyTreeElement = function(parentObject, propertyName) +{ + this.parentObject = parentObject; + this.propertyName = propertyName; + + var childObject = this.safePropertyValue(parentObject, propertyName); + var isGetter = parentObject.__lookupGetter__(propertyName); + + var title = "<span class=\"name\">" + propertyName.escapeHTML() + "</span>: "; + if (!isGetter) + title += "<span class=\"value\">" + Object.describe(childObject, true).escapeHTML() + "</span>"; + else + // FIXME: this should show something like "getter" once we can change localization (bug 16734). + title += "<span class=\"value dimmed\">—</span>"; + + var hasSubProperties = false; + var type = typeof childObject; + if (childObject && (type === "object" || type === "function")) { + for (subPropertyName in childObject) { + if (subPropertyName === "__treeElementIdentifier") + continue; + hasSubProperties = true; + break; + } + } + + TreeElement.call(this, title, null, hasSubProperties); +} + +WebInspector.ObjectPropertyTreeElement.prototype = { + safePropertyValue: function(object, propertyName) + { + var getter = object.__lookupGetter__(propertyName); + if (getter) + return; + return object[propertyName]; + }, + + onpopulate: function() + { + if (this.children.length) + return; + + var childObject = this.safePropertyValue(this.parentObject, this.propertyName); + var properties = Object.sortedProperties(childObject); + for (var i = 0; i < properties.length; ++i) { + var propertyName = properties[i]; + if (propertyName === "__treeElementIdentifier") + continue; + this.appendChild(new WebInspector.ObjectPropertyTreeElement(childObject, propertyName)); + } + } +} + +WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/page/inspector/Resource.js b/WebCore/page/inspector/Resource.js new file mode 100644 index 0000000..ed35970 --- /dev/null +++ b/WebCore/page/inspector/Resource.js @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Resource = function(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached) +{ + this.identifier = identifier; + + this.startTime = -1; + this.endTime = -1; + this.mainResource = mainResource; + this.requestHeaders = requestHeaders; + this.url = url; + this.domain = domain; + this.path = path; + this.lastPathComponent = lastPathComponent; + this.cached = cached; + + this.listItem = new WebInspector.ResourceTreeElement(this); + this.updateTitle(); + + this.category = WebInspector.resourceCategories.other; +} + +// Keep these in sync with WebCore::InspectorResource::Type +WebInspector.Resource.Type = { + Document: 0, + Stylesheet: 1, + Image: 2, + Font: 3, + Script: 4, + Other: 5, + + isTextType: function(type) + { + return (type == this.Document) || (type == this.Stylesheet) || (type == this.Script); + }, + + toString: function(type) + { + switch (type) { + case this.Document: + return WebInspector.UIString("document"); + case this.Stylesheet: + return WebInspector.UIString("stylesheet"); + case this.Image: + return WebInspector.UIString("image"); + case this.Font: + return WebInspector.UIString("font"); + case this.Script: + return WebInspector.UIString("script"); + case this.Other: + default: + return WebInspector.UIString("other"); + } + } +} + +WebInspector.Resource.prototype = { + get url() + { + return this._url; + }, + + set url(x) + { + if (this._url === x) + return; + + var oldURL = this._url; + this._url = x; + WebInspector.resourceURLChanged(this, oldURL); + this.updateTitleSoon(); + }, + + get domain() + { + return this._domain; + }, + + set domain(x) + { + if (this._domain === x) + return; + this._domain = x; + this.updateTitleSoon(); + }, + + get lastPathComponent() + { + return this._lastPathComponent; + }, + + set lastPathComponent(x) + { + if (this._lastPathComponent === x) + return; + this._lastPathComponent = x; + this._lastPathComponentLowerCase = x ? x.toLowerCase() : null; + this.updateTitleSoon(); + }, + + get displayName() + { + var title = this.lastPathComponent; + if (!title) + title = this.domain; + if (!title) + title = this.url; + return title; + }, + + get startTime() + { + return this._startTime; + }, + + set startTime(x) + { + if (this._startTime === x) + return; + + this._startTime = x; + + if (this.networkTimelineEntry) + this.networkTimelineEntry.refresh(); + }, + + get responseReceivedTime() + { + return this._responseReceivedTime; + }, + + set responseReceivedTime(x) + { + if (this._responseReceivedTime === x) + return; + + this._responseReceivedTime = x; + + if (this.networkTimelineEntry) + this.networkTimelineEntry.refresh(); + }, + + get endTime() + { + return this._endTime; + }, + + set endTime(x) + { + if (this._endTime === x) + return; + + this._endTime = x; + + if (this.networkTimelineEntry) + this.networkTimelineEntry.refresh(); + }, + + get contentLength() + { + return this._contentLength; + }, + + set contentLength(x) + { + if (this._contentLength === x) + return; + + this._contentLength = x; + + if (this._expectedContentLength && this._expectedContentLength > x) { + this.updateTitle(); + var canvas = document.getElementById("loadingIcon" + this.identifier); + if (canvas) + WebInspector.drawLoadingPieChart(canvas, (x / this._expectedContentLength)); + } + + WebInspector.networkPanel.updateSummaryGraphSoon(); + }, + + get expectedContentLength() + { + return this._expectedContentLength; + }, + + set expectedContentLength(x) + { + if (this._expectedContentLength === x) + return; + + this._expectedContentLength = x; + + if (x && this._contentLength && this._contentLength <= x) { + var canvas = document.getElementById("loadingIcon" + this.identifier); + if (canvas) + WebInspector.drawLoadingPieChart(canvas, (this._contentLength / x)); + } + }, + + get finished() + { + return this._finished; + }, + + set finished(x) + { + if (this._finished === x) + return; + + this._finished = x; + + if (x) { + var canvas = document.getElementById("loadingIcon" + this.identifier); + if (canvas) + canvas.parentNode.removeChild(canvas); + + this._checkTips(); + this._checkWarnings(); + } + + this.updateTitleSoon(); + this.updatePanel(); + }, + + get failed() + { + return this._failed; + }, + + set failed(x) + { + this._failed = x; + + this.updateTitleSoon(); + this.updatePanel(); + }, + + get category() + { + return this._category; + }, + + set category(x) + { + if (this._category === x) + return; + + var oldCategory = this._category; + if (oldCategory) + oldCategory.removeResource(this); + + this._category = x; + this.updateTitle(); + + if (this._category) + this._category.addResource(this); + + this.updatePanel(); + }, + + get mimeType() + { + return this._mimeType; + }, + + set mimeType(x) + { + if (this._mimeType === x) + return; + + this._mimeType = x; + }, + + get type() + { + return this._type; + }, + + set type(x) + { + if (this._type === x) + return; + + this._type = x; + + switch (x) { + case WebInspector.Resource.Type.Document: + this.category = WebInspector.resourceCategories.documents; + break; + case WebInspector.Resource.Type.Stylesheet: + this.category = WebInspector.resourceCategories.stylesheets; + break; + case WebInspector.Resource.Type.Script: + this.category = WebInspector.resourceCategories.scripts; + break; + case WebInspector.Resource.Type.Image: + this.category = WebInspector.resourceCategories.images; + break; + case WebInspector.Resource.Type.Font: + this.category = WebInspector.resourceCategories.fonts; + break; + case WebInspector.Resource.Type.Other: + default: + this.category = WebInspector.resourceCategories.other; + break; + } + }, + + get documentNode() { + if ("identifier" in this) + return InspectorController.getResourceDocumentNode(this.identifier); + return null; + }, + + get requestHeaders() + { + if (this._requestHeaders === undefined) + this._requestHeaders = {}; + return this._requestHeaders; + }, + + set requestHeaders(x) + { + if (this._requestHeaders === x) + return; + + this._requestHeaders = x; + delete this._sortedRequestHeaders; + + if (this.networkTimelineEntry) + this.networkTimelineEntry.refreshInfo(); + }, + + get sortedRequestHeaders() + { + if (this._sortedRequestHeaders !== undefined) + return this._sortedRequestHeaders; + + this._sortedRequestHeaders = []; + for (var key in this.requestHeaders) + this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]}); + this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedRequestHeaders; + }, + + get responseHeaders() + { + if (this._responseHeaders === undefined) + this._responseHeaders = {}; + return this._responseHeaders; + }, + + set responseHeaders(x) + { + if (this._responseHeaders === x) + return; + + this._responseHeaders = x; + delete this._sortedResponseHeaders; + + if (this.networkTimelineEntry) + this.networkTimelineEntry.refreshInfo(); + }, + + get sortedResponseHeaders() + { + if (this._sortedResponseHeaders !== undefined) + return this._sortedResponseHeaders; + + this._sortedResponseHeaders = []; + for (var key in this.responseHeaders) + this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]}); + this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedResponseHeaders; + }, + + get tips() + { + if (!("_tips" in this)) + this._tips = {}; + return this._tips; + }, + + _addTip: function(tip) + { + if (tip.id in this.tips) + return; + + this.tips[tip.id] = tip; + + // FIXME: Re-enable this code once we have a scope bar in the Console. + // Otherwise, we flood the Console with too many tips. + /* + var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, + WebInspector.ConsoleMessage.MessageLevel.Tip, tip.message, -1, this.url); + WebInspector.consolePanel.addMessage(msg); + */ + + if (this.networkTimelineEntry) + this.networkTimelineEntry.showingTipButton = true; + }, + + _checkTips: function() + { + for (var tip in WebInspector.Tips) + this._checkTip(WebInspector.Tips[tip]); + }, + + _checkTip: function(tip) + { + var addTip = false; + switch (tip.id) { + case WebInspector.Tips.ResourceNotCompressed.id: + addTip = this._shouldCompress(); + break; + } + + if (addTip) + this._addTip(tip); + }, + + _shouldCompress: function() + { + return WebInspector.Resource.Type.isTextType(this.type) + && this.domain + && !("Content-Encoding" in this.responseHeaders) + && this.contentLength !== undefined + && this.contentLength >= 512; + }, + + _mimeTypeIsConsistentWithType: function() + { + if (this.type === undefined || this.type === WebInspector.Resource.Type.Other) + return true; + + if (this.mimeType in WebInspector.MIMETypes) + return this.type in WebInspector.MIMETypes[this.mimeType]; + + return true; + }, + + _checkWarnings: function() + { + for (var warning in WebInspector.Warnings) + this._checkWarning(WebInspector.Warnings[warning]); + }, + + _checkWarning: function(warning) + { + var addWarning = false; + var msg; + switch (warning.id) { + case WebInspector.Warnings.IncorrectMIMEType.id: + if (!this._mimeTypeIsConsistentWithType()) + msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, + WebInspector.ConsoleMessage.MessageLevel.Warning, + String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, + WebInspector.Resource.Type.toString(this.type), this.mimeType), + -1, this.url); + break; + } + + if (msg) + WebInspector.consolePanel.addMessage(msg); + }, + + updateTitleSoon: function() + { + if (this.updateTitleTimeout) + return; + this.updateTitleTimeout = setTimeout(this.updateTitle.bind(this), 0); + }, + + updateTitle: function() + { + delete this.updateTitleTimeout; + + var title = this.displayName; + + var info = ""; + if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain))) + info = this.domain; + + if (this.path && this.lastPathComponent) { + var lastPathComponentIndex = this.path.lastIndexOf("/" + this.lastPathComponent); + if (lastPathComponentIndex != -1) + info += this.path.substring(0, lastPathComponentIndex); + } + + var fullTitle = ""; + + if (this.errors) + fullTitle += "<span class=\"count errors\">" + (this.errors + this.warnings) + "</span>"; + else if (this.warnings) + fullTitle += "<span class=\"count warnings\">" + this.warnings + "</span>"; + + fullTitle += "<span class=\"title" + (info && info.length ? "" : " only") + "\">" + title.escapeHTML() + "</span>"; + if (info && info.length) + fullTitle += "<span class=\"info\">" + info.escapeHTML() + "</span>"; + + var iconClass = "icon"; + switch (this.category) { + default: + break; + case WebInspector.resourceCategories.images: + case WebInspector.resourceCategories.other: + iconClass = "icon plain"; + break; + case WebInspector.resourceCategories.fonts: + iconClass = "icon font"; + } + + if (!this.finished) + fullTitle += "<div class=\"" + iconClass + "\"><canvas id=\"loadingIcon" + this.identifier + "\" class=\"progress\" width=\"16\" height=\"16\"></canvas></div>"; + else if (this.category === WebInspector.resourceCategories.images) + fullTitle += "<div class=\"" + iconClass + "\"><img class=\"preview\" src=\"" + this.url + "\"></div>"; + else if (this.category === WebInspector.resourceCategories.fonts) { + var uniqueFontName = "WebInspectorFontPreview" + this.identifier; + + this.fontStyleElement = document.createElement("style"); + this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.url + "); }"; + document.getElementsByTagName("head").item(0).appendChild(this.fontStyleElement); + + fullTitle += "<div class=\"" + iconClass + "\"><div class=\"preview\" style=\"font-family: " + uniqueFontName + "\">Ag</div></div>"; + } else + fullTitle += "<div class=\"" + iconClass + "\"></div>"; + + this.listItem.title = fullTitle; + this.listItem.tooltip = this.url; + }, + + updatePanel: function() + { + if (this._panel) { + var current = (WebInspector.currentPanel === this._panel); + + this._panel.detach(); + delete this._panel; + + if (current) + WebInspector.currentPanel = this.panel; + } + }, + + get panel() + { + if (!this._panel) { + if (this.finished && !this.failed) { + switch (this.category) { + case WebInspector.resourceCategories.documents: + this._panel = new WebInspector.DocumentPanel(this); + break; + case WebInspector.resourceCategories.stylesheets: + case WebInspector.resourceCategories.scripts: + this._panel = new WebInspector.SourcePanel(this); + break; + case WebInspector.resourceCategories.images: + this._panel = new WebInspector.ImagePanel(this); + break; + case WebInspector.resourceCategories.fonts: + this._panel = new WebInspector.FontPanel(this); + break; + } + } + + if (!this._panel) + this._panel = new WebInspector.ResourcePanel(this); + } + + return this._panel; + }, + + select: function() + { + WebInspector.navigateToResource(this); + }, + + deselect: function() + { + this.listItem.deselect(true); + if (WebInspector.currentPanel === this._panel) + WebInspector.currentPanel = null; + }, + + attach: function() + { + if (this._panel) + this._panel.attach(); + }, + + detach: function() + { + if (this._panel) + this._panel.detach(); + if (this.fontStyleElement && this.fontStyleElement.parentNode) + this.fontStyleElement.parentNode.removeChild(this.fontStyleElement); + }, + + get errors() + { + if (!("_errors" in this)) + this._errors = 0; + + return this._errors; + }, + + set errors(x) + { + if (this._errors === x) + return; + + this._errors = x; + this.updateTitleSoon(); + }, + + get warnings() + { + if (!("_warnings" in this)) + this._warnings = 0; + + return this._warnings; + }, + + set warnings(x) + { + if (this._warnings === x) + return; + + this._warnings = x; + this.updateTitleSoon(); + } +} + +WebInspector.ResourceTreeElement = function(resource) +{ + TreeElement.call(this, "", resource, false); + this.resource = resource; +} + +WebInspector.ResourceTreeElement.prototype = { + onselect: function() + { + var selectedElement = WebInspector.fileOutline.selectedTreeElement; + if (selectedElement) + selectedElement.deselect(); + this.resource.select(); + }, + + ondeselect: function() + { + this.resource.deselect(); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + } +} + +WebInspector.ResourceTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/page/inspector/ResourceCategory.js b/WebCore/page/inspector/ResourceCategory.js new file mode 100644 index 0000000..a2f2ba4 --- /dev/null +++ b/WebCore/page/inspector/ResourceCategory.js @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ResourceCategory = function(title, name) +{ + this.name = name; + this.title = title; + this.resources = []; + this.listItem = new WebInspector.ResourceCategoryTreeElement(this); + this.listItem.hidden = true; + WebInspector.fileOutline.appendChild(this.listItem); +} + +WebInspector.ResourceCategory.prototype = { + toString: function() + { + return this.title; + }, + + addResource: function(resource) + { + var a = resource; + var resourcesLength = this.resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var b = this.resources[i]; + if (a._lastPathComponentLowerCase && b._lastPathComponentLowerCase) + if (a._lastPathComponentLowerCase < b._lastPathComponentLowerCase) + break; + else if (a.name && b.name) + if (a.name < b.name) + break; + } + + this.resources.splice(i, 0, resource); + this.listItem.insertChild(resource.listItem, i); + this.listItem.hidden = false; + + resource.attach(); + }, + + removeResource: function(resource) + { + resource.detach(); + + var resourcesLength = this.resources.length; + for (var i = 0; i < resourcesLength; ++i) { + if (this.resources[i] === resource) { + this.resources.splice(i, 1); + break; + } + } + + this.listItem.removeChild(resource.listItem); + + if (!this.resources.length) + this.listItem.hidden = true; + }, + + removeAllResources: function(resource) + { + var resourcesLength = this.resources.length; + for (var i = 0; i < resourcesLength; ++i) + this.resources[i].detach(); + this.resources = []; + this.listItem.removeChildren(); + this.listItem.hidden = true; + } +} + +WebInspector.ResourceCategoryTreeElement = function(category) +{ + TreeElement.call(this, category.title, category, true); +} + +WebInspector.ResourceCategoryTreeElement.prototype = { + selectable: false, + arrowToggleWidth: 20 +} + +WebInspector.ResourceCategoryTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/page/inspector/ResourcePanel.js b/WebCore/page/inspector/ResourcePanel.js new file mode 100644 index 0000000..b165c2b --- /dev/null +++ b/WebCore/page/inspector/ResourcePanel.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ResourcePanel = function(resource, views) +{ + WebInspector.Panel.call(this, views); + this.resource = resource; +} + +WebInspector.ResourcePanel.prototype = { + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this.resource.listItem.select(true); // passing true prevents a cycle + this.resource.listItem.reveal(); + }, + + hide: function() + { + this.resource.listItem.deselect(true); // passing true prevents a cycle + WebInspector.Panel.prototype.hide.call(this); + } +} + +WebInspector.ResourcePanel.prototype.__proto__ = WebInspector.Panel.prototype; diff --git a/WebCore/page/inspector/SidebarPane.js b/WebCore/page/inspector/SidebarPane.js new file mode 100644 index 0000000..53f9d6d --- /dev/null +++ b/WebCore/page/inspector/SidebarPane.js @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SidebarPane = function(title) +{ + this.element = document.createElement("div"); + this.element.className = "pane"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false); + + this.bodyElement = document.createElement("div"); + this.bodyElement.className = "body"; + + this.element.appendChild(this.titleElement); + this.element.appendChild(this.bodyElement); + + this.title = title; + this.growbarVisible = false; + this.expanded = false; +} + +WebInspector.SidebarPane.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + get growbarVisible() + { + return this._growbarVisible; + }, + + set growbarVisible(x) + { + if (this._growbarVisible === x) + return; + + this._growbarVisible = x; + + if (x && !this._growbarElement) { + this._growbarElement = document.createElement("div"); + this._growbarElement.className = "growbar"; + this.element.appendChild(this._growbarElement); + } else if (!x && this._growbarElement) { + if (this._growbarElement.parentNode) + this._growbarElement.parentNode(this._growbarElement); + delete this._growbarElement; + } + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + if (this.onexpand) + this.onexpand(this); + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + if (this.oncollapse) + this.oncollapse(this); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + } +} diff --git a/WebCore/page/inspector/SourcePanel.js b/WebCore/page/inspector/SourcePanel.js new file mode 100644 index 0000000..32d7899 --- /dev/null +++ b/WebCore/page/inspector/SourcePanel.js @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SourcePanel = function(resource, views) +{ + var allViews = [{ title: WebInspector.UIString("Source"), name: "source" }]; + if (views) + allViews = allViews.concat(views); + + WebInspector.ResourcePanel.call(this, resource, allViews); + + this.currentView = this.views.source; + + var sourceView = this.views.source; + + sourceView.messages = []; + sourceView.frameNeedsSetup = true; + + sourceView.frameElement = document.createElement("iframe"); + sourceView.frameElement.setAttribute("viewsource", "true"); + sourceView.contentElement.appendChild(sourceView.frameElement); +} + +WebInspector.SourcePanel.prototype = { + show: function() + { + WebInspector.ResourcePanel.prototype.show.call(this); + this.setupSourceFrameIfNeeded(); + }, + + setupSourceFrameIfNeeded: function() + { + if (this.views.source.frameNeedsSetup) { + this.attach(); + + InspectorController.addSourceToFrame(this.resource.identifier, this.views.source.frameElement); + WebInspector.addMainEventListeners(this.views.source.frameElement.contentDocument); + + var length = this.views.source.messages; + for (var i = 0; i < length; ++i) + this._addMessageToSource(this.views.source.messages[i]); + + delete this.views.source.frameNeedsSetup; + } + }, + + sourceRow: function(lineNumber) + { + this.setupSourceFrameIfNeeded(); + + var doc = this.views.source.frameElement.contentDocument; + var rows = doc.getElementsByTagName("table")[0].rows; + + // Line numbers are a 1-based index, but the rows collection is 0-based. + --lineNumber; + if (lineNumber >= rows.length) + lineNumber = rows.length - 1; + + return rows[lineNumber]; + }, + + showSourceLine: function(lineNumber) + { + var row = this.sourceRow(lineNumber); + if (!row) + return; + this.currentView = this.views.source; + row.scrollIntoViewIfNeeded(true); + }, + + addMessageToSource: function(msg) + { + this.views.source.messages.push(msg); + if (!this.views.source.frameNeedsSetup) + this._addMessageToSource(msg); + }, + + _addMessageToSource: function(msg) + { + var row = this.sourceRow(msg.line); + if (!row) + return; + + var doc = this.views.source.frameElement.contentDocument; + var cell = row.getElementsByTagName("td")[1]; + + var errorDiv = cell.lastChild; + if (!errorDiv || errorDiv.nodeName.toLowerCase() !== "div" || !errorDiv.hasStyleClass("webkit-html-message-bubble")) { + errorDiv = doc.createElement("div"); + errorDiv.className = "webkit-html-message-bubble"; + cell.appendChild(errorDiv); + } + + var imageURL; + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Error: + errorDiv.addStyleClass("webkit-html-error-message"); + imageURL = "Images/errorIcon.png"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + errorDiv.addStyleClass("webkit-html-warning-message"); + imageURL = "Images/warningIcon.png"; + break; + } + + var lineDiv = doc.createElement("div"); + lineDiv.className = "webkit-html-message-line"; + errorDiv.appendChild(lineDiv); + + var image = doc.createElement("img"); + image.src = imageURL; + image.className = "webkit-html-message-icon"; + lineDiv.appendChild(image); + + lineDiv.appendChild(doc.createTextNode(msg.message)); + } +} + +WebInspector.SourcePanel.prototype.__proto__ = WebInspector.ResourcePanel.prototype; diff --git a/WebCore/page/inspector/StylesSidebarPane.js b/WebCore/page/inspector/StylesSidebarPane.js new file mode 100644 index 0000000..a701a35 --- /dev/null +++ b/WebCore/page/inspector/StylesSidebarPane.js @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StylesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); +} + +WebInspector.StylesSidebarPane.prototype = { + update: function(node, editedSection) + { + var refresh = false; + + if (!node || node === this.node) + refresh = true; + + if (node && node.nodeType === Node.TEXT_NODE && node.parentNode) + node = node.parentNode; + + if (node && node.nodeType !== Node.ELEMENT_NODE) + node = null; + + if (node) + this.node = node; + else + node = this.node; + + var body = this.bodyElement; + if (!refresh || !node) { + body.removeChildren(); + this.sections = []; + } + + if (!node) + return; + + var styleRules = []; + + if (refresh) { + for (var i = 0; i < this.sections.length; ++i) { + var section = this.sections[i]; + if (section.computedStyle) + section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle }; + styleRules.push(styleRule); + } + } else { + var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); + styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); + + var nodeName = node.nodeName.toLowerCase(); + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes[i]; + if (attr.style) { + var attrStyle = { style: attr.style, editable: false }; + attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name); + attrStyle.selectorText = nodeName + "[" + attr.name; + if (attr.value.length) + attrStyle.selectorText += "=" + attr.value; + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + } + + if (node.style && node.style.length) { + var inlineStyle = { selectorText: WebInspector.UIString("Inline Style Attribute"), style: node.style }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); + styleRules.push(inlineStyle); + } + + var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles); + if (matchedStyleRules) { + // Add rules in reverse order to match the cascade order. + for (var i = (matchedStyleRules.length - 1); i >= 0; --i) + styleRules.push(matchedStyleRules[i]); + } + } + + var usedProperties = {}; + var priorityUsed = false; + + // Walk the style rules and make a list of all used and overloaded properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle) + continue; + + styleRule.usedProperties = {}; + + var style = styleRule.style; + for (var j = 0; j < style.length; ++j) { + var name = style[j]; + + if (!priorityUsed && style.getPropertyPriority(name).length) + priorityUsed = true; + + // If the property name is already used by another rule then this rule's + // property is overloaded, so don't add it to the rule's usedProperties. + if (!(name in usedProperties)) + styleRule.usedProperties[name] = true; + + if (name === "font") { + // The font property is not reported as a shorthand. Report finding the individual + // properties so they are visible in computed style. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed. + styleRule.usedProperties["font-family"] = true; + styleRule.usedProperties["font-size"] = true; + styleRule.usedProperties["font-style"] = true; + styleRule.usedProperties["font-variant"] = true; + styleRule.usedProperties["font-weight"] = true; + styleRule.usedProperties["line-height"] = true; + } + } + + // Add all the properties found in this style to the used properties list. + // Do this here so only future rules are affect by properties used in this rule. + for (var name in styleRules[i].usedProperties) + usedProperties[name] = true; + } + + if (priorityUsed) { + // Walk the properties again and account for !important. + var foundPriorityProperties = []; + + // Walk in reverse to match the order !important overrides. + for (var i = (styleRules.length - 1); i >= 0; --i) { + if (styleRules[i].computedStyle) + continue; + + var style = styleRules[i].style; + var uniqueProperties = style.getUniqueProperties(); + for (var j = 0; j < uniqueProperties.length; ++j) { + var name = uniqueProperties[j]; + if (style.getPropertyPriority(name).length) { + if (!(name in foundPriorityProperties)) + styleRules[i].usedProperties[name] = true; + else + delete styleRules[i].usedProperties[name]; + foundPriorityProperties[name] = true; + } else if (name in foundPriorityProperties) + delete styleRules[i].usedProperties[name]; + } + } + } + + if (refresh) { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + section._usedProperties = (styleRule.usedProperties || usedProperties); + section.update((section === editedSection) || styleRule.computedStyle); + } + } else { + // Make a property section for each style rule. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var subtitle = styleRule.subtitle; + delete styleRule.subtitle; + + var computedStyle = styleRule.computedStyle; + delete styleRule.computedStyle; + + var ruleUsedProperties = styleRule.usedProperties; + delete styleRule.usedProperties; + + var editable = styleRule.editable; + delete styleRule.editable; + + // Default editable to true if it was omitted. + if (typeof editable === "undefined") + editable = true; + + var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); + section.expanded = true; + section.pane = this; + + body.appendChild(section.element); + this.sections.push(section); + } + } + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +{ + WebInspector.PropertiesSection.call(this, styleRule.selectorText); + + this.styleRule = styleRule; + this.computedStyle = computedStyle; + this.editable = (editable && !computedStyle); + + // Prevent editing the user agent rules. + if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode && !this.styleRule.parentStyleSheet.href) + this.editable = false; + + this._usedProperties = usedProperties; + + if (computedStyle) { + if (Preferences.showInheritedComputedStyleProperties) + this.element.addStyleClass("show-inherited"); + + var showInheritedLabel = document.createElement("label"); + var showInheritedInput = document.createElement("input"); + showInheritedInput.type = "checkbox"; + showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties; + + var computedStyleSection = this; + var showInheritedToggleFunction = function(event) { + Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked; + if (Preferences.showInheritedComputedStyleProperties) + computedStyleSection.element.addStyleClass("show-inherited"); + else + computedStyleSection.element.removeStyleClass("show-inherited"); + event.stopPropagation(); + }; + + showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); + + showInheritedLabel.appendChild(showInheritedInput); + showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited properties"))); + this.subtitleElement.appendChild(showInheritedLabel); + } else { + if (!subtitle) { + if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { + var url = this.styleRule.parentStyleSheet.href; + subtitle = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML()); + this.subtitleElement.addStyleClass("file"); + } else if (this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode) + subtitle = WebInspector.UIString("user agent stylesheet"); + else + subtitle = WebInspector.UIString("inline stylesheet"); + } + + this.subtitle = subtitle; + } +} + +WebInspector.StylePropertiesSection.prototype = { + get usedProperties() + { + return this._usedProperties || {}; + }, + + set usedProperties(x) + { + this._usedProperties = x; + this.update(); + }, + + isPropertyInherited: function(property) + { + if (!this.computedStyle || !this._usedProperties) + return false; + // These properties should always show for Computed Style. + var alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; + return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties); + }, + + isPropertyOverloaded: function(property, shorthand) + { + if (this.computedStyle || !this._usedProperties) + return false; + + var used = (property in this.usedProperties); + if (used || !shorthand) + return !used; + + // Find out if any of the individual longhand properties of the shorthand + // are used, if none are then the shorthand is overloaded too. + var longhandProperties = this.styleRule.style.getLonghandProperties(property); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty in this.usedProperties) + return false; + } + + return true; + }, + + update: function(full) + { + if (full || this.computedStyle) { + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + } else { + var child = this.propertiesTreeOutline.children[0]; + while (child) { + child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); + child = child.traverseNextTreeElement(false, null, true); + } + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + if (!style.length) + return; + + var foundShorthands = {}; + var uniqueProperties = style.getUniqueProperties(); + uniqueProperties.sort(); + + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = style.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + foundShorthands[shorthand] = true; + name = shorthand; + } + + var isShorthand = (shorthand ? true : false); + var inherited = this.isPropertyInherited(name); + var overloaded = this.isPropertyOverloaded(name, isShorthand); + + var item = new WebInspector.StylePropertyTreeElement(style, name, isShorthand, inherited, overloaded); + this.propertiesTreeOutline.appendChild(item); + } + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.StylePropertyTreeElement = function(style, name, shorthand, inherited, overloaded) +{ + this.style = style; + this.name = name; + this.shorthand = shorthand; + this._inherited = inherited; + this._overloaded = overloaded; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, shorthand); +} + +WebInspector.StylePropertyTreeElement.prototype = { + get inherited() + { + return this._inherited; + }, + + set inherited(x) + { + if (x === this._inherited) + return; + this._inherited = x; + this.updateState(); + }, + + get overloaded() + { + return this._overloaded; + }, + + set overloaded(x) + { + if (x === this._overloaded) + return; + this._overloaded = x; + this.updateState(); + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + // "Nicknames" for some common values that are easier to read. + var valueNicknames = { + "rgb(0, 0, 0)": "black", + "#000": "black", + "#000000": "black", + "rgb(255, 255, 255)": "white", + "#fff": "white", + "#ffffff": "white", + "#FFF": "white", + "#FFFFFF": "white", + "rgba(0, 0, 0, 0)": "transparent", + "rgb(255, 0, 0)": "red", + "rgb(0, 255, 0)": "lime", + "rgb(0, 0, 255)": "blue", + "rgb(255, 255, 0)": "yellow", + "rgb(255, 0, 255)": "magenta", + "rgb(0, 255, 255)": "cyan" + }; + + var priority = (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name)); + var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); + var htmlValue = value; + + if (priority && !priority.length) + delete priority; + if (priority) + priority = "!" + priority; + + if (value) { + var urls = value.match(/url\([^)]+\)/); + if (urls) { + for (var i = 0; i < urls.length; ++i) { + var url = urls[i].substring(4, urls[i].length - 1); + htmlValue = htmlValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")"); + } + } else { + if (value in valueNicknames) + htmlValue = valueNicknames[value]; + htmlValue = htmlValue.escapeHTML(); + } + } else + htmlValue = value = ""; + + this.updateState(); + + var nameElement = document.createElement("span"); + nameElement.className = "name"; + nameElement.textContent = this.name; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + valueElement.innerHTML = htmlValue; + + if (priority) { + var priorityElement = document.createElement("span"); + priorityElement.className = "priority"; + priorityElement.textContent = priority; + } + + this.listItemElement.removeChildren(); + + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(valueElement); + + if (priorityElement) { + this.listItemElement.appendChild(document.createTextNode(" ")); + this.listItemElement.appendChild(priorityElement); + } + + this.listItemElement.appendChild(document.createTextNode(";")); + + if (value) { + // FIXME: this dosen't catch keyword based colors like black and white + var colors = value.match(/((rgb|hsl)a?\([^)]+\))|(#[0-9a-fA-F]{6})|(#[0-9a-fA-F]{3})/g); + if (colors) { + var colorsLength = colors.length; + for (var i = 0; i < colorsLength; ++i) { + var swatchElement = document.createElement("span"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", colors[i]); + this.listItemElement.appendChild(swatchElement); + } + } + } + + this.tooltip = this.name + ": " + (valueNicknames[value] || value) + (priority ? " " + priority : ""); + }, + + updateState: function() + { + if (!this.listItemElement) + return; + + var value = (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); + if (this.style.isPropertyImplicit(this.name) || value === "initial") + this.listItemElement.addStyleClass("implicit"); + else + this.listItemElement.removeStyleClass("implicit"); + + if (this.inherited) + this.listItemElement.addStyleClass("inherited"); + else + this.listItemElement.removeStyleClass("inherited"); + + if (this.overloaded) + this.listItemElement.addStyleClass("overloaded"); + else + this.listItemElement.removeStyleClass("overloaded"); + }, + + onpopulate: function() + { + // Only populate once and if this property is a shorthand. + if (this.children.length || !this.shorthand) + return; + + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + var name = longhandProperties[i]; + + if (this.treeOutline.section) { + var inherited = this.treeOutline.section.isPropertyInherited(name); + var overloaded = this.treeOutline.section.isPropertyOverloaded(name); + } + + var item = new WebInspector.StylePropertyTreeElement(this.style, name, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(event.target); + }, + + startEditing: function(selectElement) + { + // FIXME: we don't allow editing of longhand properties under a shorthand right now. + if (this.parent.shorthand) + return; + + if (this.editing || (this.treeOutline.section && !this.treeOutline.section.editable)) + return; + + this.editing = true; + this.previousTextContent = this.listItemElement.textContent; + + this.listItemElement.addStyleClass("focusable"); + this.listItemElement.addStyleClass("editing"); + this.wasExpanded = this.expanded; + this.collapse(); + // Lie about out children to prevent toggling on click. + this.hasChildren = false; + + if (!selectElement) + selectElement = this.listItemElement; + + window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); + + var treeElement = this; + this.listItemElement.blurred = function() { treeElement.commitEditing() }; + this.listItemElement.handleKeyEvent = function(event) { + if (event.keyIdentifier === "Enter") { + treeElement.commitEditing(); + event.preventDefault(); + } else if (event.keyCode === 27) { // Escape key + treeElement.cancelEditing(); + event.preventDefault(); + } + }; + + this.previousFocusElement = WebInspector.currentFocusElement; + WebInspector.currentFocusElement = this.listItemElement; + }, + + endEditing: function() + { + // Revert the changes done in startEditing(). + delete this.listItemElement.blurred; + delete this.listItemElement.handleKeyEvent; + + WebInspector.currentFocusElement = this.previousFocusElement; + delete this.previousFocusElement; + + delete this.previousTextContent; + delete this.editing; + + this.listItemElement.removeStyleClass("focusable"); + this.listItemElement.removeStyleClass("editing"); + this.hasChildren = (this.children.length ? true : false); + if (this.wasExpanded) { + delete this.wasExpanded; + this.expand(); + } + }, + + cancelEditing: function() + { + this.endEditing(); + this.updateTitle(); + }, + + commitEditing: function() + { + var previousContent = this.previousTextContent; + + this.endEditing(); + + var userInput = this.listItemElement.textContent; + if (userInput === previousContent) + return; // nothing changed, so do nothing else + + var userInputLength = userInput.trimWhitespace().length; + + // Create a new element to parse the user input CSS. + var parseElement = document.createElement("span"); + parseElement.setAttribute("style", userInput); + + var userInputStyle = parseElement.style; + if (userInputStyle.length || !userInputLength) { + // The input was parsable or the user deleted everything, so remove the + // original property from the real style declaration. If this represents + // a shorthand remove all the longhand properties. + if (this.shorthand) { + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) + this.style.removeProperty(longhandProperties[i]); + } else + this.style.removeProperty(this.name); + } + + if (!userInputLength) { + // The user deleted the everything, so remove the tree element and update. + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(); + this.parent.removeChild(this); + return; + } + + if (!userInputStyle.length) { + // The user typed something, but it didn't parse. Just abort and restore + // the original title for this property. + this.updateTitle(); + return; + } + + // Iterate of the properties on the test element's style declaration and + // add them to the real style declaration. We take care to move shorthands. + var foundShorthands = {}; + var uniqueProperties = userInputStyle.getUniqueProperties(); + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = userInputStyle.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + var value = userInputStyle.getShorthandValue(shorthand); + var priority = userInputStyle.getShorthandPriority(shorthand); + foundShorthands[shorthand] = true; + } else { + var value = userInputStyle.getPropertyValue(name); + var priority = userInputStyle.getPropertyPriority(name); + } + + // Set the property on the real style declaration. + this.style.setProperty((shorthand || name), value, priority); + } + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(null, this.treeOutline.section); + else if (this.treeOutline.section) + this.treeOutline.section.update(true); + else + this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet. + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/page/inspector/WebKit.qrc b/WebCore/page/inspector/WebKit.qrc new file mode 100644 index 0000000..0a5e164 --- /dev/null +++ b/WebCore/page/inspector/WebKit.qrc @@ -0,0 +1,137 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/webkit/inspector"> + <file>ConsolePanel.js</file> + <file>NetworkPanel.js</file> + <file>Resource.js</file> + <file>ResourceCategory.js</file> + <file>ResourcePanel.js</file> + <file>inspector.css</file> + <file>inspector.html</file> + <file>inspector.js</file> + <file>treeoutline.js</file> + <file>utilities.js</file> + <file>Images/alternateTableRows.png</file> + <file>Images/attachedShadow.png</file> + <file>Images/backNormal.png</file> + <file>Images/bottomShadow.png</file> + <file>Images/breadcrumbBackground.png</file> + <file>Images/checker.png</file> + <file>Images/console.png</file> + <file>Images/darkShadow.png</file> + <file>Images/database.png</file> + <file>Images/databaseBrowserViewNormal.png</file> + <file>Images/databaseBrowserViewNormalSelected.png</file> + <file>Images/databaseBrowserViewSmall.png</file> + <file>Images/databaseBrowserViewSmallSelected.png</file> + <file>Images/databaseQueryViewNormal.png</file> + <file>Images/databaseQueryViewNormalSelected.png</file> + <file>Images/databaseQueryViewSmall.png</file> + <file>Images/databaseQueryViewSmallSelected.png</file> + <file>Images/disclosureDownPressed.png</file> + <file>Images/disclosureRightDown.png</file> + <file>Images/disclosureRightPressed.png</file> + <file>Images/document.png</file> + <file>Images/domViewNormal.png</file> + <file>Images/domViewNormalSelected.png</file> + <file>Images/domViewSmall.png</file> + <file>Images/domViewSmallSelected.png</file> + <file>Images/downTriangle.png</file> + <file>Images/errorIcon.png</file> + <file>Images/errorMediumIcon.png</file> + <file>Images/folder.png</file> + <file>Images/forwardNormal.png</file> + <file>Images/glossyHeader.png</file> + <file>Images/glossyHeaderPressed.png</file> + <file>Images/goArrow.png</file> + <file>Images/gradient.png</file> + <file>Images/gradientHighlight.png</file> + <file>Images/gradientHighlightBottom.png</file> + <file>Images/hideStatusWidget.png</file> + <file>Images/hideStatusWidgetPressed.png</file> + <file>Images/network.png</file> + <file>Images/paneBottomGrow.png</file> + <file>Images/paneBottomGrowActive.png</file> + <file>Images/paneGrowHandleLine.png</file> + <file>Images/paneHeader.png</file> + <file>Images/paneHeaderActive.png</file> + <file>Images/plainDocument.png</file> + <file>Images/popupArrows.png</file> + <file>Images/popupArrowsBlack.png</file> + <file>Images/reload.png</file> + <file>Images/rightTriangle.png</file> + <file>Images/segment.png</file> + <file>Images/segmentEnd.png</file> + <file>Images/segmentHover.png</file> + <file>Images/segmentHoverEnd.png</file> + <file>Images/segmentSelected.png</file> + <file>Images/segmentSelectedEnd.png</file> + <file>Images/showStatusWidget.png</file> + <file>Images/showStatusWidgetPressed.png</file> + <file>Images/sidbarItemBackground.png</file> + <file>Images/sidebarActionWidget.png</file> + <file>Images/sidebarActionWidgetPressed.png</file> + <file>Images/sidebarAttachWidget.png</file> + <file>Images/sidebarAttachWidgetPressed.png</file> + <file>Images/sidebarDetachWidget.png</file> + <file>Images/sidebarDetachWidgetPressed.png</file> + <file>Images/sidebarResizeWidget.png</file> + <file>Images/sidebarSelection.png</file> + <file>Images/sidebarSelectionBlurred.png</file> + <file>Images/sidebarSelectionBlurredTall.png</file> + <file>Images/sidebarSelectionGray.png</file> + <file>Images/sidebarSelectionGrayTall.png</file> + <file>Images/sidebarSelectionTall.png</file> + <file>Images/sidebarStatusAreaBackground.png</file> + <file>Images/sourceViewNormal.png</file> + <file>Images/sourceViewNormalSelected.png</file> + <file>Images/sourceViewSmall.png</file> + <file>Images/sourceViewSmallSelected.png</file> + <file>Images/splitviewDimple.png</file> + <file>Images/splitviewDividerBackground.png</file> + <file>Images/tab.png</file> + <file>Images/tabSelected.png</file> + <file>Images/timelinePillBlue.png</file> + <file>Images/timelinePillGray.png</file> + <file>Images/timelinePillGreen.png</file> + <file>Images/timelinePillOrange.png</file> + <file>Images/timelinePillPurple.png</file> + <file>Images/timelinePillRed.png</file> + <file>Images/timelinePillYellow.png</file> + <file>Images/tipBalloon.png</file> + <file>Images/tipBalloonBottom.png</file> + <file>Images/tipIcon.png</file> + <file>Images/tipIconPressed.png</file> + <file>Images/toggleDown.png</file> + <file>Images/toggleUp.png</file> + <file>Images/toolbarBackground.png</file> + <file>Images/toolbarBackgroundInactive.png</file> + <file>Images/toolbarButtonNormal.png</file> + <file>Images/toolbarButtonNormalInactive.png</file> + <file>Images/toolbarButtonNormalPressed.png</file> + <file>Images/toolbarButtonNormalSelected.png</file> + <file>Images/toolbarButtonNormalSelectedInactive.png</file> + <file>Images/toolbarButtonSmall.png</file> + <file>Images/toolbarButtonSmallInactive.png</file> + <file>Images/toolbarButtonSmallPressed.png</file> + <file>Images/toolbarButtonSmallSelected.png</file> + <file>Images/toolbarButtonSmallSelectedInactive.png</file> + <file>Images/toolbarPopupButtonNormal.png</file> + <file>Images/toolbarPopupButtonNormalInactive.png</file> + <file>Images/toolbarPopupButtonNormalPressed.png</file> + <file>Images/toolbarPopupButtonSmall.png</file> + <file>Images/toolbarPopupButtonSmallInactive.png</file> + <file>Images/toolbarPopupButtonSmallPressed.png</file> + <file>Images/toolbarSplitButtonDividerNormal.png</file> + <file>Images/toolbarSplitButtonDividerNormalInactive.png</file> + <file>Images/toolbarSplitButtonDividerSmall.png</file> + <file>Images/toolbarSplitButtonDividerSmallInactive.png</file> + <file>Images/treeDownTriangleBlack.png</file> + <file>Images/treeDownTriangleWhite.png</file> + <file>Images/treeLeftTriangleBlack.png</file> + <file>Images/treeRightTriangleBlack.png</file> + <file>Images/treeRightTriangleWhite.png</file> + <file>Images/warningIcon.png</file> + <file>Images/warningMediumIcon.png</file> + <file>Images/warningsErrors.png</file> +</qresource> +</RCC> diff --git a/WebCore/page/inspector/inspector.css b/WebCore/page/inspector/inspector.css new file mode 100644 index 0000000..2ba3208 --- /dev/null +++ b/WebCore/page/inspector/inspector.css @@ -0,0 +1,2169 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +body { + -webkit-user-select: none; + cursor: default; + height: 100%; + width: 100%; + overflow: hidden; + font-family: Lucida Grande, sans-serif; + margin: 0; + -webkit-text-size-adjust: none; +} + +iframe, a img { + border: none; +} + +img { + -webkit-user-drag: none; +} + +.focused .selected { + background-color: rgb(56, 121, 217); +} + +.blurred .selected, body.inactive .selected { + background-color: rgb(212, 212, 212); +} + +.hidden { + display: none; +} + +#toolbar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 32px; + background-color: rgb(245, 245, 250); + background-image: url(Images/toolbarBackground.png); + background-repeat: repeat-x; + background-position: top; + border-bottom: 1px solid rgb(80, 80, 80); + padding: 2px 8px; + -webkit-box-sizing: border-box; + -webkit-background-size: auto 135%; +} + +body.detached.platform-mac-leopard #toolbar { + background: transparent !important; +} + +body.inactive #toolbar { + background-image: url(Images/toolbarBackgroundInactive.png); + border-bottom: 1px solid rgb(64%, 64%, 64%); +} + +body.attached #toolbar { + height: 28px; + border-top: 1px solid rgb(80, 80, 80); + background-image: url(Images/darkShadow.png), url(Images/toolbarBackground.png); + background-position: center -2px, top; + -webkit-background-size: auto auto, auto 135%; +} + +body.attached.inactive #toolbar { + background-image: url(Images/darkShadow.png), url(Images/toolbarBackgroundInactive.png); + background-position: center -3px, top; + border-top: 1px solid rgb(100, 100, 100); + border-bottom: 1px solid rgb(64%, 64%, 64%); +} + +#toolbar button, #toolbar button:disabled:active { + border-width: 3px 3px 4px 3px; + border-style: none; + border-color: transparent; + background-color: transparent; + -webkit-border-image: url(Images/toolbarButtonNormal.png) 3 3 4 3; + height: 23px; + -webkit-box-sizing: border-box; + vertical-align: middle; + line-height: 10px; +} + +#toolbar button:focus { + outline: none; +} + +#toolbar button:active { + -webkit-border-image: url(Images/toolbarButtonNormalPressed.png) 3 3 4 3; +} + +#toolbar button.selected { + -webkit-border-image: url(Images/toolbarButtonNormalSelected.png) 3 3 4 3; +} + +body.inactive #toolbar button:active { + -webkit-border-image: url(Images/toolbarButtonNormalPressedInactive.png) 3 3 4 3; +} + +body.inactive #toolbar button.selected { + -webkit-border-image: url(Images/toolbarButtonNormalSelectedInactive.png) 3 3 4 3; +} + +body.inactive #toolbar button, body.inactive #toolbar button:disabled:active { + -webkit-border-image: url(Images/toolbarButtonNormalInactive.png) 3 3 4 3; +} + +body.attached #toolbar button { + height: 19px; + line-height: 7px; + -webkit-border-image: url(Images/toolbarButtonSmall.png) 3 3 4 3; +} + +body.attached #toolbar button:active { + -webkit-border-image: url(Images/toolbarButtonSmallPressed.png) 3 3 4 3; +} + +body.attached #toolbar button.selected { + -webkit-border-image: url(Images/toolbarButtonSmallSelected.png) 3 3 4 3; +} + +body.attached.inactive #toolbar button:active { + -webkit-border-image: url(Images/toolbarButtonSmallPressedInactive.png) 3 3 4 3; +} + +body.attached.inactive #toolbar button.selected { + -webkit-border-image: url(Images/toolbarButtonSmallSelectedInactive.png) 3 3 4 3; +} + +body.attached.inactive #toolbar button, body.inactive #toolbar button:disabled:active { + -webkit-border-image: url(Images/toolbarButtonSmallInactive.png) 3 3 4 3; +} + +#toolbar select, #toolbar select:disabled:active { + background-color: transparent; + border-width: 3px 10px 4px 3px; + border-color: transparent; + -webkit-border-image: url(Images/toolbarPopupButtonNormal.png) 3 10 4 3; + height: 23px; + font-size: 10px; + padding-left: 8px; + padding-right: 6px; + -webkit-box-sizing: border-box; + -webkit-border-radius: 0; + -webkit-appearance: none; + vertical-align: middle; +} + +#toolbar select:focus { + outline: none; +} + +#toolbar select:active { + -webkit-border-image: url(Images/toolbarPopupButtonNormalPressed.png) 3 10 4 3; +} + +body.inactive #toolbar select:active { + -webkit-border-image: url(Images/toolbarPopupButtonNormalPressedInactive.png) 3 10 4 3; +} + +body.inactive #toolbar select, body.inactive #toolbar select:disabled:active { + -webkit-border-image: url(Images/toolbarPopupButtonNormalInactive.png) 3 10 4 3; +} + +body.attached #toolbar select, #toolbar select:disabled:active { + -webkit-border-image: url(Images/toolbarPopupButtonSmall.png) 3 10 4 3; + height: 19px; +} + +body.attached #toolbar select:active { + -webkit-border-image: url(Images/toolbarPopupButtonSmallPressed.png) 3 10 4 3; +} + +body.attached.inactive #toolbar select:active { + -webkit-border-image: url(Images/toolbarPopupButtonSmallPressedInactive.png) 3 10 4 3; +} + +body.attached.inactive #toolbar select, body.inactive #toolbar select:disabled:active { + -webkit-border-image: url(Images/toolbarPopupButtonSmallInactive.png) 3 10 4 3; +} + +#toolbar .split-button-divider { + width: 1px; + height: 23px; + content: url(Images/toolbarSplitButtonDividerNormal.png); + vertical-align: middle; +} + +body.inactive #toolbar .split-button-divider { + content: url(Images/toolbarSplitButtonDividerNormalInactive.png); +} + +body.attached #toolbar .split-button-divider { + height: 19px; + content: url(Images/toolbarSplitButtonDividerSmall.png); +} + +body.attached.inactive #toolbar .split-button-divider { + content: url(Images/toolbarSplitButtonDividerSmallInactive.png); +} + +#toolbar .split-button { + padding: 0; + width: 26px; +} + +body.attached #toolbar .split-button { + width: 20px; +} + +#toolbar .split-button.middle { + border-left: transparent none 0 !important; + border-right: transparent none 0 !important; +} + +#toolbar .split-button.first { + border-right: transparent none 0 !important; +} + +#toolbar .split-button.last { + border-left: transparent none 0 !important; +} + +#back img { + content: url(Images/backNormal.png); + vertical-align: middle; + margin-top: -1px; + width: 8px; + height: 10px; +} + +body.attached #back img { + content: url(Images/treeLeftTriangleBlack.png); + margin-top: -1px; + margin-left: -1px; + width: 8px; + height: 8px; +} + +#back:disabled img, #forward:disabled img { + opacity: 0.45; +} + +#forward img { + content: url(Images/forwardNormal.png); + vertical-align: middle; + margin-top: -1px; + margin-left: 1px; + width: 8px; + height: 10px; +} + +body.attached #forward img { + content: url(Images/treeRightTriangleBlack.png); + margin-top: -1px; + width: 8px; + height: 8px; +} + +.view-button-source img { + content: url(Images/sourceViewNormal.png); + vertical-align: middle; + margin-top: 1px; + margin-left: -1px; + width: 11px; + height: 11px; +} + +.view-button-source.selected img { + content: url(Images/sourceViewNormalSelected.png); +} + +body.attached .view-button-source img { + content: url(Images/sourceViewSmall.png); + width: 8px; + height: 8px; +} + +body.attached .view-button-source.selected img { + content: url(Images/sourceViewSmallSelected.png); +} + +.view-button-dom img { + content: url(Images/domViewNormal.png); + vertical-align: middle; + margin-top: 1px; + margin-left: 3px; + width: 11px; + height: 11px; +} + +.view-button-dom.selected img { + content: url(Images/domViewNormalSelected.png); +} + +body.attached .view-button-dom img { + content: url(Images/domViewSmall.png); + width: 10px; + height: 8px; +} + +body.attached .view-button-dom.selected img { + content: url(Images/domViewSmallSelected.png); +} + +#toolbarButtons { + position: absolute; + left: 200px; + padding-left: 8px; +} + +#search { + float: right; + width: 210px; + font-size: 16px; +} + +body.attached #search { + font-size: 12px; +} + +#searchResults { + position: absolute; + top: -100px; + left: 0; + right: 0; + height: 100px; + z-index: -1; + background-color: white; + border-bottom: 1px solid rgb(180, 180, 180); + overflow-y: auto; + overflow-x: hidden; + -webkit-box-sizing: border-box; +} + +.search-results-section { + color: gray; + width: 28px; + float: left; + margin-left: -45px; + text-align: right; + font-size: 10px; + margin-top: 1px; + white-space: nowrap; +} + +.selected .search-results-section { + color: rgba(255, 255, 255, 0.8); +} + +body.inactive .focused .selected .search-results-section { + color: rgba(0, 0, 0, 0.5); +} + +.blurred .selected .search-results-section { + color: rgba(0, 0, 0, 0.5); +} + +#searchResults > ol > ol > li { + padding-left: 45px; + white-space: nowrap; +} + +.search-matched-string { + background-color: #ff8; +} + +.selected .search-matched-string { + background-color: transparent; +} + +#sidebar { + position: absolute; + top: 32px; + left: 0; + bottom: 0; + width: 200px; + background-color: rgb(214, 221, 229); + border-right: 1px solid rgb(64%, 64%, 64%); + -webkit-box-sizing: border-box; +} + +body.inactive #sidebar { + background-color: rgb(232, 232, 232); +} + +body.attached #sidebar { + top: 28px; +} + +#statusbar { + position: absolute; + padding: 0; + left: 0; + right: 0; + bottom: 0; + height: 21px; + border-top: 1px solid #bbb; + -webkit-box-sizing: border-box; + background-image: url(Images/sidebarStatusAreaBackground.png); + background-position: right, center; + background-repeat: no-repeat, repeat-x; +} + +#statusbar #sidebarResizeWidget { + display: block; + float: right; + width: 17px; + height: 20px; + background: url(Images/sidebarResizeWidget.png) right no-repeat; + cursor: col-resize; +} + +#statusbar button { + -webkit-apearance: none; + vertical-align: top; + border: 0; + width: 32px; + height: 20px; + margin: 0; + margin-left: -1px; + padding: 0; +} + +#statusbar button:focus { + outline: none; +} + +#statusbar button.action { + background-image: url(Images/sidebarActionWidget.png); +} + +#statusbar button.action:active { + background-image: url(Images/sidebarActionWidgetPressed.png); +} + +body.detached #attachToggle { + background-image: url(Images/sidebarAttachWidget.png); +} + +body.detached #attachToggle:active { + background-image: url(Images/sidebarAttachWidgetPressed.png); +} + +body.attached #attachToggle { + background-image: url(Images/sidebarDetachWidget.png); +} + +body.attached #attachToggle:active { + background-image: url(Images/sidebarDetachWidgetPressed.png); +} + +#status { + overflow: hidden; + position: absolute; + bottom: 21px; + left: 0; + width: 100%; + height: 78px; + padding: 2px 0; + margin: 0; + border-top: 1px solid rgb(64%, 64%, 64%); + -webkit-box-sizing: border-box; + list-style: none; + font-size: 11px; + -webkit-transition: bottom 250ms ease-in-out; +} + +#status li { + position: relative; + height: 37px; + -webkit-box-sizing: border-box; +} + +#status li.selected { + background-image: url(Images/sidebarSelectionTall.png); + background-repeat: repeat-x; + background-position: center; + background-color: transparent !important; + color: white; + font-weight: bold; + text-shadow: rgba(0, 0, 0, 0.4) 0 1px 0; +} + +#status .icon { + position: absolute; + top: 2px; + left: 15px; + width: 32px; + height: 32px; + background-repeat: no-repeat; + background-position: center center; +} + +#status .icon.console { + background-image: url(Images/console.png); +} + +#status .icon.network { + background-image: url(Images/network.png); +} + +#status .title { + -webkit-box-sizing: border-box; + position: relative; + top: 5px; + padding-left: 58px; + right: 5px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#status .title.only { + top: 10px; +} + +#status .info { + -webkit-box-sizing: border-box; + position: relative; + margin-top: 6px; + padding-left: 58px; + right: 5px; + display: block; + font-size: 9px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#list { + overflow-x: hidden; + overflow-y: auto; + position: absolute; + top: 0; + left: 0; + bottom: 99px; + width: 100%; + padding: 2px 0; + margin: 0; + -webkit-box-sizing: border-box; + list-style: none; + font-size: 11px; + -webkit-transition: bottom 250ms ease-in-out; +} + +#list > li { + height: 26px; + color: rgb(96, 110, 128); + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; + font-weight: bold; + line-height: 20px; + text-indent: 20px; + background-image: url(Images/rightTriangle.png); + background-repeat: no-repeat; + background-position: 10px 6px; + text-transform: uppercase; +} + +#list > ol + li { + margin-top: 5px; +} + +#list > li + li { + margin-top: 5px; +} + +#list > li.expanded { + background-image: url(Images/downTriangle.png); + background-position: 10px 7px; +} + +#list > ol { + display: none; + list-style: none; + padding: 0; + margin: 0; +} + +#list > ol.expanded { + display: block; +} + +#list > ol > li { + position: relative; + height: 37px; + -webkit-box-sizing: border-box; +} + +#list > ol > li:-webkit-drag { + background: transparent !important; + color: black !important; + font-weight: normal !important; +} + +#sidebar li:-webkit-drag .count { + display: none; +} + +#list .icon { + position: absolute; + top: 2px; + left: 15px; + width: 32px; + height: 32px; + background-image: url(Images/document.png); + background-repeat: no-repeat; + background-position: center center; +} + +#list .icon.database { + background-image: url(Images/database.png); +} + +#list .icon.plain { + background-image: url(Images/plainDocument.png); +} + +#list .icon.font { + background-image: url(Images/plainDocument.png); +} + +#list .icon.font .preview { + overflow: hidden; + text-align: center; + font-size: 14px; + line-height: 14px; + font-weight: normal; + color: black; + text-shadow: none; +} + +#list .icon .preview { + margin: auto; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + max-width: 20px; + max-height: 22px; + -webkit-box-sizing: border-box; + border-top: 6px solid transparent; +} + +#list .icon .progress { + margin: auto; + display: block; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +#list .title { + -webkit-box-sizing: border-box; + position: relative; + top: 5px; + padding-left: 58px; + right: 5px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#list .title.only { + top: 10px; +} + +#sidebar li .count { + float: right; + margin-top: 11px; + margin-right: 6px; + font-family: Helvetica, sans-serif; + font-weight: bold; + font-size: 11px; + line-height: 10px; + -webkit-border-radius: 7px; + color: white; + text-shadow: none; + background-image: url(Images/gradientHighlight.png), url(Images/gradient.png); + -webkit-background-size: auto 100%, auto 100%; + background-position: center; + padding: 2px 4px; + text-align: center; + text-indent: 0; + min-width: 20px; + -webkit-box-sizing: border-box; +} + +#sidebar li .count.warnings { + background-color: orange; +} + +#sidebar li .count.errors { + background-color: red; +} + +#list .info { + -webkit-box-sizing: border-box; + position: relative; + margin-top: 6px; + padding-left: 58px; + right: 5px; + display: block; + font-size: 9px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#list li.selected { + background-image: url(Images/sidebarSelectionTall.png); + background-repeat: repeat-x; + background-position: center; + background-color: transparent !important; + color: white; + font-weight: bold; + text-shadow: rgba(0, 0, 0, 0.4) 0 1px 0; +} + +#sidebar.blurred li.selected { + background-image: url(Images/sidebarSelectionBlurredTall.png); +} + +body.inactive #sidebar li.selected { + background-image: url(Images/sidebarSelectionGrayTall.png); +} + +#main { + position: absolute; + top: 32px; + left: 200px; + right: 0; + bottom: 0; + overflow: hidden; + background-color: white; + z-index: -100; +} + +body.attached #main { + top: 28px; +} + +#panels { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: -100; +} + +.panel { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.panel.selected { + display: block; + background-color: transparent !important; +} + +.content { + display: none; + -webkit-user-select: text; + cursor: auto; + overflow: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.content.selected { + display: block; + background-color: transparent !important; +} + +.panel.font { + font-size: 60px; + white-space: pre-wrap; + word-wrap: break-word; + text-align: center; +} + +.panel.font .preview { + position: absolute; + margin-top: auto; + margin-bottom: auto; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.panel.image { + position: relative; + width: 100%; + height: 100%; +} + +.panel.image > .image { + position: relative; + -webkit-box-sizing: border-box; + height: 70%; + padding: 20px; +} + +.panel.image > .info { + position: relative; + -webkit-box-sizing: border-box; + height: 30%; + padding-top: 10px; + overflow: auto; + font-size: 11px; +} + +.panel.image img { + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + max-width: 80%; + max-height: 80%; + background-image: url(Images/checker.png); + -webkit-box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5); +} + +.panel.image .title { + text-align: center; + font-size: 13px; +} + +.panel.image .infoList { + margin: 0; +} + +.panel.image .infoList dt { + font-weight: bold; + display: inline-block; + width: 50%; + text-align: right; +} + +.panel.image .infoList dd { + -webkit-box-sizing: border-box; + display: inline-block; + padding-left: 10px; + width: 50%; + text-align: left; + margin: 0; +} + +.panel.image .infoList dd::after { + white-space: pre; + content: "\A"; +} + +.content.network { +} + +.content.other { + font-family: Monaco, monospace; + font-size: 10px; + white-space: pre-wrap; + padding: 6px; +} + +.content.side { + display: block; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 225px; + bottom: 0; +} + +.content.source iframe { + width: 100%; + height: 100%; +} + +.content.tree { + display: block; + overflow: auto; + padding: 0; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 21px; +} + +.sidebar { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 225px; + background-color: rgb(232, 232, 232); + border-left: 1px solid rgb(64%, 64%, 64%); + -webkit-box-sizing: border-box; + -webkit-user-select: none; + cursor: default; + overflow: auto; + padding: 0; +} + +.crumbs { + -webkit-user-select: none; + cursor: default; + -webkit-box-sizing: border-box; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 21px; + background-image: url(Images/breadcrumbBackground.png); + background-repeat: repeat-x; + border-top: 1px solid #bbb; + font-size: 11px; + line-height: 19px; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; + color: rgb(20, 20, 20); + overflow: hidden; +} + +.crumbs > div { + position: absolute; +} + +.crumbs .crumb { + -webkit-box-sizing: border-box; + height: 20px; + border-width: 0 11px 0 0; + -webkit-border-image: url(Images/segment.png) 0 11 0 0; + margin-right: -11px; + padding-left: 15px; + padding-right: 2px; + white-space: nowrap; + float: right; +} + +.crumbs .crumb.collapsed > * { + display: none; +} + +.crumbs .crumb.collapsed::before { + content: "\2026"; /* ellipses */ + font-weight: bold; +} + +.crumbs .crumb.compact .extra { + display: none; +} + +.crumbs .crumb.dimmed { + color: rgba(0, 0, 0, 0.45); +} + +.crumbs .crumb.start { + padding-left: 7px; +} + +.crumbs .crumb.end { + border-width: 0 2px 0 0; + padding-right: 6px; + -webkit-border-image: url(Images/segmentEnd.png) 0 2 0 0; +} + +.crumbs .crumb.selected { + -webkit-border-image: url(Images/segmentSelected.png) 0 11 0 0; + background-color: transparent !important; + color: black; +} + +.crumbs .crumb.selected:hover { + -webkit-border-image: url(Images/segmentSelected.png) 0 11 0 0; +} + +.crumbs .crumb.selected.end, .crumbs .crumb.selected.end:hover { + -webkit-border-image: url(Images/segmentSelectedEnd.png) 0 2 0 0; +} + +.crumbs .crumb:hover { + -webkit-border-image: url(Images/segmentHover.png) 0 11 0 0; + color: black; +} + +.crumbs .crumb.dimmed:hover { + -webkit-border-image: url(Images/segmentHover.png) 0 11 0 0; + color: rgba(0, 0, 0, 0.75); +} + +.crumbs .crumb.end:hover { + -webkit-border-image: url(Images/segmentHoverEnd.png) 0 2 0 0; +} + +.outline-disclosure li .selection { + display: none; + position: absolute; + left: 0; + right: 0; + height: 15px; + z-index: -1; +} + +.outline-disclosure li.selected .selection { + display: block; +} + +.content.tree > ol, #searchResults > ol { + position: relative; + padding: 2px 6px !important; + margin: 0; + color: black; + -webkit-user-select: none; + cursor: default; + min-width: 100%; + -webkit-box-sizing: border-box; +} + +.outline-disclosure, .outline-disclosure ol { + list-style-type: none; + font-size: 11px; + -webkit-padding-start: 12px; + margin: 0; +} + +.outline-disclosure li { + padding: 0 0 2px 14px; + -webkit-box-sizing: border-box; + margin-top: 1px; + margin-bottom: 1px; + word-wrap: break-word; + text-indent: -2px +} + +.blurred .outline-disclosure li.selected, body.inactive .outline-disclosure li.selected { + background-color: transparent !important; + color: black; +} + +.outline-disclosure li.selected { + background-color: transparent !important; + color: white; +} + +.outline-disclosure li.parent { + text-indent: -12px +} + +.content.tree li .webkit-html-tag.close { + margin-left: -12px; +} + +.outline-disclosure li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + float: left; + width: 8px; + height: 8px; + margin-top: 1px; + padding-right: 2px; +} + +.blurred .outline-disclosure li.parent.selected::before, body.inactive .outline-disclosure li.parent.selected::before { + content: url(Images/treeRightTriangleBlack.png); +} + +.outline-disclosure li.parent.selected::before { + content: url(Images/treeRightTriangleWhite.png); +} + +.blurred .outline-disclosure li.parent.expanded.selected::before, body.inactive .outline-disclosure li.parent.expanded.selected::before { + content: url(Images/treeDownTriangleBlack.png); +} + +.outline-disclosure li.parent.expanded:before { + content: url(Images/treeDownTriangleBlack.png); +} + +.outline-disclosure li.parent.expanded.selected::before { + content: url(Images/treeDownTriangleWhite.png); +} + +.outline-disclosure ol.children { + display: none; +} + +.outline-disclosure ol.children.expanded { + display: block; +} + +.webkit-html-comment { + /* Keep this in sync with view-source.css (.webkit-html-comment) */ + color: rgb(35, 110, 37); +} + +.webkit-html-tag { + /* Keep this in sync with view-source.css (.webkit-html-tag) */ + color: rgb(136, 18, 128); +} + +.webkit-html-attribute-name { + /* Keep this in sync with view-source.css (.webkit-html-attribute-name) */ + color: rgb(153, 69, 0); +} + +.webkit-html-attribute-value { + /* Keep this in sync with view-source.css (.webkit-html-attribute-value) */ + color: rgb(26, 26, 166); +} + +.webkit-html-external-link, .webkit-html-resource-link { + /* Keep this in sync with view-source.css (.webkit-html-external-link, .webkit-html-resource-link) */ + color: #00e; +} + +.webkit-html-external-link { + /* Keep this in sync with view-source.css (.webkit-html-external-link) */ + text-decoration: none; +} + +.webkit-html-external-link:hover { + /* Keep this in sync with view-source.css (.webkit-html-external-link:hover) */ + text-decoration: underline; +} + +body:not(.inactive) .focused .outline-disclosure li.selected * { + color: inherit; +} + +.section { + display: block; + -webkit-box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px; + -webkit-border-radius: 8px; + background-color: white; + font-size: 11px; + margin-bottom: 8px; +} + +.section .header { + padding: 2px 8px 4px; + border: 2px solid rgba(255, 255, 255, 0.5); + background-color: rgb(214, 221, 229); + background-image: url(Images/gradient.png); + background-repeat: repeat-x; + background-position: bottom; + -webkit-background-size: auto 100%; + -webkit-border-radius: 8px; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; +} + +.section.expanded .header { + border-bottom: 2px ridge rgba(214, 221, 229, 0.5); + -webkit-border-top-right-radius: 8px; + -webkit-border-top-left-radius: 8px; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; +} + +.section .header .title { + font-weight: bold; + word-wrap: break-word; +} + +.section .header label { + display: none; +} + +.section.expanded .header label { + display: inline; +} + +.section .header input[type=checkbox] { + height: 1em; + width: 1em; + margin-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: top; +} + +.section .header .subtitle { + margin-top: 2px; + font-size: 10px; + word-wrap: break-word; +} + +.section .header .subtitle a { + color: inherit; +} + +.section .properties { + display: none; + margin: 0; + padding: 2px 6px 5px; + list-style: none; +} + +.section.expanded .properties { + display: block; +} + +.section .properties li { + margin-left: 10px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + -webkit-user-select: text; + cursor: auto; + outline: none; +} + +.section .properties li.parent { + margin-left: 0; +} + +.section .properties li.selected { + background-color: transparent !important; +} + +.section .properties ol { + display: none; + margin: 0; + -webkit-padding-start: 12px; + list-style: none; +} + +.section .properties ol.expanded { + display: block; +} + +.section .properties li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + opacity: 0.75; + float: left; + width: 8px; + height: 8px; + margin-top: 0; + padding-right: 2px; + -webkit-user-select: none; + cursor: default; +} + +.section .properties li.parent.expanded::before { + content: url(Images/treeDownTriangleBlack.png); + margin-top: 1px; +} + +.section .properties li.editing { + -webkit-box-shadow: rgba(0, 0, 0, .5) 3px 3px 4px; + outline: 1px solid rgb(66%, 66%, 66%); + background-color: white; + -webkit-user-modify: read-write-plaintext-only; + text-overflow: clip; + margin-left: 8px; + padding-left: 2px; + margin-bottom: -1px; + padding-bottom: 1px; + text-decoration: none !important; + opacity: 1.0 !important; +} + +.section .properties li.editing.parent::before { + display: none; +} + +.section .properties li.editing * { + color: black !important; +} + +.section .properties .overloaded { + text-decoration: line-through; +} + +.section .properties .implicit, .section .properties .inherited { + opacity: 0.5; +} + +.section:not(.show-inherited) .properties .inherited { + display: none; +} + +.section .properties .name { + color: rgb(136, 19, 145); +} + +.section .properties .value.dimmed { + color: rgb(100, 100, 100); +} + +.section .properties .number { + color: blue; +} + +.section .properties .priority { + color: rgb(128, 0, 0); +} + +.section .properties .keyword { + color: rgb(136, 19, 79); +} + +.section .properties .color { + color: rgb(118, 15, 21); +} + +.swatch { + display: inline-block; + vertical-align: middle; + margin-left: 4px; + width: 0.75em; + height: 0.75em; + border: 1px solid rgb(180, 180, 180); +} + +.pane { + margin-top: 1px; +} + +.pane > .title { + background-image: url(Images/paneHeader.png); + background-repeat: repeat-x; + background-position: bottom; + -webkit-background-size: auto 100%; + height: 14px; + padding: 0 6px; + border-top: 1px solid rgb(129, 129, 129); + border-bottom: 1px solid rgb(129, 129, 129); + font-weight: bold; + font-size: 11px; + color: rgb(85, 85, 85); +} + +.pane > .title:active { + background-image: url(Images/paneHeaderActive.png); +} + +.pane > .title::before { + content: url(Images/treeRightTriangleBlack.png); + opacity: 0.75; + float: left; + width: 8px; + height: 8px; + margin-right: 3px; + margin-top: 0; +} + +.pane.expanded > .title::before { + margin-top: 1px; + content: url(Images/treeDownTriangleBlack.png); +} + +.pane > .body { + position: relative; + padding: 8px; + display: none; + overflow: auto; +} + +.pane.expanded > .body, .pane.expanded > .growbar { + display: block; +} + +.pane > .growbar { + display: none; + background-image: url(Images/paneGrowHandleLine.png), url(Images/paneBottomGrow.png); + background-repeat: no-repeat, repeat-x; + background-position: center center, bottom; + -webkit-background-size: auto 100%, auto 100%; + height: 5px; +} + +.metrics { + font-size: 10px; + text-align: center; + white-space: nowrap; +} + +.metrics .label { + position: absolute; + margin-top: -10px; + font-size: 9px; + color: grey; + background-color: rgb(232, 232, 232); + margin-left: 3px; + padding-left: 2px; + padding-right: 2px; +} + +.metrics .margin { + border: 1px dashed; + display: inline-block; + -webkit-box-sizing: border-box; + padding: 3px; + margin: 3px; +} + +.metrics .border { + border: 1px black solid; + display: inline-block; + vertical-align: middle; + -webkit-box-sizing: border-box; + padding: 3px; + margin: 3px; +} + +.metrics .padding { + border: 1px grey dashed; + display: inline-block; + vertical-align: middle; + -webkit-box-sizing: border-box; + padding: 3px; + margin: 3px; +} + +.metrics .content { + position: static; + border: 1px grey solid; + display: inline-block; + vertical-align: middle; + -webkit-box-sizing: border-box; + padding: 3px; + margin: 3px; + min-width: 80px; + text-align: center; + overflow: visible; +} + +.metrics .left { + display: inline-block; + text-align: center; + vertical-align: middle; + -webkit-box-sizing: border-box; +} + +.metrics .right { + display: inline-block; + text-align: center; + vertical-align: middle; + -webkit-box-sizing: border-box; +} + +.metrics .top { + text-align: center; +} + +.metrics .bottom { + text-align: center; +} + +.console-message-list { + list-style: none; + margin: 0; + padding: 0; + position: absolute; + top: 0; + bottom: 20px; + left: 0; + right: 0; + overflow: auto; + -webkit-user-select: text; + cursor: auto; +} + +.console-prompt { + font-family: monospace; + font-size: 11px; + margin: 0; + padding: 2px 0 0; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 18px; + resize: none; + outline: none; + border: none; + border-top: 1px solid rgb(64%, 64%, 64%); +} + +.console-message, .console-command { + font-size: 10px; + margin: 0; + padding: 3px 3px 3px 24px; + border-bottom: 1px solid rgb(75%, 75%, 75%); + word-break: break-word; + position: relative; +} + +.console-command a:hover { + text-decoration: underline; + cursor: pointer; +} + +.console-message-message { + font-size: 11px; + white-space: pre-wrap; +} + +.console-message-url { + color: rgb(33%, 33%, 33%); + cursor: pointer; +} + +.console-message-url::after { + content: url(Images/goArrow.png); + margin-left: 3px; + width: 12px; + height: 12px; + vertical-align: middle; + opacity: 0.75; + -webkit-user-select: none; +} + +.console-message-url:hover { + color: rgb(15%, 15%, 15%); +} + +.console-message-url:hover::after { + opacity: 1; +} + +.console-error-level::before { + content: url(Images/errorMediumIcon.png); + position: absolute; + left: 5px; + top: 2px; + -webkit-user-select: none; +} + +.console-warning-level::before { + content: url(Images/warningMediumIcon.png); + position: absolute; + left: 4px; + top: 2px; + -webkit-user-select: none; +} + +.console-command-input, .console-command-output { + font-size: 11px; + white-space: pre-wrap; +} + +.console-command-input::before { + content: ">"; + font-weight: bold; + font-size: 15px; + color: blue; + position: absolute; + left: 8px; + top: 1px; + -webkit-user-select: none; +} + +.view-button-browse img { + content: url(Images/databaseBrowserViewNormal.png); + vertical-align: middle; + margin-top: -1px; + margin-left: 1px; + width: 11px; + height: 11px; +} + +.view-button-browse.selected img { + content: url(Images/databaseBrowserViewNormalSelected.png); +} + +body.attached .view-button-browse img { + content: url(Images/databaseBrowserViewSmall.png); + width: 11px; + height: 8px; + margin-top: 1px; + margin-left: 2px; +} + +body.attached .view-button-browse.selected img { + content: url(Images/databaseBrowserViewSmallSelected.png); +} + +.view-button-query img { + content: url(Images/databaseQueryViewNormal.png); + vertical-align: middle; + margin-top: -1px; + margin-left: -1px; + width: 11px; + height: 11px; +} + +.view-button-query.selected img { + content: url(Images/databaseQueryViewNormalSelected.png); +} + +body.attached .view-button-query img { + content: url(Images/databaseQueryViewSmall.png); + width: 10px; + height: 8px; + margin-top: 1px; +} + +body.attached .view-button-query.selected img { + content: url(Images/databaseQueryViewSmallSelected.png); +} + +.database-table-reload { + padding-left: 0; + padding-right: 0; + width: 28px; + margin-left: 6px; +} + +body.attached .database-table-reload { + width: 20px; +} + +.database-table-reload img { + content: url(Images/reload.png); + vertical-align: middle; + margin-top: -2px; + width: 10px; + height: 13px; +} + +.query.content { + bottom: 21px; +} + +.browse.content { + font-size: 10px; + overflow-y: auto; + overflow-x: hidden; + bottom: 21px; +} + +.browse.content .database-result-table { + border: none; +} + +.browse.content .database-table-empty, .browse.content .database-table-error { + position: absolute; + top: 0; + bottom: 25%; + left: 0; + right: 0; + font-size: 24px; + color: rgb(75%, 75%, 75%); + margin-top: auto; + margin-bottom: auto; + height: 50px; + line-height: 26px; + text-align: center; + font-weight: bold; + padding: 10px; + white-space: pre-wrap; +} + +.browse.content .database-table-error { + color: rgb(66%, 33%, 33%); +} + +.database-browse-table { + height: 100%; +} + +.database-result-table .database-result-filler-row { + height: auto; +} + +.database-result-table .database-result-filler-row.alternate td { + background-position-y: 16px; +} + +.database-result-filler-row td { + background-image: url(Images/alternateTableRows.png); +} + +.database-table-select { + margin-left: 6px; + max-width: 150px; + min-width: 75px; +} + +.database-command-list { + list-style: none; + margin: 0; + padding: 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow-y: auto; + overflow-x: hidden; +} + +.database-prompt { + font-family: monospace; + font-size: 11px; + margin: 0; + padding: 2px 0 0; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 18px; + resize: none; + outline: none; + border: none; + border-top: 1px solid rgb(64%, 64%, 64%); +} + +.database-command { + font-size: 10px; + margin: 0; + padding: 5px; + border-bottom: 1px solid rgb(75%, 75%, 75%); + word-break: break-word; + position: relative; +} + +.database-command a:hover { + text-decoration: underline; + cursor: pointer; +} + +.database-command-query { + font-family: monospace; + font-size: 11px; + white-space: pre-wrap; +} + +.database-command-result { + margin-top: 3px; +} + +.database-command-result.error { + color: red; +} + +.database-result-table { + border: 1px solid #aaa; + table-layout: fixed; + border-spacing: 0; + border-collapse: collapse; + width: 100%; + -webkit-box-sizing: border-box; +} + +.database-result-table th { + text-align: left; + background: url(Images/glossyHeader.png) repeat-x; + border-right: 1px solid #aaa; + height: 15px; + -webkit-box-sizing: border-box; + border-bottom: 1px solid #aaa; + font-weight: normal; + vertical-align: middle; + padding: 0 4px; + white-space: nowrap; +} + +.database-result-table tr { + height: 16px; +} + +.database-result-table tr.alternate { + background-color: rgb(236, 243, 254); +} + +.database-result-table td { + vertical-align: top; + padding: 2px 4px; + -webkit-box-sizing: border-box; + white-space: nowrap; + border-right: 1px solid #aaa; +} + +.database-result-table td > div, .database-result-table th > div { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.network-timeline { + position: absolute; + top: 0; + bottom: 99px; + left: 0; + right: 0; + font-family: Lucida Grande, sans-serif; + font-size: 11px; +} + +.network-divider { + width: 1px; + height: 100%; + position: absolute; + background-color: rgba(0, 0, 0, 0.1); +} + +.network-divider.last { + background-color: rgb(66%, 66%, 66%); +} + +.network-divider-label { + position: absolute; + top: 2px; + right: 3px; + font-size: 9px; + color: rgb(50%, 50%, 50%); +} + +.network-dividers { + position: absolute; + left: 153px; + right: 20px; + bottom: 0; + top: 0; + z-index: -100; + border-left: 1px solid rgb(66%, 66%, 66%); + -webkit-box-sizing: border-box; +} + +.network-resources { + position: absolute; + width: 100%; + overflow-y: overlay; + overflow-x: hidden; + border-top: 1px solid rgb(66%, 66%, 66%); + top: 15px; + bottom: 0; +} + +.network-title { + position: relative; + height: 18px; +} + +.network-title:hover { + background-color: rgba(0, 0, 200, 0.1); +} + +.network-info { + background-color: rgb(225, 225, 235); + background-image: url(Images/attachedShadow.png), url(Images/bottomShadow.png); + background-repeat: repeat-x; + background-position: top, bottom; + overflow: hidden; + -webkit-user-select: text; + cursor: auto; +} + +.network-info table { + font-size: 11px; + margin: 5px 15px 5px 5px; +} + +.network-info th { + width: 145px; +} + +.network-info thead th { + text-align: right; +} + +.network-info tbody th { + white-space: nowrap; + text-align: right; + font-weight: bold; + color: rgba(0, 0, 0, 0.5); + vertical-align: top; +} + +.network-info td { + word-break: break-word; + white-space: normal; +} + +.network-file { + position: absolute; + left: 5px; + height: 100%; + width: 145px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + line-height: 18px; + -webkit-user-select: text; +} + +.network-file a { + color: inherit; + text-decoration: none; +} + +.network-file a:hover { + text-decoration: underline; +} + +.network-area { + position: absolute; + left: 162px; + right: 28px; + height: 100%; +} + +.network-bar { + position: absolute; + top: 0; + bottom: 0; + margin: auto -7px; + border-width: 6px 7px 6px 7px; + height: 13px; + min-width: 14px; + -webkit-box-sizing: border-box; + opacity: 0.8; + -webkit-border-image: url(Images/timelinePillGray.png) 6 7 6 7; +} + +.network-bar.network-category-documents { + -webkit-border-image: url(Images/timelinePillBlue.png) 6 7 6 7; +} + +.network-bar.network-category-stylesheets { + -webkit-border-image: url(Images/timelinePillGreen.png) 6 7 6 7; +} + +.network-bar.network-category-images { + -webkit-border-image: url(Images/timelinePillPurple.png) 6 7 6 7; +} + +.network-bar.network-category-fonts { + -webkit-border-image: url(Images/timelinePillYellow.png) 6 7 6 7; +} + +.network-bar.network-category-scripts { + -webkit-border-image: url(Images/timelinePillOrange.png) 6 7 6 7; +} + +.network-summary { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 99px; + background-color: rgb(101, 111, 130); + background-image: url(Images/darkShadow.png), url(Images/gradientHighlightBottom.png); + background-repeat: repeat-x; + background-position: top, bottom; +} + +.network-graph-area { + padding-top: 20px; + position: absolute; + margin: auto; + top: 0; + bottom: 0; + right: 0; + left: 0; + width: 575px; + white-space: nowrap; + color: white; + text-shadow: black 0px 1px 1px; +} + +.network-graph-label { + height: 38px; + display: inline-block; + vertical-align: top; + margin-right: 5px; + margin-top: -2px; + text-align: right; +} + +.network-graph-side { + position: relative; + display: inline-block; + vertical-align: top; +} + +.network-graph-legend-total { + margin-top: 12px; + padding-right: 5px; +} + +.network-graph-legend-total .network-graph-legend-label { + text-align: right; +} + +.network-graph-mode { + -webkit-appearance: none; + background-color: transparent; + border: none; + font-weight: bold; + font-size: 12px; + height: 18px; + line-height: 11px; + text-align: right; + vertical-align: middle; + padding: 2px 16px 2px 8px; + margin: 0; + background-image: url(Images/popupArrows.png); + background-position: right center; + background-repeat: no-repeat; + color: inherit; + border: 1px solid transparent; + text-shadow: black 0px 2px 2px; +} + +.network-graph-mode:focus { + outline: none; +} + +.network-graph-mode:hover { + -webkit-border-radius: 9px; + background-color: rgba(0, 0, 0, 0.2); + border: 1px solid white; + -webkit-box-shadow: black 0px 1px 1px; +} + +.network-graph-legend { + margin-top: -8px; + text-align: center; +} + +.network-graph-legend-item { + display: inline-block; + font-weight: bold; + margin-right: 15px; + vertical-align: top; +} + +.network-graph-legend-label { + display: inline-block; + text-align: left; +} + +.network-graph-legend-header { + font-size: 12px; + text-transform: capitalize; +} + +.network-graph-legend-value { + font-size: 10px; +} + +.network-graph-legend-swatch { + vertical-align: top; + margin-top: 1px; + margin-right: 3px; +} + +.network-summary-graph { + vertical-align: middle; +} + +.tip-button { + background-image: url(Images/tipIcon.png); + border: none; + width: 16px; + height: 16px; + float: right; + background-color: transparent; + margin-top: 1px; +} + +.tip-button:active { + background-image: url(Images/tipIconPressed.png); +} + +.tip-balloon { + position: absolute; + left: 145px; + top: -5px; + z-index: 1000; + border-width: 51px 15px 18px 37px; + -webkit-border-image: url(Images/tipBalloon.png) 51 15 18 37; + width: 265px; +} + +.tip-balloon.bottom { + position: absolute; + left: 145px; + top: auto; + bottom: -7px; + z-index: 1000; + border-width: 18px 15px 51px 37px; + -webkit-border-image: url(Images/tipBalloonBottom.png) 18 15 51 37; +} + +.tip-balloon-content { + margin-top: -40px; + margin-bottom: -2px; + margin-left: 2px; +} + +.tip-balloon.bottom .tip-balloon-content { + margin-top: -10px; + margin-bottom: -35px; +} + +.sidebar-resizer-vertical { + position: absolute; + top: 0; + bottom: 0; + width: 5px; + z-index: 100; + cursor: col-resize; +} + +.sidebar-resizer-vertical-left { + left: 197px; +} + +.sidebar-resizer-vertical-right { + right: 222px; +} + +#searchResultsResizer { + position: absolute; + height: 5px; + left: 0; + right: 0; + cursor: row-resize; +} diff --git a/WebCore/page/inspector/inspector.html b/WebCore/page/inspector/inspector.html new file mode 100644 index 0000000..3ba0300 --- /dev/null +++ b/WebCore/page/inspector/inspector.html @@ -0,0 +1,75 @@ +<!-- +Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" type="text/css" href="inspector.css" /> + <script type="text/javascript" src="utilities.js"></script> + <script type="text/javascript" src="treeoutline.js"></script> + <script type="text/javascript" src="inspector.js"></script> + <script type="text/javascript" src="Resource.js"></script> + <script type="text/javascript" src="ResourceCategory.js"></script> + <script type="text/javascript" src="Database.js"></script> + <script type="text/javascript" src="SidebarPane.js"></script> + <script type="text/javascript" src="PropertiesSection.js"></script> + <script type="text/javascript" src="MetricsSidebarPane.js"></script> + <script type="text/javascript" src="PropertiesSidebarPane.js"></script> + <script type="text/javascript" src="StylesSidebarPane.js"></script> + <script type="text/javascript" src="Panel.js"></script> + <script type="text/javascript" src="ResourcePanel.js"></script> + <script type="text/javascript" src="SourcePanel.js"></script> + <script type="text/javascript" src="ConsolePanel.js"></script> + <script type="text/javascript" src="DatabasePanel.js"></script> + <script type="text/javascript" src="DocumentPanel.js"></script> + <script type="text/javascript" src="FontPanel.js"></script> + <script type="text/javascript" src="ImagePanel.js"></script> + <script type="text/javascript" src="NetworkPanel.js"></script> +</head> +<body class="detached"> + <div id="toolbar"> + <button id="back" class="split-button first"><img></button><img class="split-button-divider"><button id="forward" class="split-button last"><img></button> + <span id="toolbarButtons"></span> + <input id="search" type="search" autosave="inspectorSearch" results="20" incremental="incremental" onsearch="WebInspector.performSearch(this.value)"> + </div> + <div id="sidebar" class="focusable focused"> + <ol id="list"></ol> + <ol id="status"></ol> + <div id="statusbar"> + <button id="attachToggle"></button> + <span id="sidebarResizeWidget"></span> + </div> + <div id="sidebarResizer" class="sidebar-resizer-vertical sidebar-resizer-vertical-left"></div> + </div> + <div id="main" class="focusable blurred"> + <div id="searchResults" class="focusable hidden"></div> + <div id="searchResultsResizer" class="hidden"></div> + <div id="panels"></div> + </div> +</body> +</html> diff --git a/WebCore/page/inspector/inspector.js b/WebCore/page/inspector/inspector.js new file mode 100644 index 0000000..a9f5fcf --- /dev/null +++ b/WebCore/page/inspector/inspector.js @@ -0,0 +1,1186 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var Preferences = { + ignoreWhitespace: true, + showUserAgentStyles: true, + maxInlineTextChildLength: 80, + maxTextSearchResultLength: 80, + showInheritedComputedStyleProperties: false, + showMissingLocalizedStrings: false +} + +var WebInspector = { + resources: [], + resourceURLMap: {}, + backForwardList: [], + searchResultsHeight: 100, + localizedStrings: {}, + missingLocalizedStrings: {}, + + get consolePanel() + { + if (!this._consolePanel) + this._consolePanel = new WebInspector.ConsolePanel(); + + return this._consolePanel; + }, + + get networkPanel() + { + if (!this._networkPanel) + this._networkPanel = new WebInspector.NetworkPanel(); + + return this._networkPanel; + }, + + get currentBackForwardIndex() + { + if (this._currentBackForwardIndex === undefined) + this._currentBackForwardIndex = -1; + + return this._currentBackForwardIndex; + }, + + set currentBackForwardIndex(x) + { + if (this._currentBackForwardIndex === x) + return; + + this._currentBackForwardIndex = x; + this.updateBackForwardButtons(); + }, + + get currentFocusElement() + { + return this._currentFocusElement; + }, + + set currentFocusElement(x) + { + if (!x || this._currentFocusElement === x) + return; + + if (this._currentFocusElement) { + this._currentFocusElement.removeStyleClass("focused"); + this._currentFocusElement.addStyleClass("blurred"); + if (this._currentFocusElement.blurred) + this._currentFocusElement.blurred(); + } + + this._currentFocusElement = x; + + if (x) { + x.addStyleClass("focused"); + x.removeStyleClass("blurred"); + if (this._currentFocusElement.focused) + this._currentFocusElement.focused(); + } + }, + + get currentPanel() + { + return this._currentPanel; + }, + + set currentPanel(x) + { + if (this._currentPanel === x) + return; + + if (this._currentPanel) + this._currentPanel.hide(); + + this._currentPanel = x; + + if (x) + x.show(); + }, + + get attached() + { + return this._attached; + }, + + set attached(x) + { + if (this._attached === x) + return; + + this._attached = x; + + var body = document.body; + if (x) { + InspectorController.attach(); + body.removeStyleClass("detached"); + body.addStyleClass("attached"); + } else { + InspectorController.detach(); + body.removeStyleClass("attached"); + body.addStyleClass("detached"); + } + }, + + get showingSearchResults() + { + return this._showingSearchResults; + }, + + set showingSearchResults(x) + { + if (this._showingSearchResults === x) + return; + + this._showingSearchResults = x; + + var resultsContainer = document.getElementById("searchResults"); + var searchResultsResizer = document.getElementById("searchResultsResizer"); + + if (x) { + resultsContainer.removeStyleClass("hidden"); + searchResultsResizer.removeStyleClass("hidden"); + + var animations = [ + {element: resultsContainer, end: {top: 0}}, + {element: searchResultsResizer, end: {top: WebInspector.searchResultsHeight - 3}}, + {element: document.getElementById("panels"), end: {top: WebInspector.searchResultsHeight}} + ]; + + WebInspector.animateStyle(animations, 250); + } else { + searchResultsResizer.addStyleClass("hidden"); + + var animations = [ + {element: resultsContainer, end: {top: -WebInspector.searchResultsHeight}}, + {element: searchResultsResizer, end: {top: 0}}, + {element: document.getElementById("panels"), end: {top: 0}} + ]; + + var animationFinished = function() + { + resultsContainer.addStyleClass("hidden"); + resultsContainer.removeChildren(); + delete this.searchResultsTree; + }; + + WebInspector.animateStyle(animations, 250, animationFinished); + } + } +} + +WebInspector.loaded = function() +{ + var platform = InspectorController.platform(); + document.body.addStyleClass("platform-" + platform); + + this.fileOutline = new TreeOutline(document.getElementById("list")); + this.fileOutline.expandTreeElementsWhenArrowing = true; + + this.statusOutline = new TreeOutline(document.getElementById("status")); + this.statusOutline.expandTreeElementsWhenArrowing = true; + + this.resourceCategories = { + documents: new WebInspector.ResourceCategory(WebInspector.UIString("documents"), "documents"), + stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("stylesheets"), "stylesheets"), + images: new WebInspector.ResourceCategory(WebInspector.UIString("images"), "images"), + scripts: new WebInspector.ResourceCategory(WebInspector.UIString("scripts"), "scripts"), + fonts: new WebInspector.ResourceCategory(WebInspector.UIString("fonts"), "fonts"), + databases: new WebInspector.ResourceCategory(WebInspector.UIString("databases"), "databases"), + other: new WebInspector.ResourceCategory(WebInspector.UIString("other"), "other") + }; + + this.Tips = { + ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} + }; + + this.Warnings = { + IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")} + }; + + this.consoleListItem = new WebInspector.ConsoleStatusTreeElement(WebInspector.consolePanel); + this.statusOutline.appendChild(this.consoleListItem); + + this.networkListItem = new WebInspector.StatusTreeElement(WebInspector.UIString("Network"), "network", WebInspector.networkPanel); + this.statusOutline.appendChild(this.networkListItem); + + this.resourceCategories.documents.listItem.expand(); + + this.currentFocusElement = document.getElementById("sidebar"); + + this.addMainEventListeners(document); + + window.addEventListener("unload", this.windowUnload.bind(this), true); + window.addEventListener("resize", this.windowResize.bind(this), true); + + document.addEventListener("mousedown", this.changeFocus.bind(this), true); + document.addEventListener("focus", this.changeFocus.bind(this), true); + document.addEventListener("keydown", this.documentKeyDown.bind(this), true); + document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); + document.addEventListener("copy", this.documentCopy.bind(this), true); + + document.getElementById("back").title = WebInspector.UIString("Show previous panel."); + document.getElementById("forward").title = WebInspector.UIString("Show next panel."); + + document.getElementById("search").setAttribute("placeholder", WebInspector.UIString("Search")); + + document.getElementById("back").addEventListener("click", this.back.bind(this), true); + document.getElementById("forward").addEventListener("click", this.forward.bind(this), true); + this.updateBackForwardButtons(); + + document.getElementById("attachToggle").addEventListener("click", this.toggleAttach.bind(this), true); + + document.getElementById("sidebarResizeWidget").addEventListener("mousedown", this.sidebarResizerDragStart, true); + document.getElementById("sidebarResizer").addEventListener("mousedown", this.sidebarResizerDragStart, true); + document.getElementById("searchResultsResizer").addEventListener("mousedown", this.searchResultsResizerDragStart, true); + + if (platform === "mac-leopard") + document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true); + + document.body.addStyleClass("detached"); + + InspectorController.loaded(); +} + +var windowLoaded = function() +{ + var localizedStringsURL = InspectorController.localizedStringsURL(); + if (localizedStringsURL) { + var localizedStringsScriptElement = document.createElement("script"); + localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); + localizedStringsScriptElement.type = "text/javascript"; + localizedStringsScriptElement.src = localizedStringsURL; + document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement); + } else + WebInspector.loaded(); + + window.removeEventListener("load", windowLoaded, false); + delete windowLoaded; +}; + +window.addEventListener("load", windowLoaded, false); + +WebInspector.windowUnload = function(event) +{ + InspectorController.windowUnloading(); +} + +WebInspector.windowResize = function(event) +{ + if (this.currentPanel && this.currentPanel.resize) + this.currentPanel.resize(); +} + +WebInspector.windowFocused = function(event) +{ + if (event.target.nodeType === Node.DOCUMENT_NODE) + document.body.removeStyleClass("inactive"); +} + +WebInspector.windowBlured = function(event) +{ + if (event.target.nodeType === Node.DOCUMENT_NODE) + document.body.addStyleClass("inactive"); +} + +WebInspector.changeFocus = function(event) +{ + var nextFocusElement; + + var current = event.target; + while (current) { + if (current.nodeName.toLowerCase() === "input") + nextFocusElement = current; + current = current.parentNode; + } + + if (!nextFocusElement) + nextFocusElement = event.target.firstParentWithClass("focusable"); + + this.currentFocusElement = nextFocusElement; +} + +WebInspector.documentClick = function(event) +{ + var anchor = event.target.firstParentOrSelfWithNodeName("a"); + if (!anchor || !anchor.hasStyleClass("webkit-html-resource-link")) + return; + + if (WebInspector.showResourceForURL(anchor.getAttribute("href"))) { + event.preventDefault(); + event.stopPropagation(); + } +} + +WebInspector.documentKeyDown = function(event) +{ + if (!this.currentFocusElement) + return; + if (this.currentFocusElement.handleKeyEvent) + this.currentFocusElement.handleKeyEvent(event); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"]) + WebInspector[this.currentFocusElement.id + "KeyDown"](event); +} + +WebInspector.documentCanCopy = function(event) +{ + if (!this.currentFocusElement) + return; + // Calling preventDefault() will say "we support copying, so enable the Copy menu". + if (this.currentFocusElement.handleCopyEvent) + event.preventDefault(); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) + event.preventDefault(); +} + +WebInspector.documentCopy = function(event) +{ + if (!this.currentFocusElement) + return; + if (this.currentFocusElement.handleCopyEvent) + this.currentFocusElement.handleCopyEvent(event); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) + WebInspector[this.currentFocusElement.id + "Copy"](event); +} + +WebInspector.sidebarKeyDown = function(event) +{ + var nextSelectedElement; + + if (this.fileOutline.selectedTreeElement) { + if (!this.fileOutline.handleKeyEvent(event) && event.keyIdentifier === "Down" && !event.altKey) { + var nextSelectedElement = this.statusOutline.children[0]; + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(false); + } + } else if (this.statusOutline.selectedTreeElement) { + if (!this.statusOutline.handleKeyEvent(event) && event.keyIdentifier === "Up" && !event.altKey) { + var nextSelectedElement = this.fileOutline.children[0]; + var lastSelectable = null; + + while (nextSelectedElement) { + if (nextSelectedElement.selectable) + lastSelectable = nextSelectedElement; + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(false); + } + + nextSelectedElement = lastSelectable; + } + } + + if (nextSelectedElement) { + nextSelectedElement.reveal(); + nextSelectedElement.select(); + + event.preventDefault(); + event.stopPropagation(); + } +} + +WebInspector.sidebarCopy = function(event) +{ + event.clipboardData.clearData(); + event.preventDefault(); + + var selectedElement = this.fileOutline.selectedTreeElement; + if (!selectedElement || !selectedElement.representedObject || !selectedElement.representedObject.url) + return; + + event.clipboardData.setData("URL", this.fileOutline.selectedTreeElement.representedObject.url); +} + +WebInspector.mainKeyDown = function(event) +{ + if (this.currentPanel && this.currentPanel.handleKeyEvent) + this.currentPanel.handleKeyEvent(event); +} + +WebInspector.mainCopy = function(event) +{ + if (this.currentPanel && this.currentPanel.handleCopyEvent) + this.currentPanel.handleCopyEvent(event); +} + +WebInspector.searchResultsKeyDown = function(event) +{ + if (this.searchResultsTree) + this.searchResultsTree.handleKeyEvent(event); +} + +WebInspector.animateStyle = function(animations, duration, callback, complete) +{ + if (complete === undefined) + complete = 0; + var slice = (1000 / 30); // 30 frames per second + + var defaultUnit = "px"; + var propertyUnit = {opacity: ""}; + + for (var i = 0; i < animations.length; ++i) { + var animation = animations[i]; + var element = null; + var start = null; + var current = null; + var end = null; + for (key in animation) { + if (key === "element") + element = animation[key]; + else if (key === "start") + start = animation[key]; + else if (key === "current") + current = animation[key]; + else if (key === "end") + end = animation[key]; + } + + if (!element || !end) + continue; + + var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element); + if (!start) { + start = {}; + for (key in end) + start[key] = parseInt(computedStyle.getPropertyValue(key)); + animation.start = start; + } else if (complete == 0) + for (key in start) + element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + + if (!current) { + current = {}; + for (key in start) + current[key] = start[key]; + animation.current = current; + } + + function cubicInOut(t, b, c, d) + { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + } + + var style = element.style; + for (key in end) { + var startValue = start[key]; + var currentValue = current[key]; + var endValue = end[key]; + if ((complete + slice) < duration) { + var delta = (endValue - startValue) / (duration / slice); + var newValue = cubicInOut(complete, startValue, endValue - startValue, duration); + style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + current[key] = newValue; + } else { + style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + } + } + } + + if (complete < duration) + setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice); + else if (callback) + callback(); +} + +WebInspector.toggleAttach = function() +{ + this.attached = !this.attached; +} + +WebInspector.toolbarDragStart = function(event) +{ + var toolbar = document.getElementById("toolbar"); + if (event.target !== toolbar || WebInspector.attached) + return; + + toolbar.lastScreenX = event.screenX; + toolbar.lastScreenY = event.screenY; + + document.addEventListener("mousemove", WebInspector.toolbarDrag, true); + document.addEventListener("mouseup", WebInspector.toolbarDragEnd, true); + document.body.style.cursor = "default"; + + event.preventDefault(); +} + +WebInspector.toolbarDragEnd = function(event) +{ + var toolbar = document.getElementById("toolbar"); + delete toolbar.lastScreenX; + delete toolbar.lastScreenY; + + document.removeEventListener("mousemove", WebInspector.toolbarDrag, true); + document.removeEventListener("mouseup", WebInspector.toolbarDragEnd, true); + document.body.style.removeProperty("cursor"); + + event.preventDefault(); +} + +WebInspector.toolbarDrag = function(event) +{ + var toolbar = document.getElementById("toolbar"); + + var x = event.screenX - toolbar.lastScreenX; + var y = event.screenY - toolbar.lastScreenY; + + toolbar.lastScreenX = event.screenX; + toolbar.lastScreenY = event.screenY; + + // We cannot call window.moveBy here because it restricts the movement of the window + // at the edges. + InspectorController.moveByUnrestricted(x, y); + + event.preventDefault(); +} + +WebInspector.sidebarResizerDragStart = function(event) +{ + WebInspector.elementDragStart(document.getElementById("sidebar"), WebInspector.sidebarResizerDrag, WebInspector.sidebarResizerDragEnd, event, "col-resize"); +} + +WebInspector.sidebarResizerDragEnd = function(event) +{ + WebInspector.elementDragEnd(document.getElementById("sidebar"), WebInspector.sidebarResizerDrag, WebInspector.sidebarResizerDragEnd, event); +} + +WebInspector.sidebarResizerDrag = function(event) +{ + var x = event.pageX; + + // FIXME: We can should come up with a better hueristic for constraining the size of the sidebar. + var newWidth = Number.constrain(x, 100, window.innerWidth - 100); + + document.getElementById("sidebar").style.width = newWidth + "px"; + document.getElementById("sidebarResizer").style.left = (newWidth - 3) + "px"; + document.getElementById("main").style.left = newWidth + "px"; + document.getElementById("toolbarButtons").style.left = newWidth + "px"; + + if (WebInspector.currentPanel && WebInspector.currentPanel.resize) + WebInspector.currentPanel.resize(); + + event.preventDefault(); +} + +WebInspector.searchResultsResizerDragStart = function(event) +{ + WebInspector.elementDragStart(document.getElementById("searchResults"), WebInspector.searchResultsResizerDrag, WebInspector.searchResultsResizerDragEnd, event, "row-resize"); +} + +WebInspector.searchResultsResizerDragEnd = function(event) +{ + WebInspector.elementDragEnd(document.getElementById("searchResults"), WebInspector.searchResultsResizerDrag, WebInspector.searchResultsResizerDragEnd, event); +} + +WebInspector.searchResultsResizerDrag = function(event) +{ + var y = event.pageY - document.getElementById("main").offsetTop; + var newHeight = Number.constrain(y, 100, window.innerHeight - 100); + + WebInspector.searchResultsHeight = newHeight; + + document.getElementById("searchResults").style.height = WebInspector.searchResultsHeight + "px"; + document.getElementById("panels").style.top = newHeight + "px"; + document.getElementById("searchResultsResizer").style.top = (newHeight - 3) + "px"; + + event.preventDefault(); +} + +WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) +{ + if (WebInspector.draggingElement) + return elementDragEnd(event); + + WebInspector.draggingElement = true; + + document.addEventListener("mousemove", dividerDrag, true); + document.addEventListener("mouseup", elementDragEnd, true); + document.body.style.cursor = cursor; + + event.preventDefault(); +} + +WebInspector.elementDragEnd = function(element, dividerDrag, elementDragEnd, event) +{ + document.removeEventListener("mousemove", dividerDrag, true); + document.removeEventListener("mouseup", elementDragEnd, true); + document.body.style.removeProperty("cursor"); + + delete WebInspector.draggingElement; + + event.preventDefault(); +} + +WebInspector.back = function() +{ + if (this.currentBackForwardIndex <= 0) { + console.error("Can't go back from index " + this.currentBackForwardIndex); + return; + } + + this.navigateToPanel(this.backForwardList[--this.currentBackForwardIndex], null, true); +} + +WebInspector.forward = function() +{ + if (this.currentBackForwardIndex >= this.backForwardList.length - 1) { + console.error("Can't go forward from index " + this.currentBackForwardIndex); + return; + } + + this.navigateToPanel(this.backForwardList[++this.currentBackForwardIndex], null, true); +} + +WebInspector.updateBackForwardButtons = function() +{ + var index = this.currentBackForwardIndex; + + document.getElementById("back").disabled = index <= 0; + document.getElementById("forward").disabled = index >= this.backForwardList.length - 1; +} + +WebInspector.showConsole = function() +{ + this.navigateToPanel(WebInspector.consolePanel); +} + +WebInspector.showTimeline = function() +{ + this.navigateToPanel(WebInspector.networkPanel); +} + +WebInspector.addResource = function(resource) +{ + this.resources.push(resource); + + if (resource.mainResource) + this.mainResource = resource; + + if (resource.url) { + this.resourceURLMap[resource.url] = resource; + this.networkPanel.addResourceToTimeline(resource); + } +} + +WebInspector.removeResource = function(resource) +{ + resource.detach(); + + resource.category.removeResource(resource); + + if (resource.url) + delete this.resourceURLMap[resource.url]; + + var resourcesLength = this.resources.length; + for (var i = 0; i < resourcesLength; ++i) { + if (this.resources[i] === resource) { + this.resources.splice(i, 1); + break; + } + } +} + +WebInspector.clearResources = function() +{ + for (var category in this.resourceCategories) + this.resourceCategories[category].removeAllResources(); + this.resources = []; + this.backForwardList = []; + this.currentBackForwardIndex = -1; + delete this.mainResource; +} + +WebInspector.clearDatabaseResources = function() +{ + this.resourceCategories.databases.removeAllResources(); +} + +WebInspector.resourceURLChanged = function(resource, oldURL) +{ + delete this.resourceURLMap[oldURL]; + this.resourceURLMap[resource.url] = resource; +} + +WebInspector.addMessageToConsole = function(msg) +{ + this.consolePanel.addMessage(msg); + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + ++this.consoleListItem.warnings; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + ++this.consoleListItem.errors; + break; + } +} + +WebInspector.clearConsoleMessages = function() +{ + this.consolePanel.clearMessages(); + this.consoleListItem.warnings = this.consoleListItem.errors = 0; +} + +WebInspector.clearNetworkTimeline = function() +{ + if (this._networkPanel) + this._networkPanel.clearTimeline(); +} + +WebInspector.drawLoadingPieChart = function(canvas, percent) { + var g = canvas.getContext("2d"); + var darkColor = "rgb(122, 168, 218)"; + var lightColor = "rgb(228, 241, 251)"; + var cx = 8; + var cy = 8; + var r = 7; + + g.beginPath(); + g.arc(cx, cy, r, 0, Math.PI * 2, false); + g.closePath(); + + g.lineWidth = 1; + g.strokeStyle = darkColor; + g.fillStyle = lightColor; + g.fill(); + g.stroke(); + + var startangle = -Math.PI / 2; + var endangle = startangle + (percent * Math.PI * 2); + + g.beginPath(); + g.moveTo(cx, cy); + g.arc(cx, cy, r, startangle, endangle, false); + g.closePath(); + + g.fillStyle = darkColor; + g.fill(); +} + +WebInspector.updateFocusedNode = function(node) +{ + if (!node) + // FIXME: Should we deselect if null is passed in? + return; + + for (var i = 0; i < this.resourceCategories.documents.resources.length; ++i) { + var resource = this.resourceCategories.documents.resources[i]; + if (resource.documentNode !== node.ownerDocument) + continue; + + this.navigateToPanel(resource.panel, "dom"); + resource.panel.focusedDOMNode = node; + + this.currentFocusElement = document.getElementById("main"); + + break; + } +} + +WebInspector.resourceForURL = function(url) +{ + for (var resourceURL in this.resourceURLMap) { + if (resourceURL.hasSubstring(url)) + return this.resourceURLMap[resourceURL]; + } + + return null; +} + +WebInspector.showResourceForURL = function(url) +{ + var resource = this.resourceForURL(url); + if (!resource) + return false; + + this.navigateToResource(resource); + return true; +} + +WebInspector.linkifyURL = function(url, linkText, classes, isExternal) +{ + if (linkText === undefined) + linkText = url.escapeHTML(); + classes = (classes === undefined) ? "" : classes + " "; + classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"; + var link = "<a href=\"" + url + "\" class=\"" + classes + "\" title=\"" + url + "\" target=\"_blank\">" + linkText + "</a>"; + return link; +} + +WebInspector.addMainEventListeners = function(doc) +{ + doc.defaultView.addEventListener("focus", function(event) { WebInspector.windowFocused(event) }, true); + doc.defaultView.addEventListener("blur", function(event) { WebInspector.windowBlured(event) }, true); + doc.addEventListener("click", function(event) { WebInspector.documentClick(event) }, true); +} + +WebInspector.performSearch = function(query) +{ + if (!query || !query.length) { + this.showingSearchResults = false; + return; + } + + var resultsContainer = document.getElementById("searchResults"); + resultsContainer.removeChildren(); + + var isXPath = query.indexOf("/") !== -1; + + var xpathQuery; + if (isXPath) + xpathQuery = query; + else { + var escapedQuery = query.escapeCharacters("'"); + xpathQuery = "//*[contains(name(),'" + escapedQuery + "') or contains(@*,'" + escapedQuery + "')] | //text()[contains(.,'" + escapedQuery + "')] | //comment()[contains(.,'" + escapedQuery + "')]"; + } + + var resourcesToSearch = [].concat(this.resourceCategories.documents.resources, this.resourceCategories.stylesheets.resources, this.resourceCategories.scripts.resources, this.resourceCategories.other.resources); + + var files = []; + for (var i = 0; i < resourcesToSearch.length; ++i) { + var resource = resourcesToSearch[i]; + + var sourceResults = []; + if (!isXPath && "source" in resource.panel.views) { + resource.panel.setupSourceFrameIfNeeded(); + sourceResults = InspectorController.search(resource.panel.views.source.frameElement.contentDocument, query); + } + + var domResults = []; + const searchResultsProperty = "__includedInInspectorSearchResults"; + function addNodesToDOMResults(nodes, length, getItem) + { + for (var i = 0; i < length; ++i) { + var node = getItem(nodes, i); + if (searchResultsProperty in node) + continue; + node[searchResultsProperty] = true; + domResults.push(node); + } + } + + function cleanUpDOMResultsNodes() + { + for (var i = 0; i < domResults.length; ++i) + delete domResults[i][searchResultsProperty]; + } + + if (resource.category === this.resourceCategories.documents) { + var doc = resource.documentNode; + try { + var result = Document.prototype.evaluate.call(doc, xpathQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToDOMResults(result, result.snapshotLength, function(l, i) { return l.snapshotItem(i); }); + } catch(err) { + // ignore any exceptions. the query might be malformed, but we allow that. + } + + var result = Document.prototype.querySelectorAll.call(doc, query); + addNodesToDOMResults(result, result.length, function(l, i) { return l.item(i); }); + + cleanUpDOMResultsNodes(); + } + + if ((!sourceResults || !sourceResults.length) && !domResults.length) + continue; + + files.push({resource: resource, sourceResults: sourceResults, domResults: domResults}); + } + + if (!files.length) + return; + + this.showingSearchResults = true; + + var fileList = document.createElement("ol"); + fileList.className = "outline-disclosure"; + resultsContainer.appendChild(fileList); + + this.searchResultsTree = new TreeOutline(fileList); + this.searchResultsTree.expandTreeElementsWhenArrowing = true; + + var sourceResultSelected = function(element) + { + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(element.representedObject.range); + + WebInspector.navigateToPanel(element.representedObject.panel, "source"); + element.representedObject.line.scrollIntoViewIfNeeded(true); + element.listItemElement.scrollIntoViewIfNeeded(false); + } + + var domResultSelected = function(element) + { + WebInspector.navigateToPanel(element.representedObject.panel, "dom"); + element.representedObject.panel.focusedDOMNode = element.representedObject.node; + element.listItemElement.scrollIntoViewIfNeeded(false); + } + + for (var i = 0; i < files.length; ++i) { + var file = files[i]; + + var fileItem = new TreeElement(file.resource.displayName, {}, true); + fileItem.expanded = true; + fileItem.selectable = false; + this.searchResultsTree.appendChild(fileItem); + + if (file.sourceResults && file.sourceResults.length) { + for (var j = 0; j < file.sourceResults.length; ++j) { + var range = file.sourceResults[j]; + + var line = range.startContainer; + while (line.parentNode && line.nodeName.toLowerCase() != "tr") + line = line.parentNode; + var lineRange = file.resource.panel.views.source.frameElement.contentDocument.createRange(); + lineRange.selectNodeContents(line); + + // Don't include any error bubbles in the search result + var end = line.lastChild.lastChild; + if (end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble")) { + while (end && end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble")) + end = end.previousSibling; + lineRange.setEndAfter(end); + } + + var beforeRange = file.resource.panel.views.source.frameElement.contentDocument.createRange(); + beforeRange.setStart(lineRange.startContainer, lineRange.startOffset); + beforeRange.setEnd(range.startContainer, range.startOffset); + + var afterRange = file.resource.panel.views.source.frameElement.contentDocument.createRange(); + afterRange.setStart(range.endContainer, range.endOffset); + afterRange.setEnd(lineRange.endContainer, lineRange.endOffset); + + var beforeText = beforeRange.toString().trimLeadingWhitespace(); + var text = range.toString(); + var afterText = afterRange.toString().trimTrailingWhitespace(); + + var length = beforeText.length + text.length + afterText.length; + if (length > Preferences.maxTextSearchResultLength) { + var beforeAfterLength = (Preferences.maxTextSearchResultLength - text.length) / 2; + if (beforeText.length > beforeAfterLength) + beforeText = "\u2026" + beforeText.substr(-beforeAfterLength); + if (afterText.length > beforeAfterLength) + afterText = afterText.substr(0, beforeAfterLength) + "\u2026"; + } + + var title = "<div class=\"selection selected\"></div>"; + if (j == 0) + title += "<div class=\"search-results-section\">" + WebInspector.UIString("Source") + "</div>"; + title += beforeText.escapeHTML() + "<span class=\"search-matched-string\">" + text.escapeHTML() + "</span>" + afterText.escapeHTML(); + var item = new TreeElement(title, {panel: file.resource.panel, line: line, range: range}, false); + item.onselect = sourceResultSelected; + fileItem.appendChild(item); + } + } + + if (file.domResults.length) { + for (var j = 0; j < file.domResults.length; ++j) { + var node = file.domResults[j]; + var title = "<div class=\"selection selected\"></div>"; + if (j == 0) + title += "<div class=\"search-results-section\">" + WebInspector.UIString("DOM") + "</div>"; + title += nodeTitleInfo.call(node).title; + var item = new TreeElement(title, {panel: file.resource.panel, node: node}, false); + item.onselect = domResultSelected; + fileItem.appendChild(item); + } + } + } +} + +WebInspector.navigateToResource = function(resource) +{ + this.navigateToPanel(resource.panel); +} + +WebInspector.navigateToPanel = function(panel, view, fromBackForwardAction) +{ + if (this.currentPanel === panel) { + if (panel && view) + panel.currentView = view; + return; + } + + if (!fromBackForwardAction) { + var oldIndex = this.currentBackForwardIndex; + if (oldIndex >= 0) + this.backForwardList.splice(oldIndex + 1, this.backForwardList.length - oldIndex); + this.currentBackForwardIndex++; + this.backForwardList.push(panel); + } + + this.currentPanel = panel; + if (panel && view) + panel.currentView = view; +} + +WebInspector.UIString = function(string) +{ + if (string in this.localizedStrings) + string = this.localizedStrings[string]; + else { + if (!(string in this.missingLocalizedStrings)) { + console.error("Localized string \"" + string + "\" not found."); + this.missingLocalizedStrings[string] = true; + } + + if (Preferences.showMissingLocalizedStrings) + string += " (not localized)"; + } + + return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); +} + +WebInspector.StatusTreeElement = function(title, iconClass, panel) +{ + TreeElement.call(this, "<span class=\"title only\">" + title + "</span><span class=\"icon " + iconClass + "\"></span>", null, false); + this.panel = panel; +} + +WebInspector.StatusTreeElement.prototype = { + onselect: function() + { + var selectedElement = WebInspector.fileOutline.selectedTreeElement; + if (selectedElement) + selectedElement.deselect(); + if (this.panel) + WebInspector.navigateToPanel(this.panel); + }, + + ondeselect: function() + { + if (this.panel) + this.panel.hide(); + } +} + +WebInspector.StatusTreeElement.prototype.__proto__ = TreeElement.prototype; + +WebInspector.ConsoleStatusTreeElement = function(panel) +{ + WebInspector.StatusTreeElement.call(this, WebInspector.UIString("Console"), "console", panel); +} + +WebInspector.ConsoleStatusTreeElement.prototype = { + get warnings() + { + if (!("_warnings" in this)) + this._warnings = 0; + + return this._warnings; + }, + + set warnings(x) + { + if (this._warnings === x) + return; + + this._warnings = x; + + this._updateTitle(); + }, + + get errors() + { + if (!("_errors" in this)) + this._errors = 0; + + return this._errors; + }, + + set errors(x) + { + if (this._errors === x) + return; + + this._errors = x; + + this._updateTitle(); + }, + + _updateTitle: function() + { + var title = "<span class=\"title"; + if (!this.warnings && !this.errors) + title += " only"; + title += "\">" + WebInspector.UIString("Console") + "</span><span class=\"icon console\"></span>"; + + if (this.warnings || this.errors) { + title += "<span class=\"info\">"; + if (this.errors) { + title += this.errors + " error"; + if (this.errors > 1) + title += "s"; + } + if (this.warnings) { + if (this.errors) + title += ", "; + title += this.warnings + " warning"; + if (this.warnings > 1) + title += "s"; + } + title += "</span>"; + } + + this.title = title; + } +} + +WebInspector.ConsoleStatusTreeElement.prototype.__proto__ = WebInspector.StatusTreeElement.prototype; + +// This table maps MIME types to the Resource.Types which are valid for them. +// The following line: +// "text/html": {0: 1}, +// means that text/html is a valid MIME type for resources that have type +// WebInspector.Resource.Type.Document (which has a value of 0). +WebInspector.MIMETypes = { + "text/html": {0: true}, + "text/xml": {0: true}, + "text/plain": {0: true}, + "application/xhtml+xml": {0: true}, + "text/css": {1: true}, + "text/xsl": {1: true}, + "image/jpeg": {2: true}, + "image/png": {2: true}, + "image/gif": {2: true}, + "image/bmp": {2: true}, + "image/x-icon": {2: true}, + "image/x-xbitmap": {2: true}, + "font/ttf": {3: true}, + "font/opentype": {3: true}, + "application/x-font-type1": {3: true}, + "application/x-font-ttf": {3: true}, + "application/x-truetype-font": {3: true}, + "text/javascript": {4: true}, + "text/ecmascript": {4: true}, + "application/javascript": {4: true}, + "application/ecmascript": {4: true}, + "application/x-javascript": {4: true}, + "text/javascript1.1": {4: true}, + "text/javascript1.2": {4: true}, + "text/javascript1.3": {4: true}, + "text/jscript": {4: true}, + "text/livescript": {4: true}, +} diff --git a/WebCore/page/inspector/treeoutline.js b/WebCore/page/inspector/treeoutline.js new file mode 100644 index 0000000..228136a --- /dev/null +++ b/WebCore/page/inspector/treeoutline.js @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +function TreeOutline(listNode) +{ + this.children = []; + this.selectedTreeElement = null; + this._childrenListNode = listNode; + this._childrenListNode.removeChildren(); + this._knownTreeElements = []; + this._treeElementsExpandedState = []; + this.expandTreeElementsWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.selected = false; + this.treeOutline = this; +} + +TreeOutline._knownTreeElementNextIdentifier = 1; + +TreeOutline._appendChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + var lastChild = this.children[this.children.length - 1]; + if (lastChild) { + lastChild.nextSibling = child; + child.previousSibling = lastChild; + } else { + child.previousSibling = null; + child.nextSibling = null; + } + + this.children.push(child); + this.hasChildren = true; + child.parent = this; + child.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) + child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + } + + child._attach(); +} + +TreeOutline._insertChild = function(child, index) +{ + if (!child) + throw("child can't be undefined or null"); + + var previousChild = (index > 0 ? this.children[index - 1] : null); + if (previousChild) { + previousChild.nextSibling = child; + child.previousSibling = previousChild; + } else { + child.previousSibling = null; + } + + var nextChild = this.children[index]; + if (nextChild) { + nextChild.previousSibling = child; + child.nextSibling = nextChild; + } else { + child.nextSibling = null; + } + + this.children.splice(index, 0, child); + this.hasChildren = true; + child.parent = this; + child.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) + child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + } + + child._attach(); +} + +TreeOutline._removeChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + for (var i = 0; i < this.children.length; ++i) { + if (this.children[i] === child) { + this.children.splice(i, 1); + break; + } + } + + child.deselect(); + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + if (child.treeOutline) + child.treeOutline._forgetTreeElement(child); + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; +} + +TreeOutline._removeChildren = function() +{ + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + if (child.treeOutline) + child.treeOutline._forgetTreeElement(child); + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; + + if (this._childrenListNode) + this._childrenListNode.offsetTop; // force layout +} + +TreeOutline._removeChildrenRecursive = function() +{ + var childrenToRemove = this.children; + + var child = this.children[0]; + while (child) { + if (child.children.length) + childrenToRemove = childrenToRemove.concat(child.children); + child = child.traverseNextTreeElement(false, this, true); + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + var child = childrenToRemove[i]; + child.deselect(); + if (child.treeOutline) + child.treeOutline._forgetTreeElement(child); + child._detach(); + child.children = []; + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +TreeOutline.prototype._rememberTreeElement = function(element) +{ + if (!this._knownTreeElements[element.identifier]) + this._knownTreeElements[element.identifier] = []; + + // check if the element is already known + var elements = this._knownTreeElements[element.identifier]; + for (var i = 0; i < elements.length; ++i) + if (elements[i] === element) + return; + + // add the element + elements.push(element); +} + +TreeOutline.prototype._forgetTreeElement = function(element) +{ + if (!this._knownTreeElements[element.identifier]) + return; + + var elements = this._knownTreeElements[element.identifier]; + for (var i = 0; i < elements.length; ++i) { + if (elements[i] === element) { + elements.splice(i, 1); + break; + } + } +} + +TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) +{ + if (!representedObject) + return null; + + if ("__treeElementIdentifier" in representedObject) { + var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; + if (elements) { + for (var i = 0; i < elements.length; ++i) + if (elements[i].representedObject === representedObject) + return elements[i]; + } + } + + if (!isAncestor || !(isAncestor instanceof Function) || !getParent || !(getParent instanceof Function)) + return null; + + var item; + var found = false; + for (var i = 0; i < this.children.length; ++i) { + item = this.children[i]; + if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) { + found = true; + break; + } + } + + if (!found) + return null; + + var ancestors = []; + var currentObject = representedObject; + while (currentObject) { + ancestors.unshift(currentObject); + if (currentObject === item.representedObject) + break; + currentObject = getParent(currentObject); + } + + for (var i = 0; i < ancestors.length; ++i) { + item = this.findTreeElement(ancestors[i], isAncestor, getParent); + if (ancestors[i] !== representedObject && item && item.onpopulate) + item.onpopulate(item); + } + + return item; +} + +TreeOutline.prototype.handleKeyEvent = function(event) +{ + if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) + return false; + + var handled = false; + var nextSelectedElement; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedTreeElement.expanded) { + if (event.altKey) + this.selectedTreeElement.collapseRecursively(); + else + this.selectedTreeElement.collapse(); + handled = true; + } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { + handled = true; + if (this.selectedTreeElement.parent.selectable) { + nextSelectedElement = this.selectedTreeElement.parent; + handled = nextSelectedElement ? true : false; + } else if (this.selectedTreeElement.parent) + this.selectedTreeElement.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedTreeElement.revealed()) { + this.selectedTreeElement.reveal(); + handled = true; + } else if (this.selectedTreeElement.hasChildren) { + handled = true; + if (this.selectedTreeElement.expanded) { + nextSelectedElement = this.selectedTreeElement.children[0]; + handled = nextSelectedElement ? true : false; + } else { + if (event.altKey) + this.selectedTreeElement.expandRecursively(); + else + this.selectedTreeElement.expand(); + } + } + } + + if (nextSelectedElement) { + nextSelectedElement.reveal(); + nextSelectedElement.select(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + + return handled; +} + +TreeOutline.prototype.expand = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.collapse = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.revealed = function() +{ + return true; +} + +TreeOutline.prototype.reveal = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.appendChild = TreeOutline._appendChild; +TreeOutline.prototype.insertChild = TreeOutline._insertChild; +TreeOutline.prototype.removeChild = TreeOutline._removeChild; +TreeOutline.prototype.removeChildren = TreeOutline._removeChildren; +TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; + +function TreeElement(title, representedObject, hasChildren) +{ + this._title = title; + this.representedObject = (representedObject || {}); + + if (this.representedObject.__treeElementIdentifier) + this.identifier = this.representedObject.__treeElementIdentifier; + else { + this.identifier = TreeOutline._knownTreeElementNextIdentifier++; + this.representedObject.__treeElementIdentifier = this.identifier; + } + + this._hidden = false; + this.expanded = false; + this.selected = false; + this.hasChildren = hasChildren; + this.children = []; + this.treeOutline = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this._listItemNode = null; +} + +TreeElement.prototype = { + selectable: true, + arrowToggleWidth: 10, + + get listItemElement() { + return this._listItemNode; + }, + + get childrenListElement() { + return this._childrenListNode; + }, + + get title() { + return this._title; + }, + + set title(x) { + this._title = x; + if (this._listItemNode) + this._listItemNode.innerHTML = x; + }, + + get tooltip() { + return this._tooltip; + }, + + set tooltip(x) { + this._tooltip = x; + if (this._listItemNode) + this._listItemNode.title = x ? x : ""; + }, + + get hidden() { + return this._hidden; + }, + + set hidden(x) { + if (this._hidden === x) + return; + + this._hidden = x; + + if (x) { + if (this._listItemNode) + this._listItemNode.addStyleClass("hidden"); + if (this._childrenListNode) + this._childrenListNode.addStyleClass("hidden"); + } else { + if (this._listItemNode) + this._listItemNode.removeStyleClass("hidden"); + if (this._childrenListNode) + this._childrenListNode.removeStyleClass("hidden"); + } + } +} + +TreeElement.prototype.appendChild = TreeOutline._appendChild; +TreeElement.prototype.insertChild = TreeOutline._insertChild; +TreeElement.prototype.removeChild = TreeOutline._removeChild; +TreeElement.prototype.removeChildren = TreeOutline._removeChildren; +TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; + +TreeElement.prototype._attach = function() +{ + if (!this._listItemNode || this.parent.refreshChildren) { + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + + this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); + this._listItemNode.treeElement = this; + this._listItemNode.innerHTML = this._title; + this._listItemNode.title = this._tooltip ? this._tooltip : ""; + + if (this.hidden) + this._listItemNode.addStyleClass("hidden"); + if (this.hasChildren) + this._listItemNode.addStyleClass("parent"); + if (this.expanded) + this._listItemNode.addStyleClass("expanded"); + if (this.selected) + this._listItemNode.addStyleClass("selected"); + + this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false); + this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); + this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); + + if (this.onattach) + this.onattach(this); + } + + this.parent._childrenListNode.insertBefore(this._listItemNode, (this.nextSibling ? this.nextSibling._listItemNode : null)); + if (this._childrenListNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + if (this.selected) + this.select(); + if (this.expanded) + this.expand(); +} + +TreeElement.prototype._detach = function() +{ + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); +} + +TreeElement.treeElementSelected = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement || !element.treeElement.selectable) + return; + + if (event.offsetX > element.treeElement.arrowToggleWidth || !element.treeElement.hasChildren) + element.treeElement.select(); +} + +TreeElement.treeElementToggled = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (event.offsetX <= element.treeElement.arrowToggleWidth && element.treeElement.hasChildren) { + if (element.treeElement.expanded) { + if (event.altKey) + element.treeElement.collapseRecursively(); + else + element.treeElement.collapse(); + } else { + if (event.altKey) + element.treeElement.expandRecursively(); + else + element.treeElement.expand(); + } + } +} + +TreeElement.treeElementDoubleClicked = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (element.treeElement.ondblclick) + element.treeElement.ondblclick(element.treeElement, event); + else if (element.treeElement.hasChildren && !element.treeElement.expanded) + element.treeElement.expand(); +} + +TreeElement.prototype.collapse = function() +{ + if (this._listItemNode) + this._listItemNode.removeStyleClass("expanded"); + if (this._childrenListNode) + this._childrenListNode.removeStyleClass("expanded"); + + this.expanded = false; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = true; + + if (this.oncollapse) + this.oncollapse(this); +} + +TreeElement.prototype.collapseRecursively = function() +{ + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextTreeElement(false, this, true); + } +} + +TreeElement.prototype.expand = function() +{ + if (!this.hasChildren || (this.expanded && !this.refreshChildren && this._childrenListNode)) + return; + + if (!this._childrenListNode || this.refreshChildren) { + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + + if (this.onpopulate) + this.onpopulate(this); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + + delete this.refreshChildren; + } + + if (this._listItemNode) { + this._listItemNode.addStyleClass("expanded"); + if (this._childrenListNode.parentNode != this._listItemNode.parentNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + } + + if (this._childrenListNode) + this._childrenListNode.addStyleClass("expanded"); + + this.expanded = true; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = true; + + if (this.onexpand) + this.onexpand(this); +} + +TreeElement.prototype.expandRecursively = function() +{ + var item = this; + while (item) { + item.expand(); + item = item.traverseNextTreeElement(false, this); + } +} + +TreeElement.prototype.reveal = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + if (this.onreveal) + this.onreveal(this); +} + +TreeElement.prototype.revealed = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + return false; + currentAncestor = currentAncestor.parent; + } + + return true; +} + +TreeElement.prototype.select = function(supressOnSelect) +{ + if (!this.treeOutline || !this.selectable || this.selected) + return; + + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.deselect(); + + this.selected = true; + this.treeOutline.selectedTreeElement = this; + if (this._listItemNode) + this._listItemNode.addStyleClass("selected"); + + if (this.onselect && !supressOnSelect) + this.onselect(this); +} + +TreeElement.prototype.deselect = function(supressOnDeselect) +{ + if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) + return; + + this.selected = false; + this.treeOutline.selectedTreeElement = null; + if (this._listItemNode) + this._listItemNode.removeStyleClass("selected"); + + if (this.ondeselect && !supressOnDeselect) + this.ondeselect(this); +} + +TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate) +{ + if (!dontPopulate && this.hasChildren && this.onpopulate) + this.onpopulate(this); + + var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0]; + if (element && (!skipHidden || (skipHidden && this.expanded))) + return element; + + if (this === stayWithin) + return null; + + element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; + if (element) + return element; + + element = this; + while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) + element = element.parent; + + if (!element) + return null; + + return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); +} + +TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate) +{ + var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; + if (!dontPopulate && element && element.hasChildren && element.onpopulate) + element.onpopulate(element); + + while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { + if (!dontPopulate && element.hasChildren && element.onpopulate) + element.onpopulate(element); + element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); + } + + if (element) + return element; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; +} diff --git a/WebCore/page/inspector/utilities.js b/WebCore/page/inspector/utilities.js new file mode 100644 index 0000000..47d721f --- /dev/null +++ b/WebCore/page/inspector/utilities.js @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +Object.type = function(obj) +{ + if (obj === null) + return "null"; + + var type = typeof obj; + if (type !== "object" && type !== "function") + return type; + + if (obj instanceof String) + return "string"; + if (obj instanceof Array) + return "array"; + if (obj instanceof Boolean) + return "boolean"; + if (obj instanceof Number) + return "number"; + if (obj instanceof Date) + return "date"; + if (obj instanceof RegExp) + return "regexp"; + if (obj instanceof Error) + return "error"; + return type; +} + +Object.describe = function(obj, abbreviated) +{ + switch (Object.type(obj)) { + case "object": + return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1"); + case "array": + return "[" + obj.toString() + "]"; + case "string": + if (obj.length > 100) + return "\"" + obj.substring(0, 100) + "\u2026\""; + return "\"" + obj + "\""; + case "function": + var objectText = String(obj); + if (!/^function /.test(objectText)) + objectText = (type2 == "object") ? type1 : type2; + else if (abbreviated) + objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); + return objectText; + case "regexp": + return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); + default: + return String(obj); + } +} + +Object.sortedProperties = function(obj) +{ + var properties = []; + for (var prop in obj) + properties.push(prop); + properties.sort(); + return properties; +} + +Function.prototype.bind = function(thisObject) +{ + var func = this; + var args = Array.prototype.slice.call(arguments, 1); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) }; +} + +Element.prototype.removeStyleClass = function(className) +{ + // Test for the simple case before using a RegExp. + if (this.className === className) { + this.className = ""; + return; + } + + var regex = new RegExp("(^|\\s+)" + className.escapeForRegExp() + "($|\\s+)"); + if (regex.test(this.className)) + this.className = this.className.replace(regex, " "); +} + +Element.prototype.addStyleClass = function(className) +{ + if (className && !this.hasStyleClass(className)) + this.className += (this.className.length ? " " + className : className); +} + +Element.prototype.hasStyleClass = function(className) +{ + if (!className) + return false; + // Test for the simple case before using a RegExp. + if (this.className === className) + return true; + var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)"); + return regex.test(this.className); +} + +Node.prototype.firstParentOrSelfWithNodeName = function(nodeName) +{ + for (var node = this; node && (node !== document); node = node.parentNode) + if (node.nodeName.toLowerCase() === nodeName.toLowerCase()) + return node; + return null; +} + +Node.prototype.firstParentOrSelfWithClass = function(className) +{ + for (var node = this; node && (node !== document); node = node.parentNode) + if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className)) + return node; + return null; +} + +Node.prototype.firstParentWithClass = function(className) +{ + if (!this.parentNode) + return null; + return this.parentNode.firstParentOrSelfWithClass(className); +} + +Element.prototype.query = function(query) +{ + return document.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +Element.prototype.removeChildren = function() +{ + while (this.firstChild) + this.removeChild(this.firstChild); +} + +Element.prototype.firstChildSkippingWhitespace = firstChildSkippingWhitespace; +Element.prototype.lastChildSkippingWhitespace = lastChildSkippingWhitespace; + +Node.prototype.isWhitespace = isNodeWhitespace; +Node.prototype.nodeTypeName = nodeTypeName; +Node.prototype.displayName = nodeDisplayName; +Node.prototype.contentPreview = nodeContentPreview; +Node.prototype.isAncestor = isAncestorNode; +Node.prototype.isDescendant = isDescendantNode; +Node.prototype.firstCommonAncestor = firstCommonNodeAncestor; +Node.prototype.nextSiblingSkippingWhitespace = nextSiblingSkippingWhitespace; +Node.prototype.previousSiblingSkippingWhitespace = previousSiblingSkippingWhitespace; +Node.prototype.traverseNextNode = traverseNextNode; +Node.prototype.traversePreviousNode = traversePreviousNode; +Node.prototype.onlyTextChild = onlyTextChild; +Node.prototype.titleInfo = nodeTitleInfo; + +String.prototype.hasSubstring = function(string, caseInsensitive) +{ + if (!caseInsensitive) + return this.indexOf(string) !== -1; + return this.match(new RegExp(string.escapeForRegExp(), "i")); +} + +String.prototype.escapeCharacters = function(chars) +{ + var foundChar = false; + for (var i = 0; i < chars.length; ++i) { + if (this.indexOf(chars.charAt(i)) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + for (var i = 0; i < this.length; ++i) { + if (chars.indexOf(this.charAt(i)) !== -1) + result += "\\"; + result += this.charAt(i); + } + + return result; +} + +String.prototype.escapeForRegExp = function() +{ + return this.escapeCharacters("^[]{}()\\.$*+?|"); +} + +String.prototype.escapeHTML = function() +{ + return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); +} + +String.prototype.collapseWhitespace = function() +{ + return this.replace(/[\s\xA0]+/g, " "); +} + +String.prototype.trimLeadingWhitespace = function() +{ + return this.replace(/^[\s\xA0]+/g, ""); +} + +String.prototype.trimTrailingWhitespace = function() +{ + return this.replace(/[\s\xA0]+$/g, ""); +} + +String.prototype.trimWhitespace = function() +{ + return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, ""); +} + +String.prototype.trimURL = function(baseURLDomain) +{ + var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), ""); + if (baseURLDomain) + result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); + return result; +} + +CSSStyleDeclaration.prototype.getShorthandValue = function(shorthandProperty) +{ + var value = this.getPropertyValue(shorthandProperty); + if (!value) { + // Some shorthands (like border) return a null value, so compute a shorthand value. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. + + var foundProperties = {}; + for (var i = 0; i < this.length; ++i) { + var individualProperty = this[i]; + if (individualProperty in foundProperties || this.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + + var individualValue = this.getPropertyValue(individualProperty); + if (this.isPropertyImplicit(individualProperty) || individualValue === "initial") + continue; + + foundProperties[individualProperty] = true; + + if (!value) + value = ""; + else if (value.length) + value += " "; + value += individualValue; + } + } + return value; +} + +CSSStyleDeclaration.prototype.getShorthandPriority = function(shorthandProperty) +{ + var priority = this.getPropertyPriority(shorthandProperty); + if (!priority) { + for (var i = 0; i < this.length; ++i) { + var individualProperty = this[i]; + if (this.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + priority = this.getPropertyPriority(individualProperty); + break; + } + } + return priority; +} + +CSSStyleDeclaration.prototype.getLonghandProperties = function(shorthandProperty) +{ + var properties = []; + var foundProperties = {}; + + for (var i = 0; i < this.length; ++i) { + var individualProperty = this[i]; + if (individualProperty in foundProperties || this.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + foundProperties[individualProperty] = true; + properties.push(individualProperty); + } + + return properties; +} + +CSSStyleDeclaration.prototype.getUniqueProperties = function() +{ + var properties = []; + var foundProperties = {}; + + for (var i = 0; i < this.length; ++i) { + var property = this[i]; + if (property in foundProperties) + continue; + foundProperties[property] = true; + properties.push(property); + } + + return properties; +} + +function isNodeWhitespace() +{ + if (!this || this.nodeType !== Node.TEXT_NODE) + return false; + if (!this.nodeValue.length) + return true; + return this.nodeValue.match(/^[\s\xA0]+$/); +} + +function nodeTypeName() +{ + if (!this) + return "(unknown)"; + + switch (this.nodeType) { + case Node.ELEMENT_NODE: return "Element"; + case Node.ATTRIBUTE_NODE: return "Attribute"; + case Node.TEXT_NODE: return "Text"; + case Node.CDATA_SECTION_NODE: return "Character Data"; + case Node.ENTITY_REFERENCE_NODE: return "Entity Reference"; + case Node.ENTITY_NODE: return "Entity"; + case Node.PROCESSING_INSTRUCTION_NODE: return "Processing Instruction"; + case Node.COMMENT_NODE: return "Comment"; + case Node.DOCUMENT_NODE: return "Document"; + case Node.DOCUMENT_TYPE_NODE: return "Document Type"; + case Node.DOCUMENT_FRAGMENT_NODE: return "Document Fragment"; + case Node.NOTATION_NODE: return "Notation"; + } + + return "(unknown)"; +} + +function nodeDisplayName() +{ + if (!this) + return ""; + + switch (this.nodeType) { + case Node.DOCUMENT_NODE: + return "Document"; + + case Node.ELEMENT_NODE: + var name = "<" + this.nodeName.toLowerCase(); + + if (this.hasAttributes()) { + var value = this.getAttribute("id"); + if (value) + name += " id=\"" + value + "\""; + value = this.getAttribute("class"); + if (value) + name += " class=\"" + value + "\""; + if (this.nodeName.toLowerCase() === "a") { + value = this.getAttribute("name"); + if (value) + name += " name=\"" + value + "\""; + value = this.getAttribute("href"); + if (value) + name += " href=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "img") { + value = this.getAttribute("src"); + if (value) + name += " src=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "iframe") { + value = this.getAttribute("src"); + if (value) + name += " src=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "input") { + value = this.getAttribute("name"); + if (value) + name += " name=\"" + value + "\""; + value = this.getAttribute("type"); + if (value) + name += " type=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "form") { + value = this.getAttribute("action"); + if (value) + name += " action=\"" + value + "\""; + } + } + + return name + ">"; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(this)) + return "(whitespace)"; + return "\"" + this.nodeValue + "\""; + + case Node.COMMENT_NODE: + return "<!--" + this.nodeValue + "-->"; + + case Node.DOCUMENT_TYPE_NODE: + var docType = "<!DOCTYPE " + this.nodeName; + if (this.publicId) { + docType += " PUBLIC \"" + this.publicId + "\""; + if (this.systemId) + docType += " \"" + this.systemId + "\""; + } else if (this.systemId) + docType += " SYSTEM \"" + this.systemId + "\""; + if (this.internalSubset) + docType += " [" + this.internalSubset + "]"; + return docType + ">"; + } + + return this.nodeName.toLowerCase().collapseWhitespace(); +} + +function nodeContentPreview() +{ + if (!this || !this.hasChildNodes || !this.hasChildNodes()) + return ""; + + var limit = 0; + var preview = ""; + + // always skip whitespace here + var currentNode = traverseNextNode.call(this, true, this); + while (currentNode) { + if (currentNode.nodeType === Node.TEXT_NODE) + preview += currentNode.nodeValue.escapeHTML(); + else + preview += nodeDisplayName.call(currentNode).escapeHTML(); + + currentNode = traverseNextNode.call(currentNode, true, this); + + if (++limit > 4) { + preview += "…"; // ellipsis + break; + } + } + + return preview.collapseWhitespace(); +} + +function isAncestorNode(ancestor) +{ + if (!this || !ancestor) + return false; + + var currentNode = ancestor.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + + return false; +} + +function isDescendantNode(descendant) +{ + return isAncestorNode.call(descendant, this); +} + +function firstCommonNodeAncestor(node) +{ + if (!this || !node) + return; + + var node1 = this.parentNode; + var node2 = node.parentNode; + + if ((!node1 || !node2) || node1 !== node2) + return null; + + while (node1 && node2) { + if (!node1.parentNode || !node2.parentNode) + break; + if (node1 !== node2) + break; + + node1 = node1.parentNode; + node2 = node2.parentNode; + } + + return node1; +} + +function nextSiblingSkippingWhitespace() +{ + if (!this) + return; + var node = this.nextSibling; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = node.nextSibling; + return node; +} + +function previousSiblingSkippingWhitespace() +{ + if (!this) + return; + var node = this.previousSibling; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = node.previousSibling; + return node; +} + +function firstChildSkippingWhitespace() +{ + if (!this) + return; + var node = this.firstChild; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = nextSiblingSkippingWhitespace.call(node); + return node; +} + +function lastChildSkippingWhitespace() +{ + if (!this) + return; + var node = this.lastChild; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = previousSiblingSkippingWhitespace.call(node); + return node; +} + +function traverseNextNode(skipWhitespace, stayWithin) +{ + if (!this) + return; + + var node = skipWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild; + if (node) + return node; + + if (stayWithin && this === stayWithin) + return null; + + node = skipWhitespace ? nextSiblingSkippingWhitespace.call(this) : this.nextSibling; + if (node) + return node; + + node = this; + while (node && !(skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling) && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) + node = node.parentNode; + if (!node) + return null; + + return skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling; +} + +function traversePreviousNode(skipWhitespace) +{ + if (!this) + return; + var node = skipWhitespace ? previousSiblingSkippingWhitespace.call(this) : this.previousSibling; + while (node && (skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild) ) + node = skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild; + if (node) + return node; + return this.parentNode; +} + +function onlyTextChild(ignoreWhitespace) +{ + if (!this) + return null; + + var firstChild = ignoreWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild; + if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE) + return null; + + var sibling = ignoreWhitespace ? nextSiblingSkippingWhitespace.call(firstChild) : firstChild.nextSibling; + return sibling ? null : firstChild; +} + +function nodeTitleInfo(hasChildren, linkify) +{ + var info = {title: "", hasChildren: hasChildren}; + + switch (this.nodeType) { + case Node.DOCUMENT_NODE: + info.title = "Document"; + break; + + case Node.ELEMENT_NODE: + info.title = "<span class=\"webkit-html-tag\"><" + this.nodeName.toLowerCase().escapeHTML(); + + if (this.hasAttributes()) { + for (var i = 0; i < this.attributes.length; ++i) { + var attr = this.attributes[i]; + var value = attr.value.escapeHTML(); + value = value.replace(/([\/;:\)\]\}])/g, "$1​"); + + info.title += " <span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=​"; + + if (linkify && (attr.name === "src" || attr.name === "href")) + info.title += linkify(attr.value, value, "webkit-html-attribute-value", this.nodeName.toLowerCase() == "a"); + else + info.title += "<span class=\"webkit-html-attribute-value\">\"" + value + "\"</span>"; + } + } + info.title += "></span>​"; + + // If this element only has a single child that is a text node, + // just show that text and the closing tag inline rather than + // create a subtree for them + + var textChild = onlyTextChild.call(this, Preferences.ignoreWhitespace); + var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength; + + if (showInlineText) { + info.title += textChild.nodeValue.escapeHTML() + "​<span class=\"webkit-html-tag\"></" + this.nodeName.toLowerCase().escapeHTML() + "></span>"; + info.hasChildren = false; + } + break; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(this)) + info.title = "(whitespace)"; + else + info.title = "\"" + this.nodeValue.escapeHTML() + "\""; + break + + case Node.COMMENT_NODE: + info.title = "<span class=\"webkit-html-comment\"><!--" + this.nodeValue.escapeHTML() + "--></span>"; + break; + + case Node.DOCUMENT_TYPE_NODE: + info.title = "<span class=\"webkit-html-tag\"><!DOCTYPE " + this.nodeName; + if (this.publicId) { + info.title += " PUBLIC \"" + this.publicId + "\""; + if (this.systemId) + info.title += " \"" + this.systemId + "\""; + } else if (this.systemId) + info.title += " SYSTEM \"" + this.systemId + "\""; + if (this.internalSubset) + info.title += " [" + this.internalSubset + "]"; + info.title += "></span>"; + break; + default: + info.title = this.nodeName.toLowerCase().collapseWhitespace().escapeHTML(); + } + + return info; +} + +Number.secondsToString = function(seconds) +{ + var ms = seconds * 1000; + if (ms < 1000) + return Math.round(ms) + "ms"; + + if (seconds < 60) + return (Math.round(seconds * 100) / 100) + "s"; + + var minutes = seconds / 60; + if (minutes < 60) + return (Math.round(minutes * 10) / 10) + "min"; + + var hours = minutes / 60; + if (hours < 24) + return (Math.round(hours * 10) / 10) + "hrs"; + + var days = hours / 24; + return (Math.round(days * 10) / 10) + " days"; +} + +Number.bytesToString = function(bytes) +{ + if (bytes < 1024) + return bytes + "B"; + + var kilobytes = bytes / 1024; + if (kilobytes < 1024) + return (Math.round(kilobytes * 100) / 100) + "KB"; + + var megabytes = kilobytes / 1024; + return (Math.round(megabytes * 1000) / 1000) + "MB"; +} + +Number.constrain = function(num, min, max) +{ + if (num < min) + num = min; + else if (num > max) + num = max; + return num; +} + +HTMLTextAreaElement.prototype.moveCursorToEnd = function() +{ + var length = this.value.length; + this.setSelectionRange(length, length); +} + +String.sprintf = function(format) +{ + return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); +} + +String.vsprintf = function(format, substitutions) +{ + if (!format || !substitutions || !substitutions.length) + return format; + + var result = ""; + var substitutionIndex = 0; + + var index = 0; + for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { + result += format.substring(index, precentIndex); + index = precentIndex + 1; + + if (format[index] === "%") { + result += "%"; + ++index; + continue; + } + + if (!isNaN(format[index])) { + // The first character is a number, it might be a substitution index. + var number = parseInt(format.substring(index)); + while (!isNaN(format[index])) + ++index; + // If the number is greater than zero and ends with a "$", + // then this is a substitution index. + if (number > 0 && format[index] === "$") { + substitutionIndex = (number - 1); + ++index; + } + } + + var precision = -1; + if (format[index] === ".") { + // This is a precision specifier. If no digit follows the ".", + // then the precision should be zero. + ++index; + precision = parseInt(format.substring(index)); + if (isNaN(precision)) + precision = 0; + while (!isNaN(format[index])) + ++index; + } + + if (substitutionIndex >= substitutions.length) { + // If there are not enough substitutions for the current substitutionIndex + // just output the format specifier literally and move on. + console.error("String.vsprintf(\"" + format + "\", \"" + substitutions.join("\", \"") + "\"): not enough substitution arguments. Had " + substitutions.length + " but needed " + (substitutionIndex + 1) + ", so substitution was skipped."); + index = precentIndex + 1; + result += "%"; + continue; + } + + switch (format[index]) { + case "d": + var substitution = parseInt(substitutions[substitutionIndex]); + result += (!isNaN(substitution) ? substitution : 0); + break; + case "f": + var substitution = parseFloat(substitutions[substitutionIndex]); + if (substitution && precision > -1) + substitution = substitution.toFixed(precision); + result += (!isNaN(substitution) ? substitution : (precision > -1 ? Number(0).toFixed(precision) : 0)); + break; + default: + // Encountered an unsupported format character, treat as a string. + console.warn("String.vsprintf(\"" + format + "\", \"" + substitutions.join("\", \"") + "\"): unsupported format character \u201C" + format[index] + "\u201D. Treating as a string."); + // Fall through to treat this like a string. + case "s": + result += substitutions[substitutionIndex]; + break; + } + + ++substitutionIndex; + ++index; + } + + result += format.substring(index); + + return result; +} diff --git a/WebCore/page/mac/AXObjectCacheMac.mm b/WebCore/page/mac/AXObjectCacheMac.mm new file mode 100644 index 0000000..61d0319 --- /dev/null +++ b/WebCore/page/mac/AXObjectCacheMac.mm @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "AXObjectCache.h" + +#import "Document.h" +#import "FoundationExtras.h" +#import "RenderObject.h" +#import "WebCoreAXObject.h" +#import "WebCoreViewFactory.h" + +// The simple Cocoa calls in this file don't throw exceptions. + +namespace WebCore { + +struct TextMarkerData { + AXID axID; + Node* node; + int offset; + EAffinity affinity; +}; + +bool AXObjectCache::gAccessibilityEnabled = false; + +AXObjectCache::~AXObjectCache() +{ + HashMap<RenderObject*, WebCoreAXObject*>::iterator end = m_objects.end(); + for (HashMap<RenderObject*, WebCoreAXObject*>::iterator it = m_objects.begin(); it != end; ++it) { + WebCoreAXObject* obj = (*it).second; + [obj detach]; + HardRelease(obj); + } +} + +WebCoreAXObject* AXObjectCache::get(RenderObject* renderer) +{ + WebCoreAXObject* obj = m_objects.get(renderer); + if (obj) + return obj; + + obj = [[WebCoreAXObject alloc] initWithRenderer:renderer]; + HardRetainWithNSRelease(obj); + m_objects.set(renderer, obj); + return obj; +} + +void AXObjectCache::remove(RenderObject* renderer) +{ + WebCoreAXObject* obj = m_objects.take(renderer); + if (!obj) + return; + [obj detach]; + HardRelease(obj); + + ASSERT(m_objects.size() >= m_idsInUse.size()); +} + +AXID AXObjectCache::getAXID(WebCoreAXObject* obj) +{ + // check for already-assigned ID + AXID objID = [obj axObjectID]; + if (objID) { + ASSERT(m_idsInUse.contains(objID)); + return objID; + } + + // generate a new ID + static AXID lastUsedID = 0; + objID = lastUsedID; + do + ++objID; + while (objID == 0 || objID == AXIDHashTraits::deletedValue() || m_idsInUse.contains(objID)); + m_idsInUse.add(objID); + lastUsedID = objID; + [obj setAXObjectID:objID]; + + return objID; +} + +void AXObjectCache::removeAXID(WebCoreAXObject* obj) +{ + AXID objID = [obj axObjectID]; + if (objID == 0) + return; + ASSERT(objID != AXIDHashTraits::deletedValue()); + ASSERT(m_idsInUse.contains(objID)); + [obj setAXObjectID:0]; + m_idsInUse.remove(objID); +} + +WebCoreTextMarker* AXObjectCache::textMarkerForVisiblePosition(const VisiblePosition& visiblePos) +{ + Position deepPos = visiblePos.deepEquivalent(); + Node* domNode = deepPos.node(); + ASSERT(domNode); + if (!domNode) + return nil; + + // locate the renderer, which must exist for a visible dom node + RenderObject* renderer = domNode->renderer(); + ASSERT(renderer); + + // find or create an accessibility object for this renderer + WebCoreAXObject* obj = get(renderer); + + // create a text marker, adding an ID for the WebCoreAXObject if needed + TextMarkerData textMarkerData; + textMarkerData.axID = getAXID(obj); + textMarkerData.node = domNode; + textMarkerData.offset = deepPos.offset(); + textMarkerData.affinity = visiblePos.affinity(); + return [[WebCoreViewFactory sharedFactory] textMarkerWithBytes:&textMarkerData length:sizeof(textMarkerData)]; +} + +VisiblePosition AXObjectCache::visiblePositionForTextMarker(WebCoreTextMarker* textMarker) +{ + TextMarkerData textMarkerData; + + if (![[WebCoreViewFactory sharedFactory] getBytes:&textMarkerData fromTextMarker:textMarker length:sizeof(textMarkerData)]) + return VisiblePosition(); + + // return empty position if the text marker is no longer valid + if (!m_idsInUse.contains(textMarkerData.axID)) + return VisiblePosition(); + + // generate a VisiblePosition from the data we stored earlier + VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity); + + // make sure the node and offset still match (catches stale markers). affinity is not critical for this. + Position deepPos = visiblePos.deepEquivalent(); + if (deepPos.node() != textMarkerData.node || deepPos.offset() != textMarkerData.offset) + return VisiblePosition(); + + return visiblePos; +} + +void AXObjectCache::childrenChanged(RenderObject* renderer) +{ + WebCoreAXObject* obj = m_objects.get(renderer); + if (obj) + [obj childrenChanged]; +} + +void AXObjectCache::postNotification(RenderObject* renderer, const String& message) +{ + if (!renderer) + return; + + // notifications for text input objects are sent to that object + // all others are sent to the top WebArea + WebCoreAXObject* obj = [get(renderer) observableObject]; + if (obj) + NSAccessibilityPostNotification(obj, message); + else + NSAccessibilityPostNotification(get(renderer->document()->renderer()), message); +} + +void AXObjectCache::postNotificationToElement(RenderObject* renderer, const String& message) +{ + // send the notification to the specified element itself, not one of its ancestors + if (renderer) + NSAccessibilityPostNotification(get(renderer), message); +} + +void AXObjectCache::handleFocusedUIElementChanged() +{ + [[WebCoreViewFactory sharedFactory] accessibilityHandleFocusChanged]; +} + +} diff --git a/WebCore/page/mac/ChromeMac.mm b/WebCore/page/mac/ChromeMac.mm new file mode 100644 index 0000000..9fc8107 --- /dev/null +++ b/WebCore/page/mac/ChromeMac.mm @@ -0,0 +1,55 @@ +// -*- mode: c++; c-basic-offset: 4 -*- +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#import "config.h" +#import "Chrome.h" + +#import "BlockExceptions.h" +#import "Frame.h" +#import "Page.h" + +namespace WebCore { + +void Chrome::focusNSView(NSView* view) +{ + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + WebCoreFrameBridge *bridge = m_page->mainFrame()->bridge(); + NSResponder *firstResponder = [bridge firstResponder]; + if (firstResponder == view) + return; + + if (![view window] || ![view superview] || ![view acceptsFirstResponder]) + return; + + [bridge makeFirstResponder:view]; + + // Setting focus can actually cause a style change which might + // remove the view from its superview while it's being made + // first responder. This confuses AppKit so we must restore + // the old first responder. + if (![view superview]) + [bridge makeFirstResponder:firstResponder]; + + END_BLOCK_OBJC_EXCEPTIONS; +} + +} // namespace WebCore + diff --git a/WebCore/page/mac/DragControllerMac.mm b/WebCore/page/mac/DragControllerMac.mm new file mode 100644 index 0000000..2ab9d41 --- /dev/null +++ b/WebCore/page/mac/DragControllerMac.mm @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "DragController.h" + +#import "DragData.h" +#import "Frame.h" +#import "FrameView.h" +#import "Page.h" + +namespace WebCore { + +const int DragController::LinkDragBorderInset = -2; + +const int DragController::MaxOriginalImageArea = 1500 * 1500; +const int DragController::DragIconRightInset = 7; +const int DragController::DragIconBottomInset = 3; + +const float DragController::DragImageAlpha = 0.75f; + +bool DragController::isCopyKeyDown() +{ + return [[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask; +} + +DragOperation DragController::dragOperation(DragData* dragData) +{ + ASSERT(dragData); + if ([NSApp modalWindow] || !dragData->containsURL()) + return DragOperationNone; + + if (!m_document || ![[m_page->mainFrame()->view()->getOuterView() window] attachedSheet] + && [dragData->platformData() draggingSource] != m_page->mainFrame()->view()->getOuterView()) + return DragOperationCopy; + + return DragOperationNone; +} + +const IntSize& DragController::maxDragImageSize() +{ + static const IntSize maxDragImageSize(400, 400); + + return maxDragImageSize; +} + +} diff --git a/WebCore/page/mac/EventHandlerMac.mm b/WebCore/page/mac/EventHandlerMac.mm new file mode 100644 index 0000000..5c2a979 --- /dev/null +++ b/WebCore/page/mac/EventHandlerMac.mm @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventHandler.h" + +#include "BlockExceptions.h" +#include "ClipboardMac.h" +#include "EventNames.h" +#include "FocusController.h" +#include "FrameLoader.h" +#include "Frame.h" +#include "FrameView.h" +#include "KeyboardEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformScrollBar.h" +#include "PlatformWheelEvent.h" +#include "RenderWidget.h" +#include "Settings.h" +#include "WebCoreFrameBridge.h" + +namespace WebCore { + +using namespace EventNames; + +static RetainPtr<NSEvent>& currentEvent() +{ + static RetainPtr<NSEvent> event; + return event; +} + +NSEvent *EventHandler::currentNSEvent() +{ + return currentEvent().get(); +} + +bool EventHandler::wheelEvent(NSEvent *event) +{ + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + + PlatformWheelEvent wheelEvent(event); + handleWheelEvent(wheelEvent); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + return wheelEvent.isAccepted(); +} + +PassRefPtr<KeyboardEvent> EventHandler::currentKeyboardEvent() const +{ + NSEvent *event = [NSApp currentEvent]; + if (!event) + return 0; + switch ([event type]) { + case NSKeyDown: { + PlatformKeyboardEvent platformEvent(event); + platformEvent.disambiguateKeyDownEvent(PlatformKeyboardEvent::RawKeyDown); + return new KeyboardEvent(platformEvent, m_frame->document() ? m_frame->document()->defaultView() : 0); + } + case NSKeyUp: + return new KeyboardEvent(event, m_frame->document() ? m_frame->document()->defaultView() : 0); + default: + return 0; + } +} + +static inline bool isKeyboardOptionTab(KeyboardEvent* event) +{ + return event + && (event->type() == keydownEvent || event->type() == keypressEvent) + && event->altKey() + && event->keyIdentifier() == "U+0009"; +} + +bool EventHandler::invertSenseOfTabsToLinks(KeyboardEvent* event) const +{ + return isKeyboardOptionTab(event); +} + +bool EventHandler::tabsToAllControls(KeyboardEvent* event) const +{ + KeyboardUIMode keyboardUIMode = [m_frame->bridge() keyboardUIMode]; + bool handlingOptionTab = isKeyboardOptionTab(event); + + // If tab-to-links is off, option-tab always highlights all controls + if ((keyboardUIMode & KeyboardAccessTabsToLinks) == 0 && handlingOptionTab) + return true; + + // If system preferences say to include all controls, we always include all controls + if (keyboardUIMode & KeyboardAccessFull) + return true; + + // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab. + if (keyboardUIMode & KeyboardAccessTabsToLinks) + return !handlingOptionTab; + + return handlingOptionTab; +} + +bool EventHandler::needsKeyboardEventDisambiguationQuirks() const +{ + static BOOL checkedSafari = NO; + static BOOL isSafari = NO; + + if (!checkedSafari) { + isSafari = [[[NSBundle mainBundle] bundleIdentifier] isEqualToString:@"com.apple.Safari"]; + checkedSafari = YES; + } + + Document* document = m_frame->document(); + if (!document) + return false; + + // RSS view needs arrow key keypress events. + if (isSafari && document->url().protocolIs("feed") || document->url().protocolIs("feeds")) + return true; + Settings* settings = m_frame->settings(); + if (!settings) + return false; + return settings->usesDashboardBackwardCompatibilityMode() || settings->needsKeyboardEventDisambiguationQuirks(); +} + +bool EventHandler::keyEvent(NSEvent *event) +{ + bool result; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp); + + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + + result = keyEvent(PlatformKeyboardEvent(event)); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + return result; + + END_BLOCK_OBJC_EXCEPTIONS; + + return false; +} + +void EventHandler::focusDocumentView() +{ + Page* page = m_frame->page(); + if (!page) + return; + + if (FrameView* frameView = m_frame->view()) + if (NSView *documentView = frameView->getDocumentView()) + page->chrome()->focusNSView(documentView); + + page->focusController()->setFocusedFrame(m_frame); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event) +{ + // Figure out which view to send the event to. + RenderObject* target = event.targetNode() ? event.targetNode()->renderer() : 0; + if (!target || !target->isWidget()) + return false; + + // Double-click events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will + // just pass currentEvent down to the widget, we don't want to call it for events that + // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as + // part of the pressed/released handling. + return passMouseDownEventToWidget(static_cast<RenderWidget*>(target)->widget()); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget) +{ + return passMouseDownEventToWidget(renderWidget->widget()); +} + +static bool lastEventIsMouseUp() +{ + // Many AK widgets run their own event loops and consume events while the mouse is down. + // When they finish, currentEvent is the mouseUp that they exited on. We need to update + // the khtml state with this mouseUp, which khtml never saw. This method lets us detect + // that state. + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent]; + if (currentEvent() != currentEventAfterHandlingMouseDown && + [currentEventAfterHandlingMouseDown type] == NSLeftMouseUp && + [currentEventAfterHandlingMouseDown timestamp] >= [currentEvent().get() timestamp]) + return true; + END_BLOCK_OBJC_EXCEPTIONS; + + return false; +} + +bool EventHandler::passMouseDownEventToWidget(Widget* widget) +{ + // FIXME: this method always returns true + + if (!widget) { + LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed"); + return true; + } + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + NSView *nodeView = widget->getView(); + ASSERT(nodeView); + ASSERT([nodeView superview]); + NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]]; + if (!view) { + // We probably hit the border of a RenderWidget + return true; + } + + if ([m_frame->bridge() firstResponder] != view) { + // Normally [NSWindow sendEvent:] handles setting the first responder. + // But in our case, the event was sent to the view representing the entire web page. + if ([currentEvent().get() clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) + [m_frame->bridge() makeFirstResponder:view]; + } + + // We need to "defer loading" while tracking the mouse, because tearing down the + // page while an AppKit control is tracking the mouse can cause a crash. + + // FIXME: In theory, WebCore now tolerates tear-down while tracking the + // mouse. We should confirm that, and then remove the deferrsLoading + // hack entirely. + + bool wasDeferringLoading = m_frame->page()->defersLoading(); + if (!wasDeferringLoading) + m_frame->page()->setDefersLoading(true); + + ASSERT(!m_sendingEventToSubview); + m_sendingEventToSubview = true; + [view mouseDown:currentEvent().get()]; + m_sendingEventToSubview = false; + + if (!wasDeferringLoading) + m_frame->page()->setDefersLoading(false); + + // Remember which view we sent the event to, so we can direct the release event properly. + m_mouseDownView = view; + m_mouseDownWasInSubframe = false; + + // Many AppKit widgets run their own event loops and consume events while the mouse is down. + // When they finish, currentEvent is the mouseUp that they exited on. We need to update + // the EventHandler state with this mouseUp, which we never saw. + // If this event isn't a mouseUp, we assume that the mouseUp will be coming later. There + // is a hole here if the widget consumes both the mouseUp and subsequent events. + if (lastEventIsMouseUp()) + m_mousePressed = false; + + END_BLOCK_OBJC_EXCEPTIONS; + + return true; +} + +// Note that this does the same kind of check as [target isDescendantOf:superview]. +// There are two differences: This is a lot slower because it has to walk the whole +// tree, and this works in cases where the target has already been deallocated. +static bool findViewInSubviews(NSView *superview, NSView *target) +{ + BEGIN_BLOCK_OBJC_EXCEPTIONS; + NSEnumerator *e = [[superview subviews] objectEnumerator]; + NSView *subview; + while ((subview = [e nextObject])) { + if (subview == target || findViewInSubviews(subview, target)) { + return true; + } + } + END_BLOCK_OBJC_EXCEPTIONS; + + return false; +} + +NSView *EventHandler::mouseDownViewIfStillGood() +{ + // Since we have no way of tracking the lifetime of m_mouseDownView, we have to assume that + // it could be deallocated already. We search for it in our subview tree; if we don't find + // it, we set it to nil. + NSView *mouseDownView = m_mouseDownView; + if (!mouseDownView) { + return nil; + } + FrameView* topFrameView = m_frame->view(); + NSView *topView = topFrameView ? topFrameView->getView() : nil; + if (!topView || !findViewInSubviews(topView, mouseDownView)) { + m_mouseDownView = nil; + return nil; + } + return mouseDownView; +} + +bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const +{ + return m_activationEventNumber == event.eventNumber(); +} + +bool EventHandler::eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&) +{ + NSView *view = mouseDownViewIfStillGood(); + + if (!view) + return false; + + if (!m_mouseDownWasInSubframe) { + m_sendingEventToSubview = true; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + [view mouseDragged:currentEvent().get()]; + END_BLOCK_OBJC_EXCEPTIONS; + m_sendingEventToSubview = false; + } + + return true; +} + +Clipboard* EventHandler::createDraggingClipboard() const +{ + NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + // Must be done before ondragstart adds types and data to the pboard, + // also done for security, as it erases data from the last drag + [pasteboard declareTypes:[NSArray array] owner:nil]; + return new ClipboardMac(true, pasteboard, ClipboardWritable, m_frame); +} + +bool EventHandler::eventLoopHandleMouseUp(const MouseEventWithHitTestResults&) +{ + NSView *view = mouseDownViewIfStillGood(); + if (!view) + return false; + + if (!m_mouseDownWasInSubframe) { + m_sendingEventToSubview = true; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + [view mouseUp:currentEvent().get()]; + END_BLOCK_OBJC_EXCEPTIONS; + m_sendingEventToSubview = false; + } + + return true; +} + +bool EventHandler::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframe, HitTestResult* hoveredNode) +{ + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + switch ([currentEvent().get() type]) { + case NSMouseMoved: + // Since we're passing in currentEvent() here, we can call + // handleMouseMoveEvent() directly, since the save/restore of + // currentEvent() that mouseMoved() does would have no effect. + subframe->eventHandler()->handleMouseMoveEvent(currentEvent().get(), hoveredNode); + return true; + + case NSLeftMouseDown: { + Node* node = event.targetNode(); + if (!node) + return false; + RenderObject* renderer = node->renderer(); + if (!renderer || !renderer->isWidget()) + return false; + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (!widget || !widget->isFrameView()) + return false; + if (!passWidgetMouseDownEventToWidget(static_cast<RenderWidget*>(renderer))) + return false; + m_mouseDownWasInSubframe = true; + return true; + } + case NSLeftMouseUp: { + if (!m_mouseDownWasInSubframe) + return false; + NSView *view = mouseDownViewIfStillGood(); + if (!view) + return false; + ASSERT(!m_sendingEventToSubview); + m_sendingEventToSubview = true; + [view mouseUp:currentEvent().get()]; + m_sendingEventToSubview = false; + return true; + } + case NSLeftMouseDragged: { + if (!m_mouseDownWasInSubframe) + return false; + NSView *view = mouseDownViewIfStillGood(); + if (!view) + return false; + ASSERT(!m_sendingEventToSubview); + m_sendingEventToSubview = true; + [view mouseDragged:currentEvent().get()]; + m_sendingEventToSubview = false; + return true; + } + default: + return false; + } + END_BLOCK_OBJC_EXCEPTIONS; + + return false; +} + +bool EventHandler::passWheelEventToWidget(PlatformWheelEvent&, Widget* widget) +{ + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + if ([currentEvent().get() type] != NSScrollWheel || m_sendingEventToSubview || !widget) + return false; + + NSView *nodeView = widget->getView(); + ASSERT(nodeView); + ASSERT([nodeView superview]); + NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[currentEvent().get() locationInWindow] fromView:nil]]; + if (!view) + // We probably hit the border of a RenderWidget + return false; + + m_sendingEventToSubview = true; + [view scrollWheel:currentEvent().get()]; + m_sendingEventToSubview = false; + return true; + + END_BLOCK_OBJC_EXCEPTIONS; + return false; +} + +void EventHandler::mouseDown(NSEvent *event) +{ + FrameView* v = m_frame->view(); + if (!v || m_sendingEventToSubview) + return; + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + m_frame->loader()->resetMultipleFormSubmissionProtection(); + + m_mouseDownView = nil; + + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + m_mouseDown = PlatformMouseEvent(event); + + handleMousePressEvent(event); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + END_BLOCK_OBJC_EXCEPTIONS; +} + +void EventHandler::mouseDragged(NSEvent *event) +{ + FrameView* v = m_frame->view(); + if (!v || m_sendingEventToSubview) + return; + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + + handleMouseMoveEvent(event); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + END_BLOCK_OBJC_EXCEPTIONS; +} + +void EventHandler::mouseUp(NSEvent *event) +{ + FrameView* v = m_frame->view(); + if (!v || m_sendingEventToSubview) + return; + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + + // Our behavior here is a little different that Qt. Qt always sends + // a mouse release event, even for a double click. To correct problems + // in khtml's DOM click event handling we do not send a release here + // for a double click. Instead we send that event from FrameView's + // handleMouseDoubleClickEvent. Note also that the third click of + // a triple click is treated as a single click, but the fourth is then + // treated as another double click. Hence the "% 2" below. + int clickCount = [event clickCount]; + if (clickCount > 0 && clickCount % 2 == 0) + handleMouseDoubleClickEvent(event); + else + handleMouseReleaseEvent(event); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + m_mouseDownView = nil; + + END_BLOCK_OBJC_EXCEPTIONS; +} + +/* + A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus + eats all subsequent events after it is starts its modal tracking loop. After the interaction + is done, this routine is used to fix things up. When a mouse down started us tracking in + the widget, we post a fake mouse up to balance the mouse down we started with. When a + key down started us tracking in the widget, we post a fake key up to balance things out. + In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to + be over after the tracking is done. + */ +void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent) +{ + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + m_sendingEventToSubview = false; + int eventType = [initiatingEvent type]; + if (eventType == NSLeftMouseDown || eventType == NSKeyDown) { + NSEvent *fakeEvent = nil; + if (eventType == NSLeftMouseDown) { + fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp + location:[initiatingEvent locationInWindow] + modifierFlags:[initiatingEvent modifierFlags] + timestamp:[initiatingEvent timestamp] + windowNumber:[initiatingEvent windowNumber] + context:[initiatingEvent context] + eventNumber:[initiatingEvent eventNumber] + clickCount:[initiatingEvent clickCount] + pressure:[initiatingEvent pressure]]; + + [NSApp postEvent:fakeEvent atStart:YES]; + } else { // eventType == NSKeyDown + fakeEvent = [NSEvent keyEventWithType:NSKeyUp + location:[initiatingEvent locationInWindow] + modifierFlags:[initiatingEvent modifierFlags] + timestamp:[initiatingEvent timestamp] + windowNumber:[initiatingEvent windowNumber] + context:[initiatingEvent context] + characters:[initiatingEvent characters] + charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers] + isARepeat:[initiatingEvent isARepeat] + keyCode:[initiatingEvent keyCode]]; + [NSApp postEvent:fakeEvent atStart:YES]; + } + // FIXME: We should really get the current modifierFlags here, but there's no way to poll + // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have + // no up-to-date cache of them anywhere. + fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved + location:[[m_frame->bridge() window] convertScreenToBase:[NSEvent mouseLocation]] + modifierFlags:[initiatingEvent modifierFlags] + timestamp:[initiatingEvent timestamp] + windowNumber:[initiatingEvent windowNumber] + context:[initiatingEvent context] + eventNumber:0 + clickCount:0 + pressure:0]; + [NSApp postEvent:fakeEvent atStart:YES]; + } + + END_BLOCK_OBJC_EXCEPTIONS; +} + +void EventHandler::mouseMoved(NSEvent *event) +{ + // Reject a mouse moved if the button is down - screws up tracking during autoscroll + // These happen because WebKit sometimes has to fake up moved events. + if (!m_frame->view() || m_mousePressed || m_sendingEventToSubview) + return; + + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + RetainPtr<NSEvent> oldCurrentEvent = currentEvent(); + currentEvent() = event; + + mouseMoved(PlatformMouseEvent(event)); + + ASSERT(currentEvent() == event); + currentEvent() = oldCurrentEvent; + + END_BLOCK_OBJC_EXCEPTIONS; +} + +bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + return passSubframeEventToSubframe(mev, subframe); +} + +bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode) +{ + return passSubframeEventToSubframe(mev, subframe, hoveredNode); +} + +bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + return passSubframeEventToSubframe(mev, subframe); +} + +bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults&, PlatformScrollbar* scrollbar) +{ + return passMouseDownEventToWidget(scrollbar); +} + +} diff --git a/WebCore/page/mac/FrameMac.mm b/WebCore/page/mac/FrameMac.mm new file mode 100644 index 0000000..6f6f096 --- /dev/null +++ b/WebCore/page/mac/FrameMac.mm @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2007 Trolltech ASA + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "Frame.h" + +#import "AXObjectCache.h" +#import "BeforeUnloadEvent.h" +#import "BlockExceptions.h" +#import "CSSHelper.h" +#import "Cache.h" +#import "Chrome.h" +#import "ClipboardEvent.h" +#import "ClipboardMac.h" +#import "ColorMac.h" +#import "Cursor.h" +#import "DOMInternal.h" +#import "DocumentLoader.h" +#import "EditCommand.h" +#import "EditorClient.h" +#import "Event.h" +#import "EventNames.h" +#import "FloatRect.h" +#import "FoundationExtras.h" +#import "FrameLoadRequest.h" +#import "FrameLoader.h" +#import "FrameLoaderClient.h" +#import "FrameLoaderTypes.h" +#import "FramePrivate.h" +#import "FrameView.h" +#import "GraphicsContext.h" +#import "HTMLDocument.h" +#import "HTMLFormElement.h" +#import "HTMLGenericFormElement.h" +#import "HTMLInputElement.h" +#import "HTMLNames.h" +#import "HTMLTableCellElement.h" +#import "HitTestRequest.h" +#import "HitTestResult.h" +#import "KeyboardEvent.h" +#import "Logging.h" +#import "MouseEventWithHitTestResults.h" +#import "Page.h" +#import "PlatformKeyboardEvent.h" +#import "PlatformScrollBar.h" +#import "PlatformWheelEvent.h" +#import "Plugin.h" +#import "RegularExpression.h" +#import "RenderImage.h" +#import "RenderListItem.h" +#import "RenderPart.h" +#import "RenderTableCell.h" +#import "RenderTheme.h" +#import "RenderView.h" +#import "ResourceHandle.h" +#import "Settings.h" +#import "SimpleFontData.h" +#import "SystemTime.h" +#import "TextResourceDecoder.h" +#import "UserStyleSheetLoader.h" +#import "WebCoreFrameBridge.h" +#import "WebCoreViewFactory.h" +#import "WebDashboardRegion.h" +#import "WebScriptObjectPrivate.h" +#import "kjs_proxy.h" +#import "kjs_window.h" +#import "visible_units.h" +#import <Carbon/Carbon.h> +#import <JavaScriptCore/NP_jsobject.h> +#import <JavaScriptCore/npruntime_impl.h> + +#undef _webcore_TIMING + +@interface NSObject (WebPlugIn) +- (id)objectForWebScript; +- (NPObject *)createPluginScriptableObject; +@end + +@interface NSView (WebCoreHTMLDocumentView) +- (void)drawSingleRect:(NSRect)rect; +@end + +using namespace std; +using namespace KJS::Bindings; + +using KJS::JSLock; + +namespace WebCore { + +using namespace EventNames; +using namespace HTMLNames; + +void Frame::setBridge(WebCoreFrameBridge* bridge) +{ + if (d->m_bridge == bridge) + return; + + if (!bridge) { + [d->m_bridge clearFrame]; + HardRelease(d->m_bridge); + d->m_bridge = nil; + return; + } + HardRetain(bridge); + HardRelease(d->m_bridge); + d->m_bridge = bridge; +} + +WebCoreFrameBridge* Frame::bridge() const +{ + return d->m_bridge; +} + +// Either get cached regexp or build one that matches any of the labels. +// The regexp we build is of the form: (STR1|STR2|STRN) +RegularExpression* regExpForLabels(NSArray* labels) +{ + // All the ObjC calls in this method are simple array and string + // calls which we can assume do not raise exceptions + + + // Parallel arrays that we use to cache regExps. In practice the number of expressions + // that the app will use is equal to the number of locales is used in searching. + static const unsigned int regExpCacheSize = 4; + static NSMutableArray* regExpLabels = nil; + static Vector<RegularExpression*> regExps; + static RegularExpression wordRegExp = RegularExpression("\\w"); + + RegularExpression* result; + if (!regExpLabels) + regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; + CFIndex cacheHit = [regExpLabels indexOfObject:labels]; + if (cacheHit != NSNotFound) + result = regExps.at(cacheHit); + else { + String pattern("("); + unsigned int numLabels = [labels count]; + unsigned int i; + for (i = 0; i < numLabels; i++) { + String label = [labels objectAtIndex:i]; + + bool startsWithWordChar = false; + bool endsWithWordChar = false; + if (label.length() != 0) { + startsWithWordChar = wordRegExp.search(label.substring(0, 1)) >= 0; + endsWithWordChar = wordRegExp.search(label.substring(label.length() - 1, 1)) >= 0; + } + + if (i != 0) + pattern.append("|"); + // Search for word boundaries only if label starts/ends with "word characters". + // If we always searched for word boundaries, this wouldn't work for languages + // such as Japanese. + if (startsWithWordChar) + pattern.append("\\b"); + pattern.append(label); + if (endsWithWordChar) + pattern.append("\\b"); + } + pattern.append(")"); + result = new RegularExpression(pattern, false); + } + + // add regexp to the cache, making sure it is at the front for LRU ordering + if (cacheHit != 0) { + if (cacheHit != NSNotFound) { + // remove from old spot + [regExpLabels removeObjectAtIndex:cacheHit]; + regExps.remove(cacheHit); + } + // add to start + [regExpLabels insertObject:labels atIndex:0]; + regExps.insert(0, result); + // trim if too big + if ([regExpLabels count] > regExpCacheSize) { + [regExpLabels removeObjectAtIndex:regExpCacheSize]; + RegularExpression* last = regExps.last(); + regExps.removeLast(); + delete last; + } + } + return result; +} + +NSString* Frame::searchForNSLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell) +{ + RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer()); + + if (cellRenderer && cellRenderer->isTableCell()) { + RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer); + + if (cellAboveRenderer) { + HTMLTableCellElement* aboveCell = + static_cast<HTMLTableCellElement*>(cellAboveRenderer->element()); + + if (aboveCell) { + // search within the above cell we found for a match + for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { + if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) + return nodeString.substring(pos, regExp->matchedLength()); + } + } + } + } + } + // Any reason in practice to search all cells in that are above cell? + return nil; +} + +NSString* Frame::searchForLabelsBeforeElement(NSArray* labels, Element* element) +{ + RegularExpression* regExp = regExpForLabels(labels); + // We stop searching after we've seen this many chars + const unsigned int charsSearchedThreshold = 500; + // This is the absolute max we search. We allow a little more slop than + // charsSearchedThreshold, to make it more likely that we'll search whole nodes. + const unsigned int maxCharsSearched = 600; + // If the starting element is within a table, the cell that contains it + HTMLTableCellElement* startingTableCell = 0; + bool searchedCellAbove = false; + + // walk backwards in the node tree, until another element, or form, or end of tree + int unsigned lengthSearched = 0; + Node* n; + for (n = element->traversePreviousNode(); + n && lengthSearched < charsSearchedThreshold; + n = n->traversePreviousNode()) + { + if (n->hasTagName(formTag) + || (n->isHTMLElement() + && static_cast<HTMLElement*>(n)->isGenericFormElement())) + { + // We hit another form element or the start of the form - bail out + break; + } else if (n->hasTagName(tdTag) && !startingTableCell) { + startingTableCell = static_cast<HTMLTableCellElement*>(n); + } else if (n->hasTagName(trTag) && startingTableCell) { + NSString* result = searchForLabelsAboveCell(regExp, startingTableCell); + if (result && [result length] > 0) + return result; + searchedCellAbove = true; + } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + // add 100 for slop, to make it more likely that we'll search whole nodes + if (lengthSearched + nodeString.length() > maxCharsSearched) + nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) + return nodeString.substring(pos, regExp->matchedLength()); + + lengthSearched += nodeString.length(); + } + } + + // If we started in a cell, but bailed because we found the start of the form or the + // previous element, we still might need to search the row above us for a label. + if (startingTableCell && !searchedCellAbove) { + NSString* result = searchForLabelsAboveCell(regExp, startingTableCell); + if (result && [result length] > 0) + return result; + } + + return nil; +} + +NSString* Frame::matchLabelsAgainstElement(NSArray* labels, Element* element) +{ + String name = element->getAttribute(nameAttr); + if (name.isEmpty()) + return nil; + + // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" + replace(name, RegularExpression("\\d"), " "); + name.replace('_', ' '); + + RegularExpression* regExp = regExpForLabels(labels); + // Use the largest match we can find in the whole name string + int pos; + int length; + int bestPos = -1; + int bestLength = -1; + int start = 0; + do { + pos = regExp->search(name, start); + if (pos != -1) { + length = regExp->matchedLength(); + if (length >= bestLength) { + bestPos = pos; + bestLength = length; + } + start = pos + 1; + } + } while (pos != -1); + + if (bestPos != -1) + return name.substring(bestPos, bestLength); + return nil; +} + +NSImage* Frame::imageFromRect(NSRect rect) const +{ + NSView* view = d->m_view->getDocumentView(); + if (!view) + return nil; + if (![view respondsToSelector:@selector(drawSingleRect:)]) + return nil; + + NSImage* resultImage; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + NSRect bounds = [view bounds]; + + // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794) + rect = [view convertRect:rect toView:nil]; + rect.size.height = roundf(rect.size.height); + rect.size.width = roundf(rect.size.width); + rect = [view convertRect:rect fromView:nil]; + + resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease]; + + if (rect.size.width != 0 && rect.size.height != 0) { + [resultImage setFlipped:YES]; + [resultImage lockFocus]; + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(context); + CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y); + + // Note: Must not call drawRect: here, because drawRect: assumes that it's called from AppKit's + // display machinery. It calls getRectsBeingDrawn:count:, which can only be called inside + // when a real AppKit display is underway. + [view drawSingleRect:rect]; + + CGContextRestoreGState(context); + [resultImage unlockFocus]; + [resultImage setFlipped:NO]; + } + + return resultImage; + + END_BLOCK_OBJC_EXCEPTIONS; + + return nil; +} + +NSImage* Frame::selectionImage(bool forceBlackText) const +{ + d->m_paintRestriction = forceBlackText ? PaintRestrictionSelectionOnlyBlackText : PaintRestrictionSelectionOnly; + d->m_doc->updateLayout(); + NSImage* result = imageFromRect(selectionRect()); + d->m_paintRestriction = PaintRestrictionNone; + return result; +} + +NSImage* Frame::snapshotDragImage(Node* node, NSRect* imageRect, NSRect* elementRect) const +{ + RenderObject* renderer = node->renderer(); + if (!renderer) + return nil; + + renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS) + d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might + // imply new styles, plus JS could have changed other things + IntRect topLevelRect; + NSRect paintingRect = renderer->paintingRootRect(topLevelRect); + + d->m_elementToDraw = node; // invoke special sub-tree drawing mode + NSImage* result = imageFromRect(paintingRect); + renderer->updateDragState(false); + d->m_doc->updateLayout(); + d->m_elementToDraw = 0; + + if (elementRect) + *elementRect = topLevelRect; + if (imageRect) + *imageRect = paintingRect; + return result; +} + +NSDictionary* Frame::fontAttributesForSelectionStart() const +{ + Node* nodeToRemove; + RenderStyle* style = styleForSelectionStart(nodeToRemove); + if (!style) + return nil; + + NSMutableDictionary* result = [NSMutableDictionary dictionary]; + + if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) + [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; + + if (style->font().primaryFont()->getNSFont()) + [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName]; + + if (style->color().isValid() && style->color() != Color::black) + [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; + + ShadowData* shadow = style->textShadow(); + if (shadow) { + NSShadow* s = [[NSShadow alloc] init]; + [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)]; + [s setShadowBlurRadius:shadow->blur]; + [s setShadowColor:nsColor(shadow->color)]; + [result setObject:s forKey:NSShadowAttributeName]; + } + + int decoration = style->textDecorationsInEffect(); + if (decoration & LINE_THROUGH) + [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; + + int superscriptInt = 0; + switch (style->verticalAlign()) { + case BASELINE: + case BOTTOM: + case BASELINE_MIDDLE: + case LENGTH: + case MIDDLE: + case TEXT_BOTTOM: + case TEXT_TOP: + case TOP: + break; + case SUB: + superscriptInt = -1; + break; + case SUPER: + superscriptInt = 1; + break; + } + if (superscriptInt) + [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName]; + + if (decoration & UNDERLINE) + [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; + + if (nodeToRemove) { + ExceptionCode ec = 0; + nodeToRemove->remove(ec); + ASSERT(ec == 0); + } + + return result; +} + +NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const +{ + NSWritingDirection result = NSWritingDirectionLeftToRight; + + Position pos = selectionController()->selection().visibleStart().deepEquivalent(); + Node* node = pos.node(); + if (!node || !node->renderer() || !node->renderer()->containingBlock()) + return result; + RenderStyle* style = node->renderer()->containingBlock()->style(); + if (!style) + return result; + + switch (style->direction()) { + case LTR: + result = NSWritingDirectionLeftToRight; + break; + case RTL: + result = NSWritingDirectionRightToLeft; + break; + } + + return result; +} + +void Frame::issuePasteCommand() +{ + [d->m_bridge issuePasteCommand]; +} + +const short enableRomanKeyboardsOnly = -23; +void Frame::setUseSecureKeyboardEntry(bool enable) +{ + if (enable == IsSecureEventInputEnabled()) + return; + if (enable) { + EnableSecureEventInput(); +#ifdef BUILDING_ON_TIGER + KeyScript(enableRomanKeyboardsOnly); +#else + CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); + TSMSetDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources); + CFRelease(inputSources); +#endif + } else { + DisableSecureEventInput(); +#ifdef BUILDING_ON_TIGER + KeyScript(smKeyEnableKybds); +#else + TSMRemoveDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag); +#endif + } +} + +NSMutableDictionary* Frame::dashboardRegionsDictionary() +{ + Document* doc = document(); + if (!doc) + return nil; + + const Vector<DashboardRegionValue>& regions = doc->dashboardRegions(); + size_t n = regions.size(); + + // Convert the Vector<DashboardRegionValue> into a NSDictionary of WebDashboardRegions + NSMutableDictionary* webRegions = [NSMutableDictionary dictionaryWithCapacity:n]; + for (size_t i = 0; i < n; i++) { + const DashboardRegionValue& region = regions[i]; + + if (region.type == StyleDashboardRegion::None) + continue; + + NSString *label = region.label; + WebDashboardRegionType type = WebDashboardRegionTypeNone; + if (region.type == StyleDashboardRegion::Circle) + type = WebDashboardRegionTypeCircle; + else if (region.type == StyleDashboardRegion::Rectangle) + type = WebDashboardRegionTypeRectangle; + NSMutableArray *regionValues = [webRegions objectForKey:label]; + if (!regionValues) { + regionValues = [[NSMutableArray alloc] initWithCapacity:1]; + [webRegions setObject:regionValues forKey:label]; + [regionValues release]; + } + + WebDashboardRegion *webRegion = [[WebDashboardRegion alloc] initWithRect:region.bounds clip:region.clip type:type]; + [regionValues addObject:webRegion]; + [webRegion release]; + } + + return webRegions; +} + +void Frame::dashboardRegionsChanged() +{ + NSMutableDictionary *webRegions = dashboardRegionsDictionary(); + [d->m_bridge dashboardRegionsChanged:webRegions]; +} + +void Frame::willPopupMenu(NSMenu * menu) +{ + [d->m_bridge willPopupMenu:menu]; +} + +FloatRect Frame::customHighlightLineRect(const AtomicString& type, const FloatRect& lineRect, Node* node) +{ + return [d->m_bridge customHighlightRect:type forLine:lineRect representedNode:node]; +} + +void Frame::paintCustomHighlight(const AtomicString& type, const FloatRect& boxRect, const FloatRect& lineRect, bool text, bool line, Node* node) +{ + [d->m_bridge paintCustomHighlight:type forBox:boxRect onLine:lineRect behindText:text entireLine:line representedNode:node]; +} + +DragImageRef Frame::dragImageForSelection() +{ + if (!selectionController()->isRange()) + return nil; + return selectionImage(); +} + + +KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(WebCore::Widget* widget) +{ + NSView* aView = widget->getView(); + if (!aView) + return 0; + + void* nativeHandle = aView; + CreateRootObjectFunction createRootObject = RootObject::createRootObject(); + RefPtr<RootObject> rootObject = createRootObject(nativeHandle); + + if ([aView respondsToSelector:@selector(objectForWebScript)]) { + id objectForWebScript = [aView objectForWebScript]; + if (objectForWebScript) + return Instance::createBindingForLanguageInstance(Instance::ObjectiveCLanguage, objectForWebScript, rootObject.release()); + return 0; + } else if ([aView respondsToSelector:@selector(createPluginScriptableObject)]) { +#if USE(NPOBJECT) + NPObject* npObject = [aView createPluginScriptableObject]; + if (npObject) { + Instance* instance = Instance::createBindingForLanguageInstance(Instance::CLanguage, npObject, rootObject.release()); + + // -createPluginScriptableObject returns a retained NPObject. The caller is expected to release it. + _NPN_ReleaseObject(npObject); + return instance; + } +#endif + return 0; + } + + jobject applet; + + // Get a pointer to the actual Java applet instance. + if ([d->m_bridge respondsToSelector:@selector(getAppletInView:)]) + applet = [d->m_bridge getAppletInView:aView]; + else + applet = [d->m_bridge pollForAppletInView:aView]; + + if (applet) { + // Wrap the Java instance in a language neutral binding and hand + // off ownership to the APPLET element. + Instance* instance = Instance::createBindingForLanguageInstance(Instance::JavaLanguage, applet, rootObject.release()); + return instance; + } + + return 0; +} + +WebScriptObject* Frame::windowScriptObject() +{ + if (!scriptProxy()->isEnabled()) + return 0; + + if (!d->m_windowScriptObject) { + KJS::JSLock lock; + KJS::JSObject* win = KJS::Window::retrieveWindow(this); + KJS::Bindings::RootObject *root = bindingRootObject(); + d->m_windowScriptObject = [WebScriptObject scriptObjectForJSObject:toRef(win) originRootObject:root rootObject:root]; + } + + return d->m_windowScriptObject.get(); +} + +void Frame::clearPlatformScriptObjects() +{ + if (d->m_windowScriptObject) { + KJS::Bindings::RootObject* root = bindingRootObject(); + [d->m_windowScriptObject.get() _setOriginRootObject:root andRootObject:root]; + } +} + +void Frame::setUserStyleSheetLocation(const KURL& url) +{ + delete d->m_userStyleSheetLoader; + d->m_userStyleSheetLoader = 0; + if (d->m_doc && d->m_doc->docLoader()) + d->m_userStyleSheetLoader = new UserStyleSheetLoader(d->m_doc, url.string()); +} + +void Frame::setUserStyleSheet(const String& styleSheet) +{ + delete d->m_userStyleSheetLoader; + d->m_userStyleSheetLoader = 0; + if (d->m_doc) + d->m_doc->setUserStyleSheet(styleSheet); +} + +} // namespace WebCore diff --git a/WebCore/page/mac/GlobalHistoryMac.mm b/WebCore/page/mac/GlobalHistoryMac.mm new file mode 100644 index 0000000..466f60e --- /dev/null +++ b/WebCore/page/mac/GlobalHistoryMac.mm @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "GlobalHistory.h" + +#import "WebCoreHistory.h" + +namespace WebCore { + +bool historyContains(const UChar* characters, unsigned length) +{ + // the other side of the bridge is careful not to throw exceptions here + return [[WebCoreHistory historyProvider] containsURL:characters length:length]; +} + +} // namespace WebCore diff --git a/WebCore/page/mac/WebCoreAXObject.h b/WebCore/page/mac/WebCoreAXObject.h new file mode 100644 index 0000000..2014ca6 --- /dev/null +++ b/WebCore/page/mac/WebCoreAXObject.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "AXObjectCache.h" + +namespace WebCore { + class HTMLAreaElement; + class RenderObject; +} + +@interface WebCoreAXObject : NSObject +{ + WebCore::RenderObject* m_renderer; + id m_data; + WebCore::HTMLAreaElement* m_areaElement; + NSMutableArray* m_children; + WebCore::AXID m_id; +} + +- (id)initWithRenderer:(WebCore::RenderObject*)renderer; + +- (BOOL)detached; +- (void)detach; + +- (id)data; +- (void)setData:(id)data; + +- (WebCore::AXID)axObjectID; +- (void)setAXObjectID:(WebCore::AXID)axObjectID; +- (void)removeAXObjectID; + +- (WebCoreAXObject*)firstChild; +- (WebCoreAXObject*)lastChild; +- (WebCoreAXObject*)previousSibling; +- (WebCoreAXObject*)nextSibling; +- (WebCoreAXObject*)parentObject; + +- (WebCoreAXObject*)observableObject; + +- (void)childrenChanged; +- (void)clearChildren; + +@end diff --git a/WebCore/page/mac/WebCoreAXObject.mm b/WebCore/page/mac/WebCoreAXObject.mm new file mode 100644 index 0000000..d514b0e --- /dev/null +++ b/WebCore/page/mac/WebCoreAXObject.mm @@ -0,0 +1,2781 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "WebCoreAXObject.h" + +#import "DOMInternal.h" +#import "ColorMac.h" +#import "Document.h" +#import "EventNames.h" +#import "FocusController.h" +#import "Frame.h" +#import "FrameLoader.h" +#import "FrameView.h" +#import "HTMLAreaElement.h" +#import "HTMLCollection.h" +#import "HTMLFrameElementBase.h" +#import "HTMLImageElement.h" +#import "HTMLInputElement.h" +#import "HTMLLabelElement.h" +#import "HTMLMapElement.h" +#import "HTMLNames.h" +#import "HTMLSelectElement.h" +#import "HTMLTextAreaElement.h" +#import "HitTestRequest.h" +#import "HitTestResult.h" +#import "LocalizedStrings.h" +#import "NodeList.h" +#import "Page.h" +#import "RenderImage.h" +#import "RenderListMarker.h" +#import "RenderMenuList.h" +#import "RenderTextControl.h" +#import "RenderTheme.h" +#import "RenderView.h" +#import "RenderWidget.h" +#import "SelectionController.h" +#import "SimpleFontData.h" +#import "TextIterator.h" +#import "WebCoreFrameBridge.h" +#import "WebCoreFrameView.h" +#import "WebCoreObjCExtras.h" +#import "WebCoreViewFactory.h" +#import "htmlediting.h" +#import "kjs_html.h" +#import "visible_units.h" +#include <mach-o/dyld.h> + +using namespace WebCore; +using namespace EventNames; +using namespace HTMLNames; + +@interface WebCoreAXObject (PrivateWebCoreAXObject) +// forward declarations as needed +- (WebCoreTextMarker*)textMarkerForIndex: (NSNumber*) index lastIndexOK: (BOOL)lastIndexOK; +- (id)doAXLineForTextMarker: (WebCoreTextMarker* ) textMarker; +@end + +@implementation WebCoreAXObject + +#ifndef BUILDING_ON_TIGER ++ (void)initialize +{ + WebCoreObjCFinalizeOnMainThread(self); +} +#endif + +-(id)initWithRenderer:(RenderObject*)renderer +{ + [super init]; + m_renderer = renderer; + return self; +} + +-(BOOL)detached +{ + return !m_renderer; +} + +-(void)detach +{ + // Send unregisterUniqueIdForUIElement unconditionally because if it is + // ever accidently not done (via other bugs in our AX implementation) you + // end up with a crash like <rdar://problem/4273149>. It is safe and not + // expensive to send even if the object is not registered. + [[WebCoreViewFactory sharedFactory] unregisterUniqueIdForUIElement:self]; + [m_data release]; + m_data = 0; + [self removeAXObjectID]; + m_renderer = 0; + [self clearChildren]; +} + +- (void)dealloc +{ + [self detach]; + [super dealloc]; +} + +- (void)finalize +{ + [self detach]; + [super finalize]; +} + +-(id)data +{ + return m_data; +} + +-(void)setData:(id)data +{ + if (!m_renderer) + return; + + [data retain]; + [m_data release]; + m_data = data; +} + +-(HTMLAnchorElement*)anchorElement +{ + // return already-known anchor for image areas + if (m_areaElement) + return m_areaElement; + + // search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. + RenderObject* currRenderer; + for (currRenderer = m_renderer; currRenderer && !currRenderer->element(); currRenderer = currRenderer->parent()) { + if (currRenderer->continuation()) + return [currRenderer->document()->axObjectCache()->get(currRenderer->continuation()) anchorElement]; + } + + // bail of none found + if (!currRenderer) + return 0; + + // search up the DOM tree for an anchor element + // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement + Node* elt = currRenderer->element(); + for ( ; elt; elt = elt->parentNode()) { + if (elt->isLink() && elt->renderer() && !elt->renderer()->isImage()) + return static_cast<HTMLAnchorElement*>(elt); + } + + return 0; +} + +-(BOOL)isImageButton +{ + return m_renderer->isImage() && m_renderer->element() && m_renderer->element()->hasTagName(inputTag); +} + +-(Element*)mouseButtonListener +{ + // FIXME: Do the continuation search like anchorElement does + for (EventTargetNode* elt = static_cast<EventTargetNode*>(m_renderer->element()); elt; elt = static_cast<EventTargetNode*>(elt->parentNode())) { + if (elt->getHTMLEventListener(clickEvent) || elt->getHTMLEventListener(mousedownEvent) || elt->getHTMLEventListener(mouseupEvent)) + return static_cast<Element*>(elt); + } + + return 0; +} + +-(Element*)actionElement +{ + if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); + if (!input->disabled() && (input->inputType() == HTMLInputElement::CHECKBOX || + input->inputType() == HTMLInputElement::RADIO || + input->isTextButton())) + return input; + } + + if ([self isImageButton] || m_renderer->isMenuList()) + return static_cast<Element*>(m_renderer->element()); + + Element* elt = [self anchorElement]; + if (!elt) + elt = [self mouseButtonListener]; + + return elt; +} + +-(WebCoreAXObject*)firstChild +{ + if (!m_renderer || !m_renderer->firstChild()) + return nil; + + return m_renderer->document()->axObjectCache()->get(m_renderer->firstChild()); +} + +-(WebCoreAXObject*)lastChild +{ + if (!m_renderer || !m_renderer->lastChild()) + return nil; + + return m_renderer->document()->axObjectCache()->get(m_renderer->lastChild()); +} + +-(WebCoreAXObject*)previousSibling +{ + if (!m_renderer || !m_renderer->previousSibling()) + return nil; + + return m_renderer->document()->axObjectCache()->get(m_renderer->previousSibling()); +} + +-(WebCoreAXObject*)nextSibling +{ + if (!m_renderer || !m_renderer->nextSibling()) + return nil; + + return m_renderer->document()->axObjectCache()->get(m_renderer->nextSibling()); +} + +-(WebCoreAXObject*)parentObject +{ + if (m_areaElement) + return m_renderer->document()->axObjectCache()->get(m_renderer); + + if (!m_renderer || !m_renderer->parent()) + return nil; + + return m_renderer->document()->axObjectCache()->get(m_renderer->parent()); +} + +-(WebCoreAXObject*)parentObjectUnignored +{ + WebCoreAXObject* obj = [self parentObject]; + if ([obj accessibilityIsIgnored]) + return [obj parentObjectUnignored]; + + return obj; +} + +-(void)addChildrenToArray:(NSMutableArray*)array +{ + // nothing to add if there is no RenderObject + if (!m_renderer) + return; + + // try to add RenderWidget's children, but fall thru if there are none + if (m_renderer->isWidget()) { + RenderWidget* renderWidget = static_cast<RenderWidget*>(m_renderer); + Widget* widget = renderWidget->widget(); + if (widget) { + NSArray* childArr = [(widget->getOuterView()) accessibilityAttributeValue: NSAccessibilityChildrenAttribute]; + [array addObjectsFromArray: childArr]; + return; + } + } + + // add all unignored acc children + for (WebCoreAXObject* obj = [self firstChild]; obj; obj = [obj nextSibling]) { + if ([obj accessibilityIsIgnored]) + [obj addChildrenToArray: array]; + else + [array addObject: obj]; + } + + // for a RenderImage, add the <area> elements as individual accessibility objects + if (m_renderer->isImage() && !m_areaElement) { + HTMLMapElement* map = static_cast<RenderImage*>(m_renderer)->imageMap(); + if (map) { + for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) { + // add an <area> element for this child if it has a link + // NOTE: can't cache these because they all have the same renderer, which is the cache key, right? + // plus there may be little reason to since they are being added to the handy array + if (current->isLink()) { + WebCoreAXObject* obj = [[[WebCoreAXObject alloc] initWithRenderer: m_renderer] autorelease]; + obj->m_areaElement = static_cast<HTMLAreaElement*>(current); + [array addObject: obj]; + } + } + } + } +} + +-(BOOL)isWebArea +{ + return m_renderer->isRenderView(); +} + +-(BOOL)isAnchor +{ + return m_areaElement || (!m_renderer->isImage() && m_renderer->element() && m_renderer->element()->isLink()); +} + +-(BOOL)isTextControl +{ + return m_renderer->isTextField() || m_renderer->isTextArea(); +} + +static bool isPasswordFieldElement(Node* node) +{ + if (!node || !node->hasTagName(inputTag)) + return false; + + HTMLInputElement* input = static_cast<HTMLInputElement*>(node); + return input->inputType() == HTMLInputElement::PASSWORD; +} + +-(BOOL)isPasswordField +{ + return m_renderer && isPasswordFieldElement(m_renderer->element()); +} + +-(BOOL)isAttachment +{ + // widgets are the replaced elements that we represent to AX as attachments + BOOL result = m_renderer && m_renderer->isWidget(); + + // assert that a widget is a replaced element that is not an image + ASSERT(!result || (m_renderer->isReplaced() && !m_renderer->isImage())); + return result; +} + +-(NSView*)attachmentView +{ + ASSERT(m_renderer->isReplaced() && m_renderer->isWidget() && !m_renderer->isImage()); + + RenderWidget* renderWidget = static_cast<RenderWidget*>(m_renderer); + Widget* widget = renderWidget->widget(); + if (widget) + return widget->getView(); + + return nil; +} + +static int blockquoteLevel(RenderObject* renderer) +{ + int result = 0; + for (Node* node = renderer->element(); node; node = node->parent()) { + if (node->hasTagName(blockquoteTag)) + result += 1; + } + + return result; +} + +static int headingLevel(RenderObject* renderer) +{ + if (!renderer->isBlockFlow()) + return 0; + + Node* node = renderer->element(); + if (!node) + return 0; + + if (node->hasTagName(h1Tag)) + return 1; + + if (node->hasTagName(h2Tag)) + return 2; + + if (node->hasTagName(h3Tag)) + return 3; + + if (node->hasTagName(h4Tag)) + return 4; + + if (node->hasTagName(h5Tag)) + return 5; + + if (node->hasTagName(h6Tag)) + return 6; + + return 0; +} + +-(int)headingLevel +{ + return headingLevel(m_renderer); +} + +-(BOOL)isHeading +{ + return [self headingLevel] != 0; +} + +-(NSString*)role +{ + if (!m_renderer) + return NSAccessibilityUnknownRole; + + if (m_areaElement) + return @"AXLink"; + if (m_renderer->element() && m_renderer->element()->isLink()) { + if (m_renderer->isImage()) + return @"AXImageMap"; + return @"AXLink"; + } + if (m_renderer->isListMarker()) + return @"AXListMarker"; + if (m_renderer->element() && m_renderer->element()->hasTagName(buttonTag)) + return NSAccessibilityButtonRole; + if (m_renderer->isText()) + return NSAccessibilityStaticTextRole; + if (m_renderer->isImage()) { + if ([self isImageButton]) + return NSAccessibilityButtonRole; + return NSAccessibilityImageRole; + } + if ([self isWebArea]) + return @"AXWebArea"; + + if (m_renderer->isTextField()) + return NSAccessibilityTextFieldRole; + + if (m_renderer->isTextArea()) + return NSAccessibilityTextAreaRole; + + if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); + if (input->inputType() == HTMLInputElement::CHECKBOX) + return NSAccessibilityCheckBoxRole; + if (input->inputType() == HTMLInputElement::RADIO) + return NSAccessibilityRadioButtonRole; + if (input->isTextButton()) + return NSAccessibilityButtonRole; + } + + if (m_renderer->isMenuList()) + return NSAccessibilityPopUpButtonRole; + + if ([self isHeading]) + return @"AXHeading"; + + if (m_renderer->isBlockFlow()) + return NSAccessibilityGroupRole; + if ([self isAttachment]) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute]; + + return NSAccessibilityUnknownRole; +} + +-(NSString*)subrole +{ + if ([self isPasswordField]) + return NSAccessibilitySecureTextFieldSubrole; + + if ([self isAttachment]) { + NSView* attachmentView = [self attachmentView]; + if ([[attachmentView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute]) { + return [attachmentView accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; + } + } + + return nil; +} + +-(NSString*)roleDescription +{ + if (!m_renderer) + return nil; + + // attachments have the AXImage role, but a different subrole + if ([self isAttachment]) + return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute]; + + // FIXME 3447564: It would be better to call some AppKit API to get these strings + // (which would be the best way to localize them) + + NSString* role = [self role]; + if ([role isEqualToString:NSAccessibilityButtonRole]) + return NSAccessibilityRoleDescription(NSAccessibilityButtonRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityPopUpButtonRole]) + return NSAccessibilityRoleDescription(NSAccessibilityPopUpButtonRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityStaticTextRole]) + return NSAccessibilityRoleDescription(NSAccessibilityStaticTextRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityImageRole]) + return NSAccessibilityRoleDescription(NSAccessibilityImageRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityGroupRole]) + return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityCheckBoxRole]) + return NSAccessibilityRoleDescription(NSAccessibilityCheckBoxRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityRadioButtonRole]) + return NSAccessibilityRoleDescription(NSAccessibilityRadioButtonRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityTextFieldRole]) + return NSAccessibilityRoleDescription(NSAccessibilityTextFieldRole, [self subrole]); + + if ([role isEqualToString:NSAccessibilityTextAreaRole]) + return NSAccessibilityRoleDescription(NSAccessibilityTextAreaRole, [self subrole]); + + if ([role isEqualToString:@"AXWebArea"]) + return AXWebAreaText(); + + if ([role isEqualToString:@"AXLink"]) + return AXLinkText(); + + if ([role isEqualToString:@"AXListMarker"]) + return AXListMarkerText(); + + if ([role isEqualToString:@"AXImageMap"]) + return AXImageMapText(); + + if ([role isEqualToString:@"AXHeading"]) + return AXHeadingText(); + + return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil); +} + +-(NSString*)helpText +{ + if (!m_renderer) + return nil; + + if (m_areaElement) { + const AtomicString& summary = static_cast<Element*>(m_areaElement)->getAttribute(summaryAttr); + if (!summary.isEmpty()) + return summary; + const AtomicString& title = static_cast<Element*>(m_areaElement)->getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + } + + for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { + if (curr->element() && curr->element()->isHTMLElement()) { + const AtomicString& summary = static_cast<Element*>(curr->element())->getAttribute(summaryAttr); + if (!summary.isEmpty()) + return summary; + const AtomicString& title = static_cast<Element*>(curr->element())->getAttribute(titleAttr); + if (!title.isEmpty()) + return title; + } + } + + return nil; +} + +-(NSString*)textUnderElement +{ + if (!m_renderer) + return nil; + + Node* e = m_renderer->element(); + Document* d = m_renderer->document(); + if (e && d) { + Frame* p = d->frame(); + if (p) { + // catch stale WebCoreAXObject (see <rdar://problem/3960196>) + if (p->document() != d) + return nil; + return plainText(rangeOfContents(e).get()); + } + } + + // return nil for anonymous text because it is non-trivial to get + // the actual text and, so far, that is not needed + return nil; +} + +-(id)value +{ + if (!m_renderer || m_areaElement || [self isPasswordField]) + return nil; + + if (m_renderer->isText()) + return [self textUnderElement]; + + if (m_renderer->isMenuList()) + return static_cast<RenderMenuList*>(m_renderer)->text(); + + if (m_renderer->isListMarker()) + return static_cast<RenderListMarker*>(m_renderer)->text(); + + if ([self isWebArea]) { + if (m_renderer->document()->frame()) + return nil; + + // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here + VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0); + VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX); + if (startVisiblePosition.isNull() || endVisiblePosition.isNull()) + return nil; + + return plainText(makeRange(startVisiblePosition, endVisiblePosition).get()); + } + + if ([self isAttachment]) { + NSView* attachmentView = [self attachmentView]; + if ([[attachmentView accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute]) + return [attachmentView accessibilityAttributeValue:NSAccessibilityValueAttribute]; + return nil; + } + + if ([self isHeading]) + return [NSNumber numberWithInt:[self headingLevel]]; + + if ([self isTextControl]) + return (NSString*)(static_cast<RenderTextControl*>(m_renderer)->text()); + + if (m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); + + // Checkboxes return their state as an integer. 0 for off, 1 for on. + if (input->inputType() == HTMLInputElement::CHECKBOX || + input->inputType() == HTMLInputElement::RADIO) + return [NSNumber numberWithInt:input->checked()]; + } + + // FIXME: We might need to implement a value here for more types + // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; + // this would require subclassing or making accessibilityAttributeNames do something other than return a + // single static array. + return nil; +} + +static HTMLLabelElement* labelForElement(Element* element) +{ + RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); + unsigned len = list->length(); + for (unsigned i = 0; i < len; i++) { + HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); + if (label->correspondingControl() == element) + return label; + } + + return 0; +} + +-(NSString*)title +{ + if (!m_renderer || m_areaElement || !m_renderer->element()) + return nil; + + if (m_renderer->element()->hasTagName(buttonTag)) + return [self textUnderElement]; + + if (m_renderer->element()->hasTagName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); + if (input->isTextButton()) + return input->value(); + + HTMLLabelElement* label = labelForElement(input); + if (label) + return label->innerText(); + } + + if (m_renderer->element()->isLink() || [self isHeading]) + return [self textUnderElement]; + + if ([self isAttachment]) { + NSView* attachmentView = [self attachmentView]; + if ([[attachmentView accessibilityAttributeNames] containsObject:NSAccessibilityTitleAttribute]) + return [attachmentView accessibilityAttributeValue:NSAccessibilityTitleAttribute]; + } + + return nil; +} + +- (NSString*)accessibilityDescription +{ + if (!m_renderer || m_areaElement) + return nil; + + if (m_renderer->isImage()) { + if (m_renderer->element() && m_renderer->element()->isHTMLElement()) { + const AtomicString& alt = static_cast<Element*>(m_renderer->element())->getAttribute(altAttr); + if (alt.isEmpty()) + return nil; + return alt; + } + } else if ([self isAttachment]) { + NSView* attachmentView = [self attachmentView]; + if ([[attachmentView accessibilityAttributeNames] containsObject:NSAccessibilityDescriptionAttribute]) + return [attachmentView accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]; + } + + if ([self isWebArea]) { + Document *document = m_renderer->document(); + Node* owner = document->ownerElement(); + if (owner) { + if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { + HTMLFrameElementBase* frameElement = static_cast<HTMLFrameElementBase*>(owner); + return frameElement->name(); + } else if (owner->isHTMLElement()) { + return static_cast<Element*>(owner)->getAttribute(nameAttr); + } + } else { + owner = document->body(); + if (owner && owner->isHTMLElement()) + return static_cast<Element*>(owner)->getAttribute(nameAttr); + } + } + + return nil; +} + +static IntRect boundingBoxRect(RenderObject* obj) +{ + IntRect rect; + if (obj) { + if (obj->isInlineContinuation()) + obj = obj->element()->renderer(); + Vector<IntRect> rects; + int x, y; + obj->absolutePosition(x, y); + obj->absoluteRects(rects, x, y); + const size_t n = rects.size(); + for (size_t i = 0; i < n; ++i) { + IntRect r = rects[i]; + if (!r.isEmpty()) { + if (obj->style()->hasAppearance()) + theme()->adjustRepaintRect(obj, r); + rect.unite(r); + } + } + } + return rect; +} + +-(NSValue*)position +{ + IntRect rect = m_areaElement ? m_areaElement->getRect(m_renderer) : boundingBoxRect(m_renderer); + + // The Cocoa accessibility API wants the lower-left corner. + NSPoint point = NSMakePoint(rect.x(), rect.bottom()); + if (m_renderer && m_renderer->view() && m_renderer->view()->frameView()) { + NSView* view = m_renderer->view()->frameView()->getDocumentView(); + point = [[view window] convertBaseToScreen: [view convertPoint: point toView:nil]]; + } + + return [NSValue valueWithPoint: point]; +} + +-(NSValue*)size +{ + IntRect rect = m_areaElement ? m_areaElement->getRect(m_renderer) : boundingBoxRect(m_renderer); + return [NSValue valueWithSize: NSMakeSize(rect.width(), rect.height())]; +} + +// the closest object for an internal anchor +-(id)linkedUIElement +{ + if (![self isAnchor]) + return nil; + + HTMLAnchorElement* anchor = [self anchorElement]; + if (!anchor) + return nil; + + KURL linkURL = anchor->href(); + String ref = linkURL.ref(); + if (ref.isEmpty()) + return nil; + + // check if URL is the same as current URL + linkURL.setRef(""); + if (m_renderer->document()->url() != linkURL) + return nil; + + Node* linkedNode = m_renderer->document()->getElementById(ref); + if (!linkedNode) { + linkedNode = m_renderer->document()->anchors()->namedItem(ref, !m_renderer->document()->inCompatMode()); + if (!linkedNode) + return nil; + } + + // the element we find may not be accessible, keep searching until we find a good one + WebCoreAXObject* linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); + while (linkedAXElement && [linkedAXElement accessibilityIsIgnored]) { + linkedNode = linkedNode->traverseNextNode(NULL); + if (!linkedNode) + return nil; + linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); + } + + return linkedAXElement; +} + +// accessibilityShouldUseUniqueId is an AppKit method we override so that +// objects will be given a unique ID, and therefore allow AppKit to know when they +// become obsolete (e.g. when the user navigates to a new web page, making this one +// unrendered but not deallocated because it is in the back/forward cache). +// It is important to call NSAccessibilityUnregisterUniqueIdForUIElement in the +// appropriate place (e.g. dealloc) to remove these non-retained references from +// AppKit's id mapping tables. We do this in detach by calling unregisterUniqueIdForUIElement. +// +// Registering an object is also required for observing notifications. Only registered objects can be observed. +- (BOOL)accessibilityShouldUseUniqueId { + if (!m_renderer) + return NO; + + if ([self isWebArea]) + return YES; + + if ([self isTextControl]) + return YES; + + return NO; +} + +-(BOOL)accessibilityIsIgnored +{ + // ignore invisible element + if (!m_renderer || m_renderer->style()->visibility() != VISIBLE) + return YES; + + // ignore popup menu items because AppKit does + for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { + if (parent->isMenuList()) + return YES; + } + + // NOTE: BRs always have text boxes now, so the text box check here can be removed + if (m_renderer->isText()) + return m_renderer->isBR() || !static_cast<RenderText*>(m_renderer)->firstTextBox(); + + // delegate to the attachment + if ([self isAttachment]) + return [[self attachmentView] accessibilityIsIgnored]; + + if (m_areaElement || (m_renderer->element() && m_renderer->element()->isLink())) + return NO; + + // all controls are accessible + if (m_renderer->element() && m_renderer->element()->isControl()) + return NO; + + if (m_renderer->isBlockFlow() && m_renderer->childrenInline()) + return !static_cast<RenderBlock*>(m_renderer)->firstLineBox() && ![self mouseButtonListener]; + + // ignore images seemingly used as spacers + if (m_renderer->isImage()) { + // informal standard is to ignore images with zero-length alt strings + Element* elt = static_cast<Element*>(m_renderer->element()); + if (elt) { + const AtomicString& alt = elt->getAttribute(altAttr); + if (alt.isEmpty() && !alt.isNull()) + return YES; + } + + // check for one-dimensional image + if (m_renderer->height() <= 1 || m_renderer->width() <= 1) + return YES; + + // check whether rendered image was stretched from one-dimensional file image + RenderImage* image = static_cast<RenderImage*>(m_renderer); + if (image->cachedImage()) { + IntSize imageSize = image->cachedImage()->imageSize(); + return (imageSize.height() <= 1 || imageSize.width() <= 1); + } + + return NO; + } + + return (!m_renderer->isListMarker() && ![self isWebArea]); +} + +- (NSArray*)accessibilityAttributeNames +{ + if ([self isAttachment]) + return [[self attachmentView] accessibilityAttributeNames]; + + static NSArray* attributes = nil; + static NSArray* anchorAttrs = nil; + static NSArray* webAreaAttrs = nil; + static NSArray* textAttrs = nil; + NSMutableArray* tempArray; + if (attributes == nil) { + attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, + NSAccessibilitySubroleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityChildrenAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilityWindowAttribute, + @"AXSelectedTextMarkerRange", + @"AXStartTextMarker", + @"AXEndTextMarker", + @"AXVisited", + NSAccessibilityLinkedUIElementsAttribute, + nil]; + } + if (anchorAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject: NSAccessibilityURLAttribute]; + anchorAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (webAreaAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject: @"AXLinkUIElements"]; + [tempArray addObject: @"AXLoaded"]; + [tempArray addObject: @"AXLayoutCount"]; + webAreaAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + if (textAttrs == nil) { + tempArray = [[NSMutableArray alloc] initWithArray:attributes]; + [tempArray addObject: NSAccessibilityNumberOfCharactersAttribute]; + [tempArray addObject: NSAccessibilitySelectedTextAttribute]; + [tempArray addObject: NSAccessibilitySelectedTextRangeAttribute]; + [tempArray addObject: NSAccessibilityVisibleCharacterRangeAttribute]; + [tempArray addObject: NSAccessibilityInsertionPointLineNumberAttribute]; + textAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + if (!m_renderer || [self isPasswordField]) + return attributes; + + if ([self isWebArea]) + return webAreaAttrs; + + if ([self isTextControl]) + return textAttrs; + + if ([self isAnchor] || m_renderer->isImage()) + return anchorAttrs; + + return attributes; +} + +- (NSArray*)accessibilityActionNames +{ + static NSArray* actions = nil; + + if (actions == nil) { + if ([self actionElement]) + actions = [[NSArray alloc] initWithObjects: NSAccessibilityPressAction, nil]; + else if ([self isAttachment]) + actions = [[[self attachmentView] accessibilityActionNames] retain]; + } + + return actions; +} + +- (NSString*)accessibilityActionDescription:(NSString*)action +{ + // we have no custom actions + return NSAccessibilityActionDescription(action); +} + +- (void)accessibilityPerformAction:(NSString*)action +{ + if ([action isEqualToString:NSAccessibilityPressAction]) { + if ([self isAttachment]) { + [[self attachmentView] accessibilityPerformAction:action]; + return; + } + + Element* actionElement = [self actionElement]; + if (!actionElement) + return; + if (Frame* f = actionElement->document()->frame()) + f->loader()->resetMultipleFormSubmissionProtection(); + actionElement->accessKeyAction(true); + } +} + +- (WebCoreTextMarkerRange*) textMarkerRangeFromMarkers: (WebCoreTextMarker*) textMarker1 andEndMarker:(WebCoreTextMarker*) textMarker2 +{ + return [[WebCoreViewFactory sharedFactory] textMarkerRangeWithStart:textMarker1 end:textMarker2]; +} + +- (WebCoreTextMarker*) textMarkerForVisiblePosition: (VisiblePosition)visiblePos +{ + if (visiblePos.isNull()) + return nil; + + if (isPasswordFieldElement(visiblePos.deepEquivalent().node())) + return nil; + + return m_renderer->document()->axObjectCache()->textMarkerForVisiblePosition(visiblePos); +} + +- (VisiblePosition) visiblePositionForTextMarker: (WebCoreTextMarker*)textMarker +{ + return m_renderer->document()->axObjectCache()->visiblePositionForTextMarker(textMarker); +} + +- (VisiblePosition) visiblePositionForStartOfTextMarkerRange: (WebCoreTextMarkerRange*)textMarkerRange +{ + return [self visiblePositionForTextMarker:[[WebCoreViewFactory sharedFactory] startOfTextMarkerRange:textMarkerRange]]; +} + +- (VisiblePosition) visiblePositionForEndOfTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + return [self visiblePositionForTextMarker:[[WebCoreViewFactory sharedFactory] endOfTextMarkerRange:textMarkerRange]]; +} + +- (WebCoreTextMarkerRange*) textMarkerRangeFromVisiblePositions: (VisiblePosition) startPosition andEndPos: (VisiblePosition) endPosition +{ + WebCoreTextMarker* startTextMarker = [self textMarkerForVisiblePosition: startPosition]; + WebCoreTextMarker* endTextMarker = [self textMarkerForVisiblePosition: endPosition]; + return [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker]; +} + +- (WebCoreTextMarkerRange*)textMarkerRange +{ + if (!m_renderer) + return nil; + + // construct VisiblePositions for start and end + Node* node = m_renderer->element(); + VisiblePosition visiblePos1 = VisiblePosition(node, 0, VP_DEFAULT_AFFINITY); + VisiblePosition visiblePos2 = VisiblePosition(node, maxDeepOffset(node), VP_DEFAULT_AFFINITY); + + // the VisiblePositions are equal for nodes like buttons, so adjust for that + if (visiblePos1 == visiblePos2) { + visiblePos2 = visiblePos2.next(); + if (visiblePos2.isNull()) + visiblePos2 = visiblePos1; + } + + WebCoreTextMarker* startTextMarker = [self textMarkerForVisiblePosition: visiblePos1]; + WebCoreTextMarker* endTextMarker = [self textMarkerForVisiblePosition: visiblePos2]; + return [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker]; +} + +- (RenderObject*)topRenderer +{ + return m_renderer->document()->topDocument()->renderer(); +} + +- (FrameView*)frameView +{ + return m_renderer->document()->view(); +} + +- (FrameView*)topFrameView +{ + return m_renderer->document()->topDocument()->renderer()->view()->frameView(); +} + +- (id)accessibilityAttributeValue:(NSString*)attributeName +{ + if (!m_renderer) + return nil; + + if ([attributeName isEqualToString: NSAccessibilityRoleAttribute]) + return [self role]; + + if ([attributeName isEqualToString: NSAccessibilitySubroleAttribute]) + return [self subrole]; + + if ([attributeName isEqualToString: NSAccessibilityRoleDescriptionAttribute]) + return [self roleDescription]; + + if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) { + if (m_renderer->isRenderView() && m_renderer->view() && m_renderer->view()->frameView()) + return m_renderer->view()->frameView()->getView(); + return [self parentObjectUnignored]; + } + + if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { + if (!m_children) { + m_children = [NSMutableArray arrayWithCapacity: 8]; + [m_children retain]; + [self addChildrenToArray: m_children]; + } + return m_children; + } + + if ([self isWebArea]) { + if ([attributeName isEqualToString: @"AXLinkUIElements"]) { + NSMutableArray* links = [NSMutableArray arrayWithCapacity: 32]; + RefPtr<HTMLCollection> coll = m_renderer->document()->links(); + Node* curr = coll->firstItem(); + while (curr) { + RenderObject* obj = curr->renderer(); + if (obj) { + WebCoreAXObject* axobj = obj->document()->axObjectCache()->get(obj); + ASSERT([[axobj role] isEqualToString:@"AXLink"]); + if (![axobj accessibilityIsIgnored]) + [links addObject: axobj]; + } + curr = coll->nextItem(); + } + return links; + } + if ([attributeName isEqualToString: @"AXLoaded"]) + return [NSNumber numberWithBool: (!m_renderer->document()->tokenizer())]; + if ([attributeName isEqualToString: @"AXLayoutCount"]) + return [NSNumber numberWithInt: (static_cast<RenderView*>(m_renderer)->frameView()->layoutCount())]; + } + + if ([self isTextControl]) { + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute]) + return [self isPasswordField] ? nil : [NSNumber numberWithUnsignedInt: textControl->text().length()]; + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { + if ([self isPasswordField]) + return nil; + NSString* text = textControl->text(); + return [text substringWithRange: NSMakeRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart())]; + } + if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) + return [self isPasswordField] ? nil : [NSValue valueWithRange: NSMakeRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart())]; + // TODO: Get actual visible range. <rdar://problem/4712101> + if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) + return [self isPasswordField] ? nil : [NSValue valueWithRange: NSMakeRange(0, textControl->text().length())]; + if ([attributeName isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) { + if ([self isPasswordField] || textControl->selectionStart() != textControl->selectionEnd()) + return nil; + NSNumber* index = [NSNumber numberWithInt: textControl->selectionStart()]; + return [self doAXLineForTextMarker: [self textMarkerForIndex: index lastIndexOK: YES]]; + } + } + + if ([attributeName isEqualToString: NSAccessibilityURLAttribute]) { + if ([self isAnchor]) { + if (HTMLAnchorElement* anchor = [self anchorElement]) { + NSURL *href = anchor->href(); + return href; + } + } else if (m_renderer->isImage() && m_renderer->element() && m_renderer->element()->hasTagName(imgTag)) { + NSURL *src = static_cast<HTMLImageElement*>(m_renderer->element())->src(); + return src; + } + return nil; + } + + if ([attributeName isEqualToString: @"AXVisited"]) + return [NSNumber numberWithBool: m_renderer->style()->pseudoState() == PseudoVisited]; + + if ([attributeName isEqualToString: NSAccessibilityTitleAttribute]) + return [self title]; + + if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute]) + return [self accessibilityDescription]; + + if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) + return [self value]; + + if ([attributeName isEqualToString: NSAccessibilityHelpAttribute]) + return [self helpText]; + + if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) + return [NSNumber numberWithBool: (m_renderer->element() && m_renderer->document()->focusedNode() == m_renderer->element())]; + + if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute]) + return [NSNumber numberWithBool: m_renderer->element() ? m_renderer->element()->isEnabled() : YES]; + + if ([attributeName isEqualToString: NSAccessibilitySizeAttribute]) + return [self size]; + + if ([attributeName isEqualToString: NSAccessibilityPositionAttribute]) + return [self position]; + + if ([attributeName isEqualToString: NSAccessibilityWindowAttribute]) { + if (m_renderer && m_renderer->view() && m_renderer->view()->frameView()) + return [m_renderer->view()->frameView()->getView() window]; + + return nil; + } + + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) { + // get the selection from the document + Selection selection = [self frameView]->frame()->selectionController()->selection(); + if (selection.isNone()) + return nil; + + return (id) [self textMarkerRangeFromVisiblePositions:selection.visibleStart() andEndPos:selection.visibleEnd()]; + } + + if ([attributeName isEqualToString: @"AXStartTextMarker"]) + return (id) [self textMarkerForVisiblePosition: startOfDocument(m_renderer->document())]; + + if ([attributeName isEqualToString: @"AXEndTextMarker"]) + return (id) [self textMarkerForVisiblePosition: endOfDocument(m_renderer->document())]; + + if ([attributeName isEqualToString: NSAccessibilityLinkedUIElementsAttribute]) + return (id) [self linkedUIElement]; + + return nil; +} + +- (NSArray* )accessibilityParameterizedAttributeNames +{ + if ([self isAttachment]) + return nil; + + static NSArray* paramAttrs = nil; + static NSArray* textParamAttrs = nil; + if (paramAttrs == nil) { + paramAttrs = [[NSArray alloc] initWithObjects: + @"AXUIElementForTextMarker", + @"AXTextMarkerRangeForUIElement", + @"AXLineForTextMarker", + @"AXTextMarkerRangeForLine", + @"AXStringForTextMarkerRange", + @"AXTextMarkerForPosition", + @"AXBoundsForTextMarkerRange", + @"AXAttributedStringForTextMarkerRange", + @"AXTextMarkerRangeForUnorderedTextMarkers", + @"AXNextTextMarkerForTextMarker", + @"AXPreviousTextMarkerForTextMarker", + @"AXLeftWordTextMarkerRangeForTextMarker", + @"AXRightWordTextMarkerRangeForTextMarker", + @"AXLeftLineTextMarkerRangeForTextMarker", + @"AXRightLineTextMarkerRangeForTextMarker", + @"AXSentenceTextMarkerRangeForTextMarker", + @"AXParagraphTextMarkerRangeForTextMarker", + @"AXNextWordEndTextMarkerForTextMarker", + @"AXPreviousWordStartTextMarkerForTextMarker", + @"AXNextLineEndTextMarkerForTextMarker", + @"AXPreviousLineStartTextMarkerForTextMarker", + @"AXNextSentenceEndTextMarkerForTextMarker", + @"AXPreviousSentenceStartTextMarkerForTextMarker", + @"AXNextParagraphEndTextMarkerForTextMarker", + @"AXPreviousParagraphStartTextMarkerForTextMarker", + @"AXStyleTextMarkerRangeForTextMarker", + @"AXLengthForTextMarkerRange", + nil]; + } + + if (textParamAttrs == nil) { + NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; + [tempArray addObject: (NSString*)kAXLineForIndexParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXRangeForLineParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXStringForRangeParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXRangeForPositionParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXRangeForIndexParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXBoundsForRangeParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXRTFForRangeParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXAttributedStringForRangeParameterizedAttribute]; + [tempArray addObject: (NSString*)kAXStyleRangeForIndexParameterizedAttribute]; + textParamAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + if ([self isPasswordField]) + return [NSArray array]; + + if (!m_renderer) + return paramAttrs; + + if ([self isTextControl]) + return textParamAttrs; + + return paramAttrs; +} + +- (id)doAXUIElementForTextMarker: (WebCoreTextMarker* ) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + RenderObject* obj = visiblePos.deepEquivalent().node()->renderer(); + if (!obj) + return nil; + + return obj->document()->axObjectCache()->get(obj); +} + +- (id)doAXTextMarkerRangeForUIElement: (id) uiElement +{ + return (id)[uiElement textMarkerRange]; +} + +- (id)doAXLineForTextMarker: (WebCoreTextMarker* ) textMarker +{ + unsigned int lineCount = 0; + VisiblePosition savedVisiblePos; + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // move up until we get to the top + // NOTE: BUG This only takes us to the top of the rootEditableElement, not the top of the + // top document. + while (visiblePos.isNotNull() && !(inSameLine(visiblePos, savedVisiblePos))) { + lineCount += 1; + savedVisiblePos = visiblePos; + visiblePos = previousLinePosition(visiblePos, 0); + } + + return [NSNumber numberWithUnsignedInt:(lineCount - 1)]; +} + +- (id)doAXTextMarkerRangeForLine: (NSNumber*) lineNumber +{ + unsigned lineCount = [lineNumber unsignedIntValue]; + if (lineCount == 0 || !m_renderer) + return nil; + + // iterate over the lines + // NOTE: BUG this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the + // last offset of the last line + VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForCoordinates(0, 0); + VisiblePosition savedVisiblePos; + while (--lineCount != 0) { + savedVisiblePos = visiblePos; + visiblePos = nextLinePosition(visiblePos, 0); + if (visiblePos.isNull() || visiblePos == savedVisiblePos) + return nil; + } + + // make a caret selection for the marker position, then extend it to the line + // NOTE: ignores results of sel.modify because it returns false when + // starting at an empty line. The resulting selection in that case + // will be a caret at visiblePos. + SelectionController selectionController; + selectionController.setSelection(Selection(visiblePos)); + selectionController.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); + + // return a marker range for the selection start to end + VisiblePosition startPosition = selectionController.selection().visibleStart(); + VisiblePosition endPosition = selectionController.selection().visibleEnd(); + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +static NSString *nsStringForReplacedNode(Node* replacedNode) +{ + // we should always be given a rendered node and a replaced node, but be safe + // replaced nodes are either attachments (widgets) or images + if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) { + ASSERT_NOT_REACHED(); + return nil; + } + + // create an AX object, but skip it if it is not supposed to be seen + WebCoreAXObject* obj = replacedNode->renderer()->document()->axObjectCache()->get(replacedNode->renderer()); + if ([obj accessibilityIsIgnored]) + return nil; + + // use the attachmentCharacter to represent the replaced node + const UniChar attachmentChar = NSAttachmentCharacter; + return [NSString stringWithCharacters:&attachmentChar length:1]; +} + +- (id)doAXStringForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + // extract the start and end VisiblePosition + VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange]; + if (startVisiblePosition.isNull()) + return nil; + + VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange]; + if (endVisiblePosition.isNull()) + return nil; + + NSMutableString* resultString = [[[NSMutableString alloc] init] autorelease]; + TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); + while (!it.atEnd()) { + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.length() != 0) { + [resultString appendString:[NSString stringWithCharacters:it.characters() length:it.length()]]; + } else { + // locate the node and starting offset for this replaced range + int exception = 0; + Node* node = it.range()->startContainer(exception); + ASSERT(node == it.range()->endContainer(exception)); + int offset = it.range()->startOffset(exception); + NSString* attachmentString = nsStringForReplacedNode(node->childNode(offset)); + + // append the replacement string + if (attachmentString) + [resultString appendString:attachmentString]; + } + it.advance(); + } + + return [resultString length] > 0 ? resultString : nil; +} + +- (id)doAXTextMarkerForPosition: (NSPoint) point +{ + // convert absolute point to view coordinates + FrameView* frameView = [self topFrameView]; + NSView* view = frameView->getDocumentView(); + RenderObject* renderer = [self topRenderer]; + Node* innerNode = 0; + + // locate the node containing the point + IntPoint pointResult; + while (1) { + // ask the document layer to hitTest + NSPoint windowCoord = [[view window] convertScreenToBase: point]; + IntPoint ourpoint([view convertPoint:windowCoord fromView:nil]); + + HitTestRequest request(true, true); + HitTestResult result(ourpoint); + renderer->layer()->hitTest(request, result); + innerNode = result.innerNode(); + if (!innerNode || !innerNode->renderer()) + return nil; + + pointResult = result.localPoint(); + + // done if hit something other than a widget + renderer = innerNode->renderer(); + if (!renderer->isWidget()) + break; + + // descend into widget (FRAME, IFRAME, OBJECT...) + Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); + if (!widget || !widget->isFrameView()) + break; + Frame* frame = static_cast<FrameView*>(widget)->frame(); + if (!frame) + break; + Document* document = frame->document(); + if (!document) + break; + renderer = document->renderer(); + frameView = static_cast<FrameView*>(widget); + view = frameView->getDocumentView(); + } + + // get position within the node + VisiblePosition pos = innerNode->renderer()->positionForPoint(pointResult); + return (id) [self textMarkerForVisiblePosition:pos]; +} + +- (id)doAXBoundsForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + // extract the start and end VisiblePosition + VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange]; + if (startVisiblePosition.isNull()) + return nil; + + VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange]; + if (endVisiblePosition.isNull()) + return nil; + + IntRect rect1 = startVisiblePosition.caretRect(); + IntRect rect2 = endVisiblePosition.caretRect(); + + // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds + if (rect2.y() != rect1.y()) { + VisiblePosition endOfFirstLine = endOfLine(startVisiblePosition); + if (startVisiblePosition == endOfFirstLine) { + startVisiblePosition.setAffinity(DOWNSTREAM); + rect1 = startVisiblePosition.caretRect(); + } + if (endVisiblePosition == endOfFirstLine) { + endVisiblePosition.setAffinity(UPSTREAM); + rect2 = endVisiblePosition.caretRect(); + } + } + + IntRect ourrect = rect1; + ourrect.unite(rect2); + + // try to use the document view from the first position, so that nested WebAreas work, + // but fall back to the top level doc if we do not find it easily + RenderObject* renderer = startVisiblePosition.deepEquivalent().node()->renderer(); + FrameView* frameView = renderer ? renderer->document()->view() : 0; + if (!frameView) + frameView = [self frameView]; + NSView *view = frameView->getView(); + + // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead + if (rect1.bottom() != rect2.bottom()) { + RefPtr<Range> dataRange = makeRange(startVisiblePosition, endVisiblePosition); + IntRect boundingBox = dataRange->boundingBox(); + String rangeString = plainText(dataRange.get()); + if (rangeString.length() > 1 && !boundingBox.isEmpty()) + ourrect = boundingBox; + } + + // convert our rectangle to screen coordinates + NSRect rect = ourrect; + rect = NSOffsetRect(rect, -frameView->contentsX(), -frameView->contentsY()); + rect = [view convertRect:rect toView:nil]; + rect.origin = [[view window] convertBaseToScreen:rect.origin]; + + // return the converted rect + return [NSValue valueWithRect:rect]; +} + +static CGColorRef CreateCGColorIfDifferent(NSColor* nsColor, CGColorRef existingColor) +{ + // get color information assuming NSDeviceRGBColorSpace + NSColor* rgbColor = [nsColor colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + if (rgbColor == nil) + rgbColor = [NSColor blackColor]; + CGFloat components[4]; + [rgbColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; + + // create a new CGColorRef to return + CGColorSpaceRef cgColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGColorRef cgColor = CGColorCreate(cgColorSpace, components); + CGColorSpaceRelease(cgColorSpace); + CFMakeCollectable(cgColor); + + // check for match with existing color + if (existingColor && CGColorEqualToColor(cgColor, existingColor)) + cgColor = nil; + + return cgColor; +} + +static void AXAttributeStringSetColor(NSMutableAttributedString* attrString, NSString* attribute, NSColor* color, NSRange range) +{ + if (color) { + CGColorRef existingColor = (CGColorRef) [attrString attribute:attribute atIndex:range.location effectiveRange:nil]; + CGColorRef cgColor = CreateCGColorIfDifferent(color, existingColor); + if (cgColor) { + [attrString addAttribute:attribute value:(id)cgColor range:range]; + CGColorRelease(cgColor); + } + } else + [attrString removeAttribute:attribute range:range]; +} + +static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range) +{ + if (number) + [attrString addAttribute:attribute value:number range:range]; + else + [attrString removeAttribute:attribute range:range]; +} + +static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, NSString* attribute, NSFont* font, NSRange range) +{ + NSDictionary* dict; + + if (font) { + dict = [NSDictionary dictionaryWithObjectsAndKeys: + [font fontName] , NSAccessibilityFontNameKey, + [font familyName] , NSAccessibilityFontFamilyKey, + [font displayName] , NSAccessibilityVisibleNameKey, + [NSNumber numberWithFloat:[font pointSize]] , NSAccessibilityFontSizeKey, + nil]; + + [attrString addAttribute:attribute value:dict range:range]; + } else + [attrString removeAttribute:attribute range:range]; + +} + +static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + RenderStyle* style = renderer->style(); + + // set basic font info + AXAttributeStringSetFont(attrString, NSAccessibilityFontTextAttribute, style->font().primaryFont()->getNSFont(), range); + + // set basic colors + AXAttributeStringSetColor(attrString, NSAccessibilityForegroundColorTextAttribute, nsColor(style->color()), range); + AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, nsColor(style->backgroundColor()), range); + + // set super/sub scripting + EVerticalAlign alignment = style->verticalAlign(); + if (alignment == SUB) + AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:(-1)], range); + else if (alignment == SUPER) + AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:1], range); + else + [attrString removeAttribute:NSAccessibilitySuperscriptTextAttribute range:range]; + + // set shadow + if (style->textShadow()) + AXAttributeStringSetNumber(attrString, NSAccessibilityShadowTextAttribute, [NSNumber numberWithBool:YES], range); + else + [attrString removeAttribute:NSAccessibilityShadowTextAttribute range:range]; + + // set underline and strikethrough + int decor = style->textDecorationsInEffect(); + if ((decor & UNDERLINE) == 0) { + [attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range]; + [attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range]; + } + + if ((decor & LINE_THROUGH) == 0) { + [attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range]; + [attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range]; + } + + if ((decor & (UNDERLINE | LINE_THROUGH)) != 0) { + // find colors using quirk mode approach (strict mode would use current + // color for all but the root line box, which would use getTextDecorationColors) + Color underline, overline, linethrough; + renderer->getTextDecorationColors(decor, underline, overline, linethrough); + + if ((decor & UNDERLINE) != 0) { + AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, [NSNumber numberWithBool:YES], range); + AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, nsColor(underline), range); + } + + if ((decor & LINE_THROUGH) != 0) { + AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, [NSNumber numberWithBool:YES], range); + AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, nsColor(linethrough), range); + } + } +} + +static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + int parentHeadingLevel = headingLevel(renderer->parent()); + + if (parentHeadingLevel) + [attrString addAttribute:@"AXHeadingLevel" value:[NSNumber numberWithInt:parentHeadingLevel] range:range]; + else + [attrString removeAttribute:@"AXHeadingLevel" range:range]; +} + +static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) +{ + int quoteLevel = blockquoteLevel(renderer); + + if (quoteLevel) + [attrString addAttribute:@"AXBlockQuoteLevel" value:[NSNumber numberWithInt:quoteLevel] range:range]; + else + [attrString removeAttribute:@"AXBlockQuoteLevel" range:range]; +} + +static void AXAttributeStringSetElement(NSMutableAttributedString* attrString, NSString* attribute, id element, NSRange range) +{ + if (element) { + // make a serialiazable AX object + AXUIElementRef axElement = [[WebCoreViewFactory sharedFactory] AXUIElementForElement:element]; + if (axElement) { + [attrString addAttribute:attribute value:(id)axElement range:range]; + CFRelease(axElement); + } + } else + [attrString removeAttribute:attribute range:range]; +} + +static WebCoreAXObject* AXLinkElementForNode (Node* node) +{ + RenderObject* obj = node->renderer(); + if (!obj) + return nil; + + WebCoreAXObject* axObj = obj->document()->axObjectCache()->get(obj); + HTMLAnchorElement* anchor = [axObj anchorElement]; + if (!anchor || !anchor->renderer()) + return nil; + + return anchor->renderer()->document()->axObjectCache()->get(anchor->renderer()); +} + +static void AXAttributeStringSetSpelling(NSMutableAttributedString* attrString, Node* node, int offset, NSRange range) +{ + Vector<DocumentMarker> markers = node->renderer()->document()->markersForNode(node); + Vector<DocumentMarker>::iterator markerIt = markers.begin(); + + unsigned endOffset = (unsigned)offset + range.length; + for ( ; markerIt != markers.end(); markerIt++) { + DocumentMarker marker = *markerIt; + + if (marker.type != DocumentMarker::Spelling) + continue; + + if (marker.endOffset <= (unsigned)offset) + continue; + + if (marker.startOffset > endOffset) + break; + + // add misspelling attribute for the intersection of the marker and the range + int rStart = range.location + (marker.startOffset - offset); + int rLength = MIN(marker.endOffset, endOffset) - marker.startOffset; + NSRange spellRange = NSMakeRange(rStart, rLength); + AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, [NSNumber numberWithBool:YES], spellRange); + + if (marker.endOffset > endOffset + 1) + break; + } +} + +static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, int offset, const UChar* chars, int length) +{ + // skip invisible text + if (!node->renderer()) + return; + + // easier to calculate the range before appending the string + NSRange attrStringRange = NSMakeRange([attrString length], length); + + // append the string from this node + [[attrString mutableString] appendString:[NSString stringWithCharacters:chars length:length]]; + + // add new attributes and remove irrelevant inherited ones + // NOTE: color attributes are handled specially because -[NSMutableAttributedString addAttribute: value: range:] does not merge + // identical colors. Workaround is to not replace an existing color attribute if it matches what we are adding. This also means + // we cannot just pre-remove all inherited attributes on the appended string, so we have to remove the irrelevant ones individually. + + // remove inherited attachment from prior AXAttributedStringAppendReplaced + [attrString removeAttribute:NSAccessibilityAttachmentTextAttribute range:attrStringRange]; + + // set new attributes + AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange); + AXAttributeStringSetElement(attrString, NSAccessibilityLinkTextAttribute, AXLinkElementForNode(node), attrStringRange); + + // do spelling last because it tends to break up the range + AXAttributeStringSetSpelling(attrString, node, offset, attrStringRange); +} + +- (id)doAXAttributedStringForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + // extract the start and end VisiblePosition + VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange]; + if (startVisiblePosition.isNull()) + return nil; + + VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange]; + if (endVisiblePosition.isNull()) + return nil; + + // iterate over the range to build the AX attributed string + NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init]; + TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); + while (!it.atEnd()) { + // locate the node and starting offset for this range + int exception = 0; + Node* node = it.range()->startContainer(exception); + ASSERT(node == it.range()->endContainer(exception)); + int offset = it.range()->startOffset(exception); + + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.length() != 0) { + AXAttributedStringAppendText(attrString, node, offset, it.characters(), it.length()); + } else { + Node* replacedNode = node->childNode(offset); + NSString *attachmentString = nsStringForReplacedNode(replacedNode); + if (attachmentString) { + NSRange attrStringRange = NSMakeRange([attrString length], [attachmentString length]); + + // append the placeholder string + [[attrString mutableString] appendString:attachmentString]; + + // remove all inherited attributes + [attrString setAttributes:nil range:attrStringRange]; + + // add the attachment attribute + WebCoreAXObject* obj = replacedNode->renderer()->document()->axObjectCache()->get(replacedNode->renderer()); + AXAttributeStringSetElement(attrString, NSAccessibilityAttachmentTextAttribute, obj, attrStringRange); + } + } + it.advance(); + } + + return [attrString autorelease]; +} + +- (id)doAXTextMarkerRangeForUnorderedTextMarkers: (NSArray*) markers +{ + // get and validate the markers + if ([markers count] < 2) + return nil; + + WebCoreTextMarker* textMarker1 = (WebCoreTextMarker*) [markers objectAtIndex:0]; + WebCoreTextMarker* textMarker2 = (WebCoreTextMarker*) [markers objectAtIndex:1]; + if (![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker1] || ![[WebCoreViewFactory sharedFactory] objectIsTextMarker:textMarker2]) + return nil; + + // convert to VisiblePosition + VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:textMarker1]; + VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:textMarker2]; + if (visiblePos1.isNull() || visiblePos2.isNull()) + return nil; + + WebCoreTextMarker* startTextMarker; + WebCoreTextMarker* endTextMarker; + bool alreadyInOrder; + + // upstream is ordered before downstream for the same position + if (visiblePos1 == visiblePos2 && visiblePos2.affinity() == UPSTREAM) + alreadyInOrder = false; + + // use selection order to see if the positions are in order + else + alreadyInOrder = Selection(visiblePos1, visiblePos2).isBaseFirst(); + + if (alreadyInOrder) { + startTextMarker = textMarker1; + endTextMarker = textMarker2; + } else { + startTextMarker = textMarker2; + endTextMarker = textMarker1; + } + + return (id) [self textMarkerRangeFromMarkers: startTextMarker andEndMarker:endTextMarker]; +} + +- (id)doAXNextTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return nil; + + return (id) [self textMarkerForVisiblePosition:nextVisiblePos]; +} + +- (id)doAXPreviousTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition previousVisiblePos = visiblePos.previous(); + if (previousVisiblePos.isNull()) + return nil; + + return (id) [self textMarkerForVisiblePosition:previousVisiblePos]; +} + +- (id)doAXLeftWordTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition startPosition = startOfWord(visiblePos, LeftWordIfOnBoundary); + VisiblePosition endPosition = endOfWord(startPosition); + + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXRightWordTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary); + VisiblePosition endPosition = endOfWord(startPosition); + + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + + +static VisiblePosition updateAXLineStartForVisiblePosition(const VisiblePosition& visiblePosition) +{ + // A line in the accessibility sense should include floating objects, such as aligned image, as part of a line. + // So let's update the position to include that. + VisiblePosition tempPosition; + VisiblePosition startPosition = visiblePosition; + Position p; + RenderObject* renderer; + while (true) { + tempPosition = startPosition.previous(); + if (tempPosition.isNull()) + break; + p = tempPosition.deepEquivalent(); + if (!p.node()) + break; + renderer = p.node()->renderer(); + if (!renderer || renderer->inlineBox(p.offset(), tempPosition.affinity()) || (renderer->isRenderBlock() && p.offset() == 0)) + break; + startPosition = tempPosition; + } + + return startPosition; +} + +- (id)doAXLeftLineTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make a caret selection for the position before marker position (to make sure + // we move off of a line start) + VisiblePosition prevVisiblePos = visiblePos.previous(); + if (prevVisiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfLine(prevVisiblePos); + + // keep searching for a valid line start position. Unless the textmarker is at the very beginning, there should + // always be a valid line range. However, startOfLine will return null for position next to a floating object, + // since floating object doesn't really belong to any line. + // This check will reposition the marker before the floating object, to ensure we get a line start. + if (startPosition.isNull()) { + while (startPosition.isNull() && prevVisiblePos.isNotNull()) { + prevVisiblePos = prevVisiblePos.previous(); + startPosition = startOfLine(prevVisiblePos); + } + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + VisiblePosition endPosition = endOfLine(prevVisiblePos); + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXRightLineTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a line end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfLine(nextVisiblePos); + + // fetch for a valid line start position + if (startPosition.isNull() ) { + startPosition = visiblePos; + nextVisiblePos = nextVisiblePos.next(); + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + VisiblePosition endPosition = endOfLine(nextVisiblePos); + + // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position + // Unless the textmarker is at the very end, there should always be a valid line range. However, endOfLine will + // return null for position by a floating object, since floating object doesn't really belong to any line. + // This check will reposition the marker after the floating object, to ensure we get a line end. + while (endPosition.isNull() && nextVisiblePos.isNotNull()) { + nextVisiblePos = nextVisiblePos.next(); + endPosition = endOfLine(nextVisiblePos); + } + + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXSentenceTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition startPosition = startOfSentence(visiblePos); + VisiblePosition endPosition = endOfSentence(startPosition); + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXParagraphTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + VisiblePosition startPosition = startOfParagraph(visiblePos); + VisiblePosition endPosition = endOfParagraph(startPosition); + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXNextWordEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a word end + visiblePos = visiblePos.next(); + if (visiblePos.isNull()) + return nil; + + VisiblePosition endPosition = endOfWord(visiblePos, LeftWordIfOnBoundary); + return (id) [self textMarkerForVisiblePosition:endPosition]; +} + +- (id)doAXPreviousWordStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a word start + visiblePos = visiblePos.previous(); + if (visiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfWord(visiblePos, RightWordIfOnBoundary); + return (id) [self textMarkerForVisiblePosition:startPosition]; +} + +- (id)doAXNextLineEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // to make sure we move off of a line end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return nil; + + VisiblePosition endPosition = endOfLine(nextVisiblePos); + + // as long as the position hasn't reached the end of the doc, keep searching for a valid line end position + // There are cases like when the position is next to a floating object that'll return null for end of line. This code will avoid returning null. + while (endPosition.isNull() && nextVisiblePos.isNotNull()) { + nextVisiblePos = nextVisiblePos.next(); + endPosition = endOfLine(nextVisiblePos); + } + + return (id) [self textMarkerForVisiblePosition: endPosition]; +} + +- (id)doAXPreviousLineStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a line start + VisiblePosition prevVisiblePos = visiblePos.previous(); + if (prevVisiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfLine(prevVisiblePos); + + // as long as the position hasn't reached the beginning of the doc, keep searching for a valid line start position + // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null. + if (startPosition.isNull()) { + while (startPosition.isNull() && prevVisiblePos.isNotNull()) { + prevVisiblePos = prevVisiblePos.previous(); + startPosition = startOfLine(prevVisiblePos); + } + } else + startPosition = updateAXLineStartForVisiblePosition(startPosition); + + return (id) [self textMarkerForVisiblePosition: startPosition]; +} + +- (id)doAXNextSentenceEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a sentence end + VisiblePosition nextVisiblePos = visiblePos.next(); + if (nextVisiblePos.isNull()) + return nil; + + // an empty line is considered a sentence. If it's skipped, then the sentence parser will not + // see this empty line. Instead, return the end position of the empty line. + VisiblePosition endPosition; + String lineString = plainText(makeRange(startOfLine(visiblePos), endOfLine(visiblePos)).get()); + if (lineString.isEmpty()) + endPosition = nextVisiblePos; + else + endPosition = endOfSentence(nextVisiblePos); + + return (id) [self textMarkerForVisiblePosition: endPosition]; +} + +- (id)doAXPreviousSentenceStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + // NOTE: BUG FO 2 IMPLEMENT (currently returns incorrect answer) + // Related? <rdar://problem/3927736> Text selection broken in 8A336 + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a sentence start + VisiblePosition previousVisiblePos = visiblePos.previous(); + if (previousVisiblePos.isNull()) + return nil; + + // treat empty line as a separate sentence. + VisiblePosition startPosition; + String lineString = plainText(makeRange(startOfLine(previousVisiblePos), endOfLine(previousVisiblePos)).get()); + if (lineString.isEmpty()) + startPosition = previousVisiblePos; + else + startPosition = startOfSentence(previousVisiblePos); + + return (id) [self textMarkerForVisiblePosition: startPosition]; +} + +- (id)doAXNextParagraphEndTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a paragraph end + visiblePos = visiblePos.next(); + if (visiblePos.isNull()) + return nil; + + VisiblePosition endPosition = endOfParagraph(visiblePos); + return (id) [self textMarkerForVisiblePosition: endPosition]; +} + +- (id)doAXPreviousParagraphStartTextMarkerForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + // make sure we move off of a paragraph start + visiblePos = visiblePos.previous(); + if (visiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfParagraph(visiblePos); + return (id) [self textMarkerForVisiblePosition: startPosition]; +} + +static VisiblePosition startOfStyleRange (const VisiblePosition visiblePos) +{ + RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer(); + RenderObject* startRenderer = renderer; + RenderStyle* style = renderer->style(); + + // traverse backward by renderer to look for style change + for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { + // skip non-leaf nodes + if (r->firstChild()) + continue; + + // stop at style change + if (r->style() != style) + break; + + // remember match + startRenderer = r; + } + + return VisiblePosition(startRenderer->node(), 0, VP_DEFAULT_AFFINITY); +} + +static VisiblePosition endOfStyleRange (const VisiblePosition visiblePos) +{ + RenderObject* renderer = visiblePos.deepEquivalent().node()->renderer(); + RenderObject* endRenderer = renderer; + RenderStyle* style = renderer->style(); + + // traverse forward by renderer to look for style change + for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { + // skip non-leaf nodes + if (r->firstChild()) + continue; + + // stop at style change + if (r->style() != style) + break; + + // remember match + endRenderer = r; + } + + return VisiblePosition(endRenderer->node(), maxDeepOffset(endRenderer->node()), VP_DEFAULT_AFFINITY); +} + +- (id)doAXStyleTextMarkerRangeForTextMarker: (WebCoreTextMarker*) textMarker +{ + VisiblePosition visiblePos = [self visiblePositionForTextMarker:textMarker]; + if (visiblePos.isNull()) + return nil; + + VisiblePosition startPosition = startOfStyleRange(visiblePos); + VisiblePosition endPosition = endOfStyleRange(visiblePos); + return (id) [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +- (id)doAXLengthForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + // NOTE: BUG Multi-byte support + CFStringRef string = (CFStringRef) [self doAXStringForTextMarkerRange: textMarkerRange]; + if (!string) + return nil; + + return [NSNumber numberWithInt:CFStringGetLength(string)]; +} + +// NOTE: Consider providing this utility method as AX API +- (WebCoreTextMarker*)textMarkerForIndex: (NSNumber*) index lastIndexOK: (BOOL)lastIndexOK +{ + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + unsigned int indexValue = [index unsignedIntValue]; + + // lastIndexOK specifies whether the position after the last character is acceptable + if (indexValue >= textControl->text().length()) { + if (!lastIndexOK || indexValue > textControl->text().length()) + return nil; + } + VisiblePosition position = textControl->visiblePositionForIndex(indexValue); + position.setAffinity(DOWNSTREAM); + return [self textMarkerForVisiblePosition: position]; +} + +// NOTE: Consider providing this utility method as AX API +- (NSNumber*)indexForTextMarker: (WebCoreTextMarker*) marker +{ + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + + VisiblePosition position = [self visiblePositionForTextMarker: marker]; + Node* node = position.deepEquivalent().node(); + if (!node) + return nil; + + for (RenderObject* renderer = node->renderer(); renderer && renderer->element(); renderer = renderer->parent()) { + if (renderer == textControl) + return [NSNumber numberWithInt: textControl->indexForVisiblePosition(position)]; + } + + return nil; +} + +// NOTE: Consider providing this utility method as AX API +- (WebCoreTextMarkerRange*)textMarkerRangeForRange: (NSRange) range +{ + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + if (range.location + range.length > textControl->text().length()) + return nil; + + VisiblePosition startPosition = textControl->visiblePositionForIndex(range.location); + startPosition.setAffinity(DOWNSTREAM); + VisiblePosition endPosition = textControl->visiblePositionForIndex(range.location + range.length); + return [self textMarkerRangeFromVisiblePositions:startPosition andEndPos:endPosition]; +} + +// NOTE: Consider providing this utility method as AX API +- (NSValue*)rangeForTextMarkerRange: (WebCoreTextMarkerRange*) textMarkerRange +{ + WebCoreTextMarker* textMarker1 = [[WebCoreViewFactory sharedFactory] startOfTextMarkerRange:textMarkerRange]; + WebCoreTextMarker* textMarker2 = [[WebCoreViewFactory sharedFactory] endOfTextMarkerRange:textMarkerRange]; + NSNumber* index1 = [self indexForTextMarker: textMarker1]; + NSNumber* index2 = [self indexForTextMarker: textMarker2]; + if (!index1 || !index2 || [index1 unsignedIntValue] > [index2 unsignedIntValue]) + return nil; + + return [NSValue valueWithRange: NSMakeRange([index1 unsignedIntValue], [index2 unsignedIntValue] - [index1 unsignedIntValue])]; +} + +// Given an indexed character, the line number of the text associated with this accessibility +// object that contains the character. +- (id)doAXLineForIndex: (NSNumber*) index +{ + return [self doAXLineForTextMarker: [self textMarkerForIndex: index lastIndexOK: NO]]; +} + +// Given a line number, the range of characters of the text associated with this accessibility +// object that contains the line number. +- (id)doAXRangeForLine: (NSNumber*) lineNumber +{ + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + + // iterate to the specified line + VisiblePosition visiblePos = textControl->visiblePositionForIndex(0); + VisiblePosition savedVisiblePos; + for (unsigned lineCount = [lineNumber unsignedIntValue]; lineCount != 0; lineCount -= 1) { + savedVisiblePos = visiblePos; + visiblePos = nextLinePosition(visiblePos, 0); + if (visiblePos.isNull() || visiblePos == savedVisiblePos) + return nil; + } + + // make a caret selection for the marker position, then extend it to the line + // NOTE: ignores results of selectionController.modify because it returns false when + // starting at an empty line. The resulting selection in that case + // will be a caret at visiblePos. + SelectionController selectionController; + selectionController.setSelection(Selection(visiblePos)); + selectionController.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary); + selectionController.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); + + // calculate the indices for the selection start and end + VisiblePosition startPosition = selectionController.selection().visibleStart(); + VisiblePosition endPosition = selectionController.selection().visibleEnd(); + int index1 = textControl->indexForVisiblePosition(startPosition); + int index2 = textControl->indexForVisiblePosition(endPosition); + + // add one to the end index for a line break not caused by soft line wrap (to match AppKit) + if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull()) + index2 += 1; + + // return nil rather than an zero-length range (to match AppKit) + if (index1 == index2) + return nil; + + return [NSValue valueWithRange: NSMakeRange(index1, index2 - index1)]; +} + +// A substring of the text associated with this accessibility object that is +// specified by the given character range. +- (id)doAXStringForRange: (NSRange) range +{ + if ([self isPasswordField]) + return nil; + + if (range.length == 0) + return @""; + + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + String text = textControl->text(); + if (range.location + range.length > text.length()) + return nil; + + return text.substring(range.location, range.length); +} + +// The composed character range in the text associated with this accessibility object that +// is specified by the given screen coordinates. This parameterized attribute returns the +// complete range of characters (including surrogate pairs of multi-byte glyphs) at the given +// screen coordinates. +// NOTE: This varies from AppKit when the point is below the last line. AppKit returns an +// an error in that case. We return textControl->text().length(), 1. Does this matter? +- (id)doAXRangeForPosition: (NSPoint) point +{ + NSNumber* index = [self indexForTextMarker: [self doAXTextMarkerForPosition: point]]; + if (!index) + return nil; + + return [NSValue valueWithRange: NSMakeRange([index unsignedIntValue], 1)]; +} + +// The composed character range in the text associated with this accessibility object that +// is specified by the given index value. This parameterized attribute returns the complete +// range of characters (including surrogate pairs of multi-byte glyphs) at the given index. +- (id)doAXRangeForIndex: (NSNumber*) number +{ + ASSERT(m_renderer->isTextField() || m_renderer->isTextArea()); + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + String text = textControl->text(); + if (!text.length() || [number unsignedIntValue] > text.length() - 1) + return nil; + + return [NSValue valueWithRange: NSMakeRange([number unsignedIntValue], 1)]; +} + +// The bounding rectangle of the text associated with this accessibility object that is +// specified by the given range. This is the bounding rectangle a sighted user would see +// on the display screen, in pixels. +- (id)doAXBoundsForRange: (NSRange) range +{ + return [self doAXBoundsForTextMarkerRange: [self textMarkerRangeForRange:range]]; +} + +// The CFAttributedStringType representation of the text associated with this accessibility +// object that is specified by the given range. +- (id)doAXAttributedStringForRange: (NSRange) range +{ + return [self doAXAttributedStringForTextMarkerRange: [self textMarkerRangeForRange:range]]; +} + +// The RTF representation of the text associated with this accessibility object that is +// specified by the given range. +- (id)doAXRTFForRange: (NSRange) range +{ + NSAttributedString* attrString = [self doAXAttributedStringForRange: range]; + return [attrString RTFFromRange: NSMakeRange(0, [attrString length]) documentAttributes: nil]; +} + +// Given a character index, the range of text associated with this accessibility object +// over which the style in effect at that character index applies. +- (id)doAXStyleRangeForIndex: (NSNumber*) index +{ + WebCoreTextMarkerRange* textMarkerRange = [self doAXStyleTextMarkerRangeForTextMarker: [self textMarkerForIndex: index lastIndexOK: NO]]; + return [self rangeForTextMarkerRange: textMarkerRange]; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter +{ + WebCoreTextMarker* textMarker = nil; + WebCoreTextMarkerRange* textMarkerRange = nil; + NSNumber* number = nil; + NSArray* array = nil; + WebCoreAXObject* uiElement = nil; + NSPoint point = NSZeroPoint; + bool pointSet = false; + NSRange range = {0, 0}; + bool rangeSet = false; + + // basic parameter validation + if (!m_renderer || !attribute || !parameter) + return nil; + + // common parameter type check/casting. Nil checks in handlers catch wrong type case. + // NOTE: This assumes nil is not a valid parameter, because it is indistinguishable from + // a parameter of the wrong type. + if ([[WebCoreViewFactory sharedFactory] objectIsTextMarker:parameter]) + textMarker = (WebCoreTextMarker*) parameter; + + else if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:parameter]) + textMarkerRange = (WebCoreTextMarkerRange*) parameter; + + else if ([parameter isKindOfClass:[WebCoreAXObject self]]) + uiElement = (WebCoreAXObject*) parameter; + + else if ([parameter isKindOfClass:[NSNumber self]]) + number = parameter; + + else if ([parameter isKindOfClass:[NSArray self]]) + array = parameter; + + else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) { + pointSet = true; + point = [(NSValue*)parameter pointValue]; + + } else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSRange)) == 0) { + rangeSet = true; + range = [(NSValue*)parameter rangeValue]; + + } else { + // got a parameter of a type we never use + // NOTE: No ASSERT_NOT_REACHED because this can happen accidentally + // while using accesstool (e.g.), forcing you to start over + return nil; + } + + // dispatch + if ([attribute isEqualToString: @"AXUIElementForTextMarker"]) + return [self doAXUIElementForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXTextMarkerRangeForUIElement"]) + return [self doAXTextMarkerRangeForUIElement: uiElement]; + + if ([attribute isEqualToString: @"AXLineForTextMarker"]) + return [self doAXLineForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXTextMarkerRangeForLine"]) + return [self doAXTextMarkerRangeForLine: number]; + + if ([attribute isEqualToString: @"AXStringForTextMarkerRange"]) + return [self doAXStringForTextMarkerRange: textMarkerRange]; + + if ([attribute isEqualToString: @"AXTextMarkerForPosition"]) + return pointSet ? [self doAXTextMarkerForPosition: point] : nil; + + if ([attribute isEqualToString: @"AXBoundsForTextMarkerRange"]) + return [self doAXBoundsForTextMarkerRange: textMarkerRange]; + + if ([attribute isEqualToString: @"AXAttributedStringForTextMarkerRange"]) + return [self doAXAttributedStringForTextMarkerRange: textMarkerRange]; + + if ([attribute isEqualToString: @"AXTextMarkerRangeForUnorderedTextMarkers"]) + return [self doAXTextMarkerRangeForUnorderedTextMarkers: array]; + + if ([attribute isEqualToString: @"AXNextTextMarkerForTextMarker"]) + return [self doAXNextTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXPreviousTextMarkerForTextMarker"]) + return [self doAXPreviousTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXLeftWordTextMarkerRangeForTextMarker"]) + return [self doAXLeftWordTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXRightWordTextMarkerRangeForTextMarker"]) + return [self doAXRightWordTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXLeftLineTextMarkerRangeForTextMarker"]) + return [self doAXLeftLineTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXRightLineTextMarkerRangeForTextMarker"]) + return [self doAXRightLineTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXSentenceTextMarkerRangeForTextMarker"]) + return [self doAXSentenceTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXParagraphTextMarkerRangeForTextMarker"]) + return [self doAXParagraphTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXNextWordEndTextMarkerForTextMarker"]) + return [self doAXNextWordEndTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXPreviousWordStartTextMarkerForTextMarker"]) + return [self doAXPreviousWordStartTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXNextLineEndTextMarkerForTextMarker"]) + return [self doAXNextLineEndTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXPreviousLineStartTextMarkerForTextMarker"]) + return [self doAXPreviousLineStartTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXNextSentenceEndTextMarkerForTextMarker"]) + return [self doAXNextSentenceEndTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXPreviousSentenceStartTextMarkerForTextMarker"]) + return [self doAXPreviousSentenceStartTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXNextParagraphEndTextMarkerForTextMarker"]) + return [self doAXNextParagraphEndTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXPreviousParagraphStartTextMarkerForTextMarker"]) + return [self doAXPreviousParagraphStartTextMarkerForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXStyleTextMarkerRangeForTextMarker"]) + return [self doAXStyleTextMarkerRangeForTextMarker: textMarker]; + + if ([attribute isEqualToString: @"AXLengthForTextMarkerRange"]) + return [self doAXLengthForTextMarkerRange: textMarkerRange]; + + if ([self isTextControl]) { + if ([attribute isEqualToString: (NSString*)kAXLineForIndexParameterizedAttribute]) + return [self doAXLineForIndex: number]; + + if ([attribute isEqualToString: (NSString*)kAXRangeForLineParameterizedAttribute]) + return [self doAXRangeForLine: number]; + + if ([attribute isEqualToString: (NSString*)kAXStringForRangeParameterizedAttribute]) + return rangeSet ? [self doAXStringForRange: range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute]) + return pointSet ? [self doAXRangeForPosition: point] : nil; + + if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute]) + return [self doAXRangeForIndex: number]; + + if ([attribute isEqualToString: (NSString*)kAXBoundsForRangeParameterizedAttribute]) + return rangeSet ? [self doAXBoundsForRange: range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXRTFForRangeParameterizedAttribute]) + return rangeSet ? [self doAXRTFForRange: range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXAttributedStringForRangeParameterizedAttribute]) + return rangeSet ? [self doAXAttributedStringForRange: range] : nil; + + if ([attribute isEqualToString: (NSString*)kAXStyleRangeForIndexParameterizedAttribute]) + return [self doAXStyleRangeForIndex: number]; + } + + return nil; +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + if (!m_renderer) + return NSAccessibilityUnignoredAncestor(self); + + HitTestRequest request(true, true); + HitTestResult result = HitTestResult(IntPoint(point)); + m_renderer->layer()->hitTest(request, result); + if (!result.innerNode()) + return NSAccessibilityUnignoredAncestor(self); + Node* node = result.innerNode()->shadowAncestorNode(); + RenderObject* obj = node->renderer(); + if (!obj) + return NSAccessibilityUnignoredAncestor(self); + + return NSAccessibilityUnignoredAncestor(obj->document()->axObjectCache()->get(obj)); +} + +- (RenderObject*)rendererForView:(NSView*)view +{ + // check for WebKit NSView that lets us find its bridge + WebCoreFrameBridge* bridge = nil; + if ([view conformsToProtocol:@protocol(WebCoreBridgeHolder)]) { + NSView<WebCoreBridgeHolder>* bridgeHolder = (NSView<WebCoreBridgeHolder>*)view; + bridge = [bridgeHolder webCoreBridge]; + } + + Frame* frame = [bridge _frame]; + if (!frame) + return nil; + + Document* document = frame->document(); + if (!document) + return nil; + + Node* node = document->ownerElement(); + if (!node) + return nil; + + return node->renderer(); +} + +// _accessibilityParentForSubview is called by AppKit when moving up the tree +// we override it so that we can return our WebCoreAXObject parent of an AppKit AX object +- (id)_accessibilityParentForSubview:(NSView*)subview +{ + RenderObject* renderer = [self rendererForView:subview]; + if (!renderer) + return nil; + + WebCoreAXObject* obj = renderer->document()->axObjectCache()->get(renderer); + return [obj parentObjectUnignored]; +} + +- (id)accessibilityFocusedUIElement +{ + // get the focused node in the page + Page* page = m_renderer->document()->page(); + if (!page) + return nil; + + Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); + Node* focusedNode = focusedDocument->focusedNode(); + if (!focusedNode || !focusedNode->renderer()) + focusedNode = focusedDocument; + + WebCoreAXObject* obj = focusedNode->renderer()->document()->axObjectCache()->get(focusedNode->renderer()); + + // the HTML element, for example, is focusable but has an AX object that is ignored + if ([obj accessibilityIsIgnored]) + obj = [obj parentObjectUnignored]; + + return obj; +} + +- (void)doSetAXSelectedTextMarkerRange: (WebCoreTextMarkerRange*)textMarkerRange +{ + // extract the start and end VisiblePosition + VisiblePosition startVisiblePosition = [self visiblePositionForStartOfTextMarkerRange: textMarkerRange]; + if (startVisiblePosition.isNull()) + return; + + VisiblePosition endVisiblePosition = [self visiblePositionForEndOfTextMarkerRange: textMarkerRange]; + if (endVisiblePosition.isNull()) + return; + + // make selection and tell the document to use it + Selection newSelection = Selection(startVisiblePosition, endVisiblePosition); + m_renderer->document()->frame()->selectionController()->setSelection(newSelection); +} + +- (BOOL)canSetFocusAttribute +{ + // NOTE: It would be more accurate to ask the document whether setFocusedNode() would + // do anything. For example, it setFocusedNode() will do nothing if the current focused + // node will not relinquish the focus. + if (!m_renderer->element() || !m_renderer->element()->isEnabled()) + return NO; + + NSString* role = [self role]; + if ([role isEqualToString:@"AXLink"] || + [role isEqualToString:NSAccessibilityTextFieldRole] || + [role isEqualToString:NSAccessibilityTextAreaRole] || + [role isEqualToString:NSAccessibilityButtonRole] || + [role isEqualToString:NSAccessibilityPopUpButtonRole] || + [role isEqualToString:NSAccessibilityCheckBoxRole] || + [role isEqualToString:NSAccessibilityRadioButtonRole]) + return YES; + + return NO; +} + +- (BOOL)canSetValueAttribute +{ + return [self isTextControl]; +} + +- (BOOL)canSetTextRangeAttributes +{ + return [self isTextControl]; +} + +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName +{ + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) + return YES; + + if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) + return [self canSetFocusAttribute]; + + if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) + return [self canSetValueAttribute]; + + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute] || + [attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute] || + [attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) + return [self canSetTextRangeAttributes]; + + return NO; +} + +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName +{ + WebCoreTextMarkerRange* textMarkerRange = nil; + NSNumber* number = nil; + NSString* string = nil; + NSRange range = {0, 0}; + + // decode the parameter + if ([[WebCoreViewFactory sharedFactory] objectIsTextMarkerRange:value]) + textMarkerRange = (WebCoreTextMarkerRange*) value; + + else if ([value isKindOfClass:[NSNumber self]]) + number = value; + + else if ([value isKindOfClass:[NSString self]]) + string = value; + + else if ([value isKindOfClass:[NSValue self]]) + range = [value rangeValue]; + + // handle the command + if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) { + ASSERT(textMarkerRange); + [self doSetAXSelectedTextMarkerRange:textMarkerRange]; + + } else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) { + ASSERT(number); + if ([self canSetFocusAttribute] && number) { + if ([number intValue] == 0) + m_renderer->document()->setFocusedNode(0); + else { + if (m_renderer->element()->isElementNode()) + static_cast<Element*>(m_renderer->element())->focus(); + else + m_renderer->document()->setFocusedNode(m_renderer->element()); + } + } + } else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) { + if (!string) + return; + if (m_renderer->isTextField()) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); + input->setValue(string); + } else if (m_renderer->isTextArea()) { + HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->element()); + textArea->setValue(string); + } + } else if ([self isTextControl]) { + RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); + if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { + // TODO: set selected text (ReplaceSelectionCommand). <rdar://problem/4712125> + } else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) { + textControl->setSelectionRange(range.location, range.location + range.length); + } else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) { + // TODO: make range visible (scrollRectToVisible). <rdar://problem/4712101> + } + } +} + +- (WebCoreAXObject*)observableObject +{ + for (RenderObject* renderer = m_renderer; renderer && renderer->element(); renderer = renderer->parent()) { + if (renderer->isTextField() || renderer->isTextArea()) + return renderer->document()->axObjectCache()->get(renderer); + } + + return nil; +} + +- (void)childrenChanged +{ + [self clearChildren]; + + if ([self accessibilityIsIgnored]) + [[self parentObject] childrenChanged]; +} + +- (void)clearChildren +{ + [m_children release]; + m_children = nil; +} + +-(AXID)axObjectID +{ + return m_id; +} + +-(void)setAXObjectID:(AXID) axObjectID +{ + m_id = axObjectID; +} + +- (void)removeAXObjectID +{ + if (!m_id) + return; + + m_renderer->document()->axObjectCache()->removeAXID(self); +} + +@end diff --git a/WebCore/page/mac/WebCoreFrameBridge.h b/WebCore/page/mac/WebCoreFrameBridge.h new file mode 100644 index 0000000..76315f7 --- /dev/null +++ b/WebCore/page/mac/WebCoreFrameBridge.h @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import <Cocoa/Cocoa.h> +#import <JavaVM/jni.h> +#import <WebCore/WebCoreKeyboardUIMode.h> +#import <WebCore/EditAction.h> +#import <WebCore/FrameLoaderTypes.h> +#import <WebCore/SelectionController.h> +#import <WebCore/TextAffinity.h> +#import <WebCore/TextGranularity.h> + +#if USE(NPOBJECT) +#import <JavaScriptCore/npruntime.h> +#endif + +namespace WebCore { + class Frame; + class HTMLFrameOwnerElement; + class Page; + class String; +} + +@class DOMCSSStyleDeclaration; +@class DOMDocument; +@class DOMDocumentFragment; +@class DOMElement; +@class DOMHTMLInputElement; +@class DOMHTMLTextAreaElement; +@class DOMNode; +@class DOMRange; +@class NSMenu; + +@protocol WebCoreRenderTreeCopier; + +enum WebCoreDeviceType { + WebCoreDeviceScreen, + WebCoreDevicePrinter +}; + +enum WebScrollDirection { + WebScrollUp, + WebScrollDown, + WebScrollLeft, + WebScrollRight +}; + +enum WebScrollGranularity { + WebScrollLine, + WebScrollPage, + WebScrollDocument, + WebScrollWheel +}; + +@protocol WebCoreOpenPanelResultListener <NSObject> +- (void)chooseFilename:(NSString *)fileName; +- (void)cancel; +@end + +// WebCoreFrameBridge objects are used by WebCore to abstract away operations that need +// to be implemented by library clients, for example WebKit. The objects are also +// used in the opposite direction, for simple access to WebCore functions without dealing +// directly with the KHTML C++ classes. + +// A WebCoreFrameBridge creates and holds a reference to a Frame. + +// The WebCoreFrameBridge interface contains methods for use by the non-WebCore side of the bridge. + +@interface WebCoreFrameBridge : NSObject +{ +@public + WebCore::Frame* m_frame; + BOOL _shouldCreateRenderers; + BOOL _closed; +} + +- (WebCore::Frame*)_frame; // underscore to prevent conflict with -[NSView frame] + ++ (WebCoreFrameBridge *)bridgeForDOMDocument:(DOMDocument *)document; + +- (id)init; +- (void)close; + +- (void)clearFrame; + +- (NSURL *)baseURL; + +- (void)installInFrame:(NSView *)view; + +- (BOOL)scrollOverflowInDirection:(WebScrollDirection)direction granularity:(WebScrollGranularity)granularity; + +- (void)createFrameViewWithNSView:(NSView *)view marginWidth:(int)mw marginHeight:(int)mh; + +- (void)reapplyStylesForDeviceType:(WebCoreDeviceType)deviceType; +- (void)forceLayoutAdjustingViewSize:(BOOL)adjustSizeFlag; +- (void)forceLayoutWithMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustSizeFlag; +- (void)sendScrollEvent; +- (BOOL)needsLayout; +- (void)drawRect:(NSRect)rect; +- (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit; +- (NSArray*)computePageRectsWithPrintWidthScaleFactor:(float)printWidthScaleFactor printHeight:(float)printHeight; + +- (NSObject *)copyRenderTree:(id <WebCoreRenderTreeCopier>)copier; +- (NSString *)renderTreeAsExternalRepresentation; + +- (NSURL *)URLWithAttributeString:(NSString *)string; + +- (DOMElement *)elementWithName:(NSString *)name inForm:(DOMElement *)form; +- (BOOL)elementDoesAutoComplete:(DOMElement *)element; +- (BOOL)elementIsPassword:(DOMElement *)element; +- (DOMElement *)formForElement:(DOMElement *)element; +- (DOMElement *)currentForm; +- (NSArray *)controlsInForm:(DOMElement *)form; +- (NSString *)searchForLabels:(NSArray *)labels beforeElement:(DOMElement *)element; +- (NSString *)matchLabels:(NSArray *)labels againstElement:(DOMElement *)element; + +- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection; +- (unsigned)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(unsigned)limit; +- (BOOL)markedTextMatchesAreHighlighted; +- (void)setMarkedTextMatchesAreHighlighted:(BOOL)doHighlight; +- (void)unmarkAllTextMatches; +- (NSArray *)rectsForTextMatches; + +- (void)setTextSizeMultiplier:(float)multiplier; + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string; +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string forceUserGesture:(BOOL)forceUserGesture; +- (NSAppleEventDescriptor *)aeDescByEvaluatingJavaScriptFromString:(NSString *)string; + +- (NSString *)selectedString; + +- (NSString *)stringForRange:(DOMRange *)range; + +- (NSString *)markupStringFromNode:(DOMNode *)node nodes:(NSArray **)nodes; +- (NSString *)markupStringFromRange:(DOMRange *)range nodes:(NSArray **)nodes; + +- (NSRect)caretRectAtNode:(DOMNode *)node offset:(int)offset affinity:(NSSelectionAffinity)affinity; +- (NSRect)firstRectForDOMRange:(DOMRange *)range; +- (void)scrollDOMRangeToVisible:(DOMRange *)range; + +- (NSFont *)fontForSelection:(BOOL *)hasMultipleFonts; + +- (NSString *)stringWithData:(NSData *)data; // using the encoding of the frame's main resource ++ (NSString *)stringWithData:(NSData *)data textEncodingName:(NSString *)textEncodingName; // nil for textEncodingName means Latin-1 + +- (void)setShouldCreateRenderers:(BOOL)shouldCreateRenderers; + +- (void)setBaseBackgroundColor:(NSColor *)backgroundColor; +- (void)setDrawsBackground:(BOOL)drawsBackround; + +- (id)accessibilityTree; + +- (DOMRange *)rangeByAlteringCurrentSelection:(WebCore::SelectionController::EAlteration)alteration direction:(WebCore::SelectionController::EDirection)direction granularity:(WebCore::TextGranularity)granularity; +- (WebCore::TextGranularity)selectionGranularity; +- (void)smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)charRangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString; +- (void)selectNSRange:(NSRange)range; +- (NSRange)selectedNSRange; +- (NSRange)markedTextNSRange; +- (DOMRange *)convertNSRangeToDOMRange:(NSRange)range; +- (NSRange)convertDOMRangeToNSRange:(DOMRange *)range; + +- (DOMDocumentFragment *)documentFragmentWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString; +- (DOMDocumentFragment *)documentFragmentWithText:(NSString *)text inContext:(DOMRange *)context; +- (DOMDocumentFragment *)documentFragmentWithNodesAsParagraphs:(NSArray *)nodes; + +- (void)replaceSelectionWithFragment:(DOMDocumentFragment *)fragment selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle; +- (void)replaceSelectionWithNode:(DOMNode *)node selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle; +- (void)replaceSelectionWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace; +- (void)replaceSelectionWithText:(NSString *)text selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace; + +- (void)insertParagraphSeparatorInQuotedContent; + +- (DOMRange *)characterRangeAtPoint:(NSPoint)point; + +- (DOMCSSStyleDeclaration *)typingStyle; +- (void)setTypingStyle:(DOMCSSStyleDeclaration *)style withUndoAction:(WebCore::EditAction)undoAction; + +- (void)dragSourceMovedTo:(NSPoint)windowLoc; +- (void)dragSourceEndedAt:(NSPoint)windowLoc operation:(NSDragOperation)operation; + +- (BOOL)getData:(NSData **)data andResponse:(NSURLResponse **)response forURL:(NSString *)URL; +- (void)getAllResourceDatas:(NSArray **)datas andResponses:(NSArray **)responses; + +- (BOOL)canProvideDocumentSource; +- (BOOL)canSaveAsWebArchive; + +- (void)receivedData:(NSData *)data textEncodingName:(NSString *)textEncodingName; + +@end + +// The WebCoreFrameBridge protocol contains methods for use by the WebCore side of the bridge. + +@protocol WebCoreFrameBridge + +- (NSWindow *)window; + +- (NSResponder *)firstResponder; +- (void)makeFirstResponder:(NSResponder *)responder; + +- (void)runOpenPanelForFileButtonWithResultListener:(id <WebCoreOpenPanelResultListener>)resultListener; + +- (jobject)getAppletInView:(NSView *)view; + +// Deprecated, use getAppletInView: instead. +- (jobject)pollForAppletInView:(NSView *)view; + +- (void)issuePasteCommand; + +- (void)setIsSelected:(BOOL)isSelected forView:(NSView *)view; + +- (void)dashboardRegionsChanged:(NSMutableDictionary *)regions; +- (void)willPopupMenu:(NSMenu *)menu; + +- (NSRect)customHighlightRect:(NSString*)type forLine:(NSRect)lineRect representedNode:(WebCore::Node *)node; +- (void)paintCustomHighlight:(NSString*)type forBox:(NSRect)boxRect onLine:(NSRect)lineRect behindText:(BOOL)text entireLine:(BOOL)line representedNode:(WebCore::Node *)node; + +- (WebCore::KeyboardUIMode)keyboardUIMode; + +@end + +// This interface definition allows those who hold a WebCoreFrameBridge * to call all the methods +// in the WebCoreFrameBridge protocol without requiring the base implementation to supply the methods. +// This idiom is appropriate because WebCoreFrameBridge is an abstract class. + +@interface WebCoreFrameBridge (SubclassResponsibility) <WebCoreFrameBridge> +@end + +// Protocols that make up part of the interfaces above. + +@protocol WebCoreRenderTreeCopier <NSObject> +- (NSObject *)nodeWithName:(NSString *)name position:(NSPoint)p rect:(NSRect)rect view:(NSView *)view children:(NSArray *)children; +@end diff --git a/WebCore/page/mac/WebCoreFrameBridge.mm b/WebCore/page/mac/WebCoreFrameBridge.mm new file mode 100644 index 0000000..3f20706 --- /dev/null +++ b/WebCore/page/mac/WebCoreFrameBridge.mm @@ -0,0 +1,1242 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2006 David Smith (catfish.man@gmail.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "WebCoreFrameBridge.h" + +#import "AXObjectCache.h" +#import "CSSHelper.h" +#import "Cache.h" +#import "ClipboardMac.h" +#import "ColorMac.h" +#import "DOMImplementation.h" +#import "DOMInternal.h" +#import "DOMWindow.h" +#import "DeleteSelectionCommand.h" +#import "DocLoader.h" +#import "DocumentFragment.h" +#import "DocumentLoader.h" +#import "DocumentType.h" +#import "Editor.h" +#import "EditorClient.h" +#import "EventHandler.h" +#import "FloatRect.h" +#import "FormDataStreamMac.h" +#import "Frame.h" +#import "FrameLoader.h" +#import "FrameLoaderClient.h" +#import "FrameTree.h" +#import "FrameView.h" +#import "GraphicsContext.h" +#import "HTMLDocument.h" +#import "HTMLFormElement.h" +#import "HTMLInputElement.h" +#import "HTMLNames.h" +#import "HitTestResult.h" +#import "Image.h" +#import "LoaderNSURLExtras.h" +#import "MoveSelectionCommand.h" +#import "Page.h" +#import "PlatformMouseEvent.h" +#import "PlatformScreen.h" +#import "PluginInfoStore.h" +#import "RenderImage.h" +#import "RenderPart.h" +#import "RenderTreeAsText.h" +#import "RenderView.h" +#import "RenderWidget.h" +#import "ReplaceSelectionCommand.h" +#import "ResourceRequest.h" +#import "SelectionController.h" +#import "SimpleFontData.h" +#import "SmartReplace.h" +#import "SubresourceLoader.h" +#import "SystemTime.h" +#import "Text.h" +#import "TextEncoding.h" +#import "TextIterator.h" +#import "TextResourceDecoder.h" +#import "TypingCommand.h" +#import "WebCoreViewFactory.h" +#import "XMLTokenizer.h" +#import "htmlediting.h" +#import "kjs_proxy.h" +#import "kjs_window.h" +#import "markup.h" +#import "visible_units.h" +#import <OpenScripting/ASRegistry.h> +#import <JavaScriptCore/array_instance.h> +#import <JavaScriptCore/date_object.h> +#import <JavaScriptCore/runtime_root.h> +#import <wtf/RetainPtr.h> + +@class NSView; + +using namespace std; +using namespace WebCore; +using namespace HTMLNames; + +using KJS::ArrayInstance; +using KJS::BooleanType; +using KJS::DateInstance; +using KJS::ExecState; +using KJS::GetterSetterType; +using KJS::JSImmediate; +using KJS::JSLock; +using KJS::JSObject; +using KJS::JSValue; +using KJS::NullType; +using KJS::NumberType; +using KJS::ObjectType; +using KJS::SavedBuiltins; +using KJS::SavedProperties; +using KJS::StringType; +using KJS::UndefinedType; +using KJS::UnspecifiedType; +using KJS::Window; + +using KJS::Bindings::RootObject; + +static PassRefPtr<RootObject> createRootObject(void* nativeHandle) +{ + NSView *view = (NSView *)nativeHandle; + WebCoreFrameBridge *bridge = [[WebCoreViewFactory sharedFactory] bridgeForView:view]; + if (!bridge) + return 0; + + Frame* frame = [bridge _frame]; + return frame->createRootObject(nativeHandle, frame->scriptProxy()->globalObject()); +} + +static pthread_t mainThread = 0; + +static void updateRenderingForBindings(ExecState* exec, JSObject* rootObject) +{ + if (pthread_self() != mainThread) + return; + + if (!rootObject) + return; + + Window* window = static_cast<Window*>(rootObject); + if (!window) + return; + + if (Frame* frame = window->impl()->frame()) + if (Document* doc = frame->document()) + doc->updateRendering(); +} + +static NSAppleEventDescriptor* aeDescFromJSValue(ExecState* exec, JSValue* jsValue) +{ + NSAppleEventDescriptor* aeDesc = 0; + switch (jsValue->type()) { + case BooleanType: + aeDesc = [NSAppleEventDescriptor descriptorWithBoolean:jsValue->getBoolean()]; + break; + case StringType: + aeDesc = [NSAppleEventDescriptor descriptorWithString:String(jsValue->getString())]; + break; + case NumberType: { + double value = jsValue->getNumber(); + int intValue = (int)value; + if (value == intValue) + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeSInt32 bytes:&intValue length:sizeof(intValue)]; + else + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeIEEE64BitFloatingPoint bytes:&value length:sizeof(value)]; + break; + } + case ObjectType: { + JSObject* object = jsValue->getObject(); + if (object->inherits(&DateInstance::info)) { + DateInstance* date = static_cast<DateInstance*>(object); + double ms = 0; + int tzOffset = 0; + if (date->getTime(ms, tzOffset)) { + CFAbsoluteTime utcSeconds = ms / 1000 - kCFAbsoluteTimeIntervalSince1970; + LongDateTime ldt; + if (noErr == UCConvertCFAbsoluteTimeToLongDateTime(utcSeconds, &ldt)) + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeLongDateTime bytes:&ldt length:sizeof(ldt)]; + } + } + else if (object->inherits(&ArrayInstance::info)) { + static HashSet<JSObject*> visitedElems; + if (!visitedElems.contains(object)) { + visitedElems.add(object); + + ArrayInstance* array = static_cast<ArrayInstance*>(object); + aeDesc = [NSAppleEventDescriptor listDescriptor]; + unsigned numItems = array->getLength(); + for (unsigned i = 0; i < numItems; ++i) + [aeDesc insertDescriptor:aeDescFromJSValue(exec, array->getItem(i)) atIndex:0]; + + visitedElems.remove(object); + } + } + if (!aeDesc) { + JSValue* primitive = object->toPrimitive(exec); + if (exec->hadException()) { + exec->clearException(); + return [NSAppleEventDescriptor nullDescriptor]; + } + return aeDescFromJSValue(exec, primitive); + } + break; + } + case UndefinedType: + aeDesc = [NSAppleEventDescriptor descriptorWithTypeCode:cMissingValue]; + break; + default: + LOG_ERROR("Unknown JavaScript type: %d", jsValue->type()); + // no break; + case UnspecifiedType: + case NullType: + case GetterSetterType: + aeDesc = [NSAppleEventDescriptor nullDescriptor]; + break; + } + + return aeDesc; +} + +@implementation WebCoreFrameBridge + +static inline WebCoreFrameBridge *bridge(Frame *frame) +{ + if (!frame) + return nil; + return frame->bridge(); +} + +- (NSString *)domain +{ + Document *doc = m_frame->document(); + if (doc) + return doc->domain(); + return nil; +} + ++ (WebCoreFrameBridge *)bridgeForDOMDocument:(DOMDocument *)document +{ + return bridge([document _document]->frame()); +} + +- (id)init +{ + static bool initializedKJS; + if (!initializedKJS) { + initializedKJS = true; + + mainThread = pthread_self(); + RootObject::setCreateRootObject(createRootObject); + KJS::Bindings::Instance::setDidExecuteFunction(updateRenderingForBindings); + } + + if (!(self = [super init])) + return nil; + + _shouldCreateRenderers = YES; + return self; +} + +- (void)dealloc +{ + ASSERT(_closed); + [super dealloc]; +} + +- (void)finalize +{ + ASSERT(_closed); + [super finalize]; +} + +- (void)close +{ + [self clearFrame]; + _closed = YES; +} + +- (void)addData:(NSData *)data +{ + Document *doc = m_frame->document(); + + // Document may be nil if the part is about to redirect + // as a result of JS executing during load, i.e. one frame + // changing another's location before the frame's document + // has been created. + if (doc) { + doc->setShouldCreateRenderers(_shouldCreateRenderers); + m_frame->loader()->addData((const char *)[data bytes], [data length]); + } +} + +- (BOOL)scrollOverflowInDirection:(WebScrollDirection)direction granularity:(WebScrollGranularity)granularity +{ + if (!m_frame) + return NO; + return m_frame->eventHandler()->scrollOverflow((ScrollDirection)direction, (ScrollGranularity)granularity); +} + +- (void)clearFrame +{ + m_frame = 0; +} + +- (void)createFrameViewWithNSView:(NSView *)view marginWidth:(int)mw marginHeight:(int)mh +{ + // If we own the view, delete the old one - otherwise the render m_frame will take care of deleting the view. + if (m_frame) + m_frame->setView(0); + + FrameView* frameView = new FrameView(m_frame); + m_frame->setView(frameView); + frameView->deref(); + + frameView->setView(view); + if (mw >= 0) + frameView->setMarginWidth(mw); + if (mh >= 0) + frameView->setMarginHeight(mh); +} + +- (NSString *)_stringWithDocumentTypeStringAndMarkupString:(NSString *)markupString +{ + return m_frame->documentTypeString() + markupString; +} + +- (NSArray *)nodesFromList:(Vector<Node*> *)nodesVector +{ + size_t size = nodesVector->size(); + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:size]; + for (size_t i = 0; i < size; ++i) + [nodes addObject:[DOMNode _wrapNode:(*nodesVector)[i]]]; + return nodes; +} + +- (NSString *)markupStringFromNode:(DOMNode *)node nodes:(NSArray **)nodes +{ + // FIXME: This is never "for interchange". Is that right? See the next method. + Vector<Node*> nodeList; + NSString *markupString = createMarkup([node _node], IncludeNode, nodes ? &nodeList : 0); + if (nodes) + *nodes = [self nodesFromList:&nodeList]; + + return [self _stringWithDocumentTypeStringAndMarkupString:markupString]; +} + +- (NSString *)markupStringFromRange:(DOMRange *)range nodes:(NSArray **)nodes +{ + // FIXME: This is always "for interchange". Is that right? See the previous method. + Vector<Node*> nodeList; + NSString *markupString = createMarkup([range _range], nodes ? &nodeList : 0, AnnotateForInterchange); + if (nodes) + *nodes = [self nodesFromList:&nodeList]; + + return [self _stringWithDocumentTypeStringAndMarkupString:markupString]; +} + +- (NSString *)selectedString +{ + String text = m_frame->selectedText(); + text.replace('\\', m_frame->backslashAsCurrencySymbol()); + return text; +} + +- (NSString *)stringForRange:(DOMRange *)range +{ + // This will give a system malloc'd buffer that can be turned directly into an NSString + unsigned length; + UChar* buf = plainTextToMallocAllocatedBuffer([range _range], length); + + if (!buf) + return [NSString string]; + + UChar backslashAsCurrencySymbol = m_frame->backslashAsCurrencySymbol(); + if (backslashAsCurrencySymbol != '\\') + for (unsigned n = 0; n < length; n++) + if (buf[n] == '\\') + buf[n] = backslashAsCurrencySymbol; + + // Transfer buffer ownership to NSString + return [[[NSString alloc] initWithCharactersNoCopy:buf length:length freeWhenDone:YES] autorelease]; +} + +- (void)reapplyStylesForDeviceType:(WebCoreDeviceType)deviceType +{ + if (m_frame->view()) + m_frame->view()->setMediaType(deviceType == WebCoreDeviceScreen ? "screen" : "print"); + Document *doc = m_frame->document(); + if (doc) + doc->setPrinting(deviceType == WebCoreDevicePrinter); + m_frame->reapplyStyles(); +} + +- (void)forceLayoutAdjustingViewSize:(BOOL)flag +{ + m_frame->forceLayout(!flag); + if (flag) + m_frame->view()->adjustViewSize(); +} + +- (void)forceLayoutWithMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)flag +{ + m_frame->forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, flag); +} + +- (void)sendScrollEvent +{ + m_frame->sendScrollEvent(); +} + +- (void)drawRect:(NSRect)rect +{ + PlatformGraphicsContext* platformContext = static_cast<PlatformGraphicsContext*>([[NSGraphicsContext currentContext] graphicsPort]); + ASSERT([[NSGraphicsContext currentContext] isFlipped]); + GraphicsContext context(platformContext); + + m_frame->paint(&context, enclosingIntRect(rect)); +} + +// Used by pagination code called from AppKit when a standalone web page is printed. +- (NSArray*)computePageRectsWithPrintWidthScaleFactor:(float)printWidthScaleFactor printHeight:(float)printHeight +{ + NSMutableArray* pages = [NSMutableArray arrayWithCapacity:5]; + if (printWidthScaleFactor <= 0) { + LOG_ERROR("printWidthScaleFactor has bad value %.2f", printWidthScaleFactor); + return pages; + } + + if (printHeight <= 0) { + LOG_ERROR("printHeight has bad value %.2f", printHeight); + return pages; + } + + if (!m_frame || !m_frame->document() || !m_frame->view()) return pages; + RenderView* root = static_cast<RenderView *>(m_frame->document()->renderer()); + if (!root) return pages; + + FrameView* view = m_frame->view(); + if (!view) + return pages; + + NSView* documentView = view->getDocumentView(); + if (!documentView) + return pages; + + float currPageHeight = printHeight; + float docHeight = root->layer()->height(); + float docWidth = root->layer()->width(); + float printWidth = docWidth/printWidthScaleFactor; + + // We need to give the part the opportunity to adjust the page height at each step. + for (float i = 0; i < docHeight; i += currPageHeight) { + float proposedBottom = min(docHeight, i + printHeight); + m_frame->adjustPageHeight(&proposedBottom, i, proposedBottom, i); + currPageHeight = max(1.0f, proposedBottom - i); + for (float j = 0; j < docWidth; j += printWidth) { + NSValue* val = [NSValue valueWithRect: NSMakeRect(j, i, printWidth, currPageHeight)]; + [pages addObject: val]; + } + } + + return pages; +} + +// This is to support the case where a webview is embedded in the view that's being printed +- (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit +{ + m_frame->adjustPageHeight(newBottom, oldTop, oldBottom, bottomLimit); +} + +- (NSObject *)copyRenderNode:(RenderObject *)node copier:(id <WebCoreRenderTreeCopier>)copier +{ + NSMutableArray *children = [[NSMutableArray alloc] init]; + for (RenderObject *child = node->firstChild(); child; child = child->nextSibling()) { + [children addObject:[self copyRenderNode:child copier:copier]]; + } + + NSString *name = [[NSString alloc] initWithUTF8String:node->renderName()]; + + RenderWidget* renderWidget = node->isWidget() ? static_cast<RenderWidget*>(node) : 0; + Widget* widget = renderWidget ? renderWidget->widget() : 0; + NSView *view = widget ? widget->getView() : nil; + + int nx, ny; + node->absolutePosition(nx, ny); + NSObject *copiedNode = [copier nodeWithName:name + position:NSMakePoint(nx,ny) + rect:NSMakeRect(node->xPos(), node->yPos(), node->width(), node->height()) + view:view + children:children]; + + [name release]; + [children release]; + + return copiedNode; +} + +- (NSObject *)copyRenderTree:(id <WebCoreRenderTreeCopier>)copier +{ + RenderObject *renderer = m_frame->renderer(); + if (!renderer) { + return nil; + } + return [self copyRenderNode:renderer copier:copier]; +} + +- (void)installInFrame:(NSView *)view +{ + // If this isn't the main frame, it must have a render m_frame set, or it + // won't ever get installed in the view hierarchy. + ASSERT(m_frame == m_frame->page()->mainFrame() || m_frame->ownerElement()); + + m_frame->view()->setView(view); + // FIXME: frame tries to do this too, is it needed? + if (m_frame->ownerRenderer()) { + m_frame->ownerRenderer()->setWidget(m_frame->view()); + // Now the render part owns the view, so we don't any more. + } + + m_frame->view()->initScrollbars(); +} + +static HTMLInputElement* inputElementFromDOMElement(DOMElement* element) +{ + Node* node = [element _node]; + if (node->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(node); + return nil; +} + +static HTMLFormElement *formElementFromDOMElement(DOMElement *element) +{ + Node *node = [element _node]; + // This should not be necessary, but an XSL file on + // maps.google.com crashes otherwise because it is an xslt file + // that contains <form> elements that aren't in any namespace, so + // they come out as generic CML elements + if (node && node->hasTagName(formTag)) { + return static_cast<HTMLFormElement *>(node); + } + return nil; +} + +- (DOMElement *)elementWithName:(NSString *)name inForm:(DOMElement *)form +{ + HTMLFormElement *formElement = formElementFromDOMElement(form); + if (formElement) { + Vector<HTMLGenericFormElement*>& elements = formElement->formElements; + AtomicString targetName = name; + for (unsigned int i = 0; i < elements.size(); i++) { + HTMLGenericFormElement *elt = elements[i]; + // Skip option elements, other duds + if (elt->name() == targetName) + return [DOMElement _wrapElement:elt]; + } + } + return nil; +} + +- (BOOL)elementDoesAutoComplete:(DOMElement *)element +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + return inputElement != nil + && inputElement->inputType() == HTMLInputElement::TEXT + && inputElement->autoComplete(); +} + +- (BOOL)elementIsPassword:(DOMElement *)element +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + return inputElement != nil + && inputElement->inputType() == HTMLInputElement::PASSWORD; +} + +- (DOMElement *)formForElement:(DOMElement *)element; +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + if (inputElement) { + HTMLFormElement *formElement = inputElement->form(); + if (formElement) { + return [DOMElement _wrapElement:formElement]; + } + } + return nil; +} + +- (DOMElement *)currentForm +{ + return [DOMElement _wrapElement:m_frame->currentForm()]; +} + +- (NSArray *)controlsInForm:(DOMElement *)form +{ + NSMutableArray *results = nil; + HTMLFormElement *formElement = formElementFromDOMElement(form); + if (formElement) { + Vector<HTMLGenericFormElement*>& elements = formElement->formElements; + for (unsigned int i = 0; i < elements.size(); i++) { + if (elements.at(i)->isEnumeratable()) { // Skip option elements, other duds + DOMElement *de = [DOMElement _wrapElement:elements.at(i)]; + if (!results) { + results = [NSMutableArray arrayWithObject:de]; + } else { + [results addObject:de]; + } + } + } + } + return results; +} + +- (NSString *)searchForLabels:(NSArray *)labels beforeElement:(DOMElement *)element +{ + return m_frame->searchForLabelsBeforeElement(labels, [element _element]); +} + +- (NSString *)matchLabels:(NSArray *)labels againstElement:(DOMElement *)element +{ + return m_frame->matchLabelsAgainstElement(labels, [element _element]); +} + +- (NSURL *)URLWithAttributeString:(NSString *)string +{ + Document* doc = m_frame->document(); + if (!doc) + return nil; + // FIXME: is parseURL appropriate here? + return doc->completeURL(parseURL(string)); +} + +- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection +{ + return m_frame->findString(string, forward, caseFlag, wrapFlag, startInSelection); +} + +- (unsigned)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(unsigned)limit +{ + return m_frame->markAllMatchesForText(string, caseFlag, limit); +} + +- (BOOL)markedTextMatchesAreHighlighted +{ + return m_frame->markedTextMatchesAreHighlighted(); +} + +- (void)setMarkedTextMatchesAreHighlighted:(BOOL)doHighlight +{ + m_frame->setMarkedTextMatchesAreHighlighted(doHighlight); +} + +- (void)unmarkAllTextMatches +{ + Document *doc = m_frame->document(); + if (!doc) { + return; + } + doc->removeMarkers(DocumentMarker::TextMatch); +} + +- (NSArray *)rectsForTextMatches +{ + Document *doc = m_frame->document(); + if (!doc) + return [NSArray array]; + + NSMutableArray *result = [NSMutableArray array]; + Vector<IntRect> rects = doc->renderedRectsForMarkers(DocumentMarker::TextMatch); + unsigned count = rects.size(); + for (unsigned index = 0; index < count; ++index) + [result addObject:[NSValue valueWithRect:rects[index]]]; + + return result; +} + +- (void)setTextSizeMultiplier:(float)multiplier +{ + int newZoomFactor = (int)rint(multiplier * 100); + if (m_frame->zoomFactor() == newZoomFactor) { + return; + } + m_frame->setZoomFactor(newZoomFactor); +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string +{ + return [self stringByEvaluatingJavaScriptFromString:string forceUserGesture:true]; +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string forceUserGesture:(BOOL)forceUserGesture +{ + ASSERT(m_frame->document()); + + JSValue* result = m_frame->loader()->executeScript(string, forceUserGesture); + + if (!m_frame) // In case the script removed our frame from the page. + return @""; + + // This bizarre set of rules matches behavior from WebKit for Safari 2.0. + // If you don't like it, use -[WebScriptObject evaluateWebScript:] or + // JSEvaluateScript instead, since they have less surprising semantics. + if (!result || !result->isBoolean() && !result->isString() && !result->isNumber()) + return @""; + + JSLock lock; + return String(result->toString(m_frame->scriptProxy()->globalObject()->globalExec())); +} + +- (NSAppleEventDescriptor *)aeDescByEvaluatingJavaScriptFromString:(NSString *)string +{ + ASSERT(m_frame->document()); + ASSERT(m_frame == m_frame->page()->mainFrame()); + JSValue* result = m_frame->loader()->executeScript(string, true); + if (!result) // FIXME: pass errors + return 0; + JSLock lock; + return aeDescFromJSValue(m_frame->scriptProxy()->globalObject()->globalExec(), result); +} + +- (NSRect)caretRectAtNode:(DOMNode *)node offset:(int)offset affinity:(NSSelectionAffinity)affinity +{ + return [node _node]->renderer()->caretRect(offset, static_cast<EAffinity>(affinity)); +} + +- (NSRect)firstRectForDOMRange:(DOMRange *)range +{ + return m_frame->firstRectForRange([range _range]); +} + +- (void)scrollDOMRangeToVisible:(DOMRange *)range +{ + NSRect rangeRect = [self firstRectForDOMRange:range]; + Node *startNode = [[range startContainer] _node]; + + if (startNode && startNode->renderer()) { + RenderLayer *layer = startNode->renderer()->enclosingLayer(); + if (layer) + layer->scrollRectToVisible(enclosingIntRect(rangeRect), RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignToEdgeIfNeeded); + } +} + +- (NSURL *)baseURL +{ + return m_frame->document()->baseURL(); +} + +- (NSString *)stringWithData:(NSData *)data +{ + Document* doc = m_frame->document(); + if (!doc) + return nil; + TextResourceDecoder* decoder = doc->decoder(); + if (!decoder) + return nil; + return decoder->encoding().decode(reinterpret_cast<const char*>([data bytes]), [data length]); +} + ++ (NSString *)stringWithData:(NSData *)data textEncodingName:(NSString *)textEncodingName +{ + WebCore::TextEncoding encoding(textEncodingName); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + return encoding.decode(reinterpret_cast<const char*>([data bytes]), [data length]); +} + +- (BOOL)needsLayout +{ + return m_frame->view() ? m_frame->view()->needsLayout() : false; +} + +- (NSString *)renderTreeAsExternalRepresentation +{ + return externalRepresentation(m_frame->renderer()); +} + +- (void)setShouldCreateRenderers:(BOOL)f +{ + _shouldCreateRenderers = f; +} + +- (id)accessibilityTree +{ + AXObjectCache::enableAccessibility(); + if (!m_frame || !m_frame->document()) + return nil; + RenderView* root = static_cast<RenderView *>(m_frame->document()->renderer()); + if (!root) + return nil; + return m_frame->document()->axObjectCache()->get(root); +} + +- (void)setBaseBackgroundColor:(NSColor *)backgroundColor +{ + if (m_frame && m_frame->view()) { + Color color = colorFromNSColor([backgroundColor colorUsingColorSpaceName:NSDeviceRGBColorSpace]); + m_frame->view()->setBaseBackgroundColor(color); + } +} + +- (void)setDrawsBackground:(BOOL)drawsBackground +{ + if (m_frame && m_frame->view()) + m_frame->view()->setTransparent(!drawsBackground); +} + +- (DOMRange *)rangeByAlteringCurrentSelection:(SelectionController::EAlteration)alteration direction:(SelectionController::EDirection)direction granularity:(TextGranularity)granularity +{ + if (m_frame->selectionController()->isNone()) + return nil; + + SelectionController selectionController; + selectionController.setSelection(m_frame->selectionController()->selection()); + selectionController.modify(alteration, direction, granularity); + return [DOMRange _wrapRange:selectionController.toRange().get()]; +} + +- (TextGranularity)selectionGranularity +{ + return m_frame->selectionGranularity(); +} + +- (NSRange)convertToNSRange:(Range *)range +{ + int exception = 0; + + if (!range || range->isDetached()) + return NSMakeRange(NSNotFound, 0); + + Element* selectionRoot = m_frame->selectionController()->rootEditableElement(); + Element* scope = selectionRoot ? selectionRoot : m_frame->document()->documentElement(); + + // Mouse events may cause TSM to attempt to create an NSRange for a portion of the view + // that is not inside the current editable region. These checks ensure we don't produce + // potentially invalid data when responding to such requests. + if (range->startContainer(exception) != scope && !range->startContainer(exception)->isDescendantOf(scope)) + return NSMakeRange(NSNotFound, 0); + if(range->endContainer(exception) != scope && !range->endContainer(exception)->isDescendantOf(scope)) + return NSMakeRange(NSNotFound, 0); + + RefPtr<Range> testRange = new Range(scope->document(), scope, 0, range->startContainer(exception), range->startOffset(exception)); + ASSERT(testRange->startContainer(exception) == scope); + int startPosition = TextIterator::rangeLength(testRange.get()); + + testRange->setEnd(range->endContainer(exception), range->endOffset(exception), exception); + ASSERT(testRange->startContainer(exception) == scope); + int endPosition = TextIterator::rangeLength(testRange.get()); + + return NSMakeRange(startPosition, endPosition - startPosition); +} + +- (PassRefPtr<Range>)convertToDOMRange:(NSRange)nsrange +{ + if (nsrange.location > INT_MAX) + return 0; + if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) + nsrange.length = INT_MAX - nsrange.location; + + // our critical assumption is that we are only called by input methods that + // concentrate on a given area containing the selection + // We have to do this because of text fields and textareas. The DOM for those is not + // directly in the document DOM, so serialization is problematic. Our solution is + // to use the root editable element of the selection start as the positional base. + // That fits with AppKit's idea of an input context. + Element* selectionRoot = m_frame->selectionController()->rootEditableElement(); + Element* scope = selectionRoot ? selectionRoot : m_frame->document()->documentElement(); + return TextIterator::rangeFromLocationAndLength(scope, nsrange.location, nsrange.length); +} + +- (DOMRange *)convertNSRangeToDOMRange:(NSRange)nsrange +{ + return [DOMRange _wrapRange:[self convertToDOMRange:nsrange].get()]; +} + +- (NSRange)convertDOMRangeToNSRange:(DOMRange *)range +{ + return [self convertToNSRange:[range _range]]; +} + +- (void)selectNSRange:(NSRange)range +{ + RefPtr<Range> domRange = [self convertToDOMRange:range]; + if (domRange) + m_frame->selectionController()->setSelection(Selection(domRange.get(), SEL_DEFAULT_AFFINITY)); +} + +- (NSRange)selectedNSRange +{ + return [self convertToNSRange:m_frame->selectionController()->toRange().get()]; +} + +- (DOMRange *)markDOMRange +{ + return [DOMRange _wrapRange:m_frame->mark().toRange().get()]; +} + +- (NSRange)markedTextNSRange +{ + return [self convertToNSRange:m_frame->editor()->compositionRange().get()]; +} + +// Given proposedRange, returns an extended range that includes adjacent whitespace that should +// be deleted along with the proposed range in order to preserve proper spacing and punctuation of +// the text surrounding the deletion. +- (DOMRange *)smartDeleteRangeForProposedRange:(DOMRange *)proposedRange +{ + Node *startContainer = [[proposedRange startContainer] _node]; + Node *endContainer = [[proposedRange endContainer] _node]; + if (startContainer == nil || endContainer == nil) + return nil; + + ASSERT(startContainer->document() == endContainer->document()); + + m_frame->document()->updateLayoutIgnorePendingStylesheets(); + + Position start(startContainer, [proposedRange startOffset]); + Position end(endContainer, [proposedRange endOffset]); + Position newStart = start.upstream().leadingWhitespacePosition(DOWNSTREAM, true); + if (newStart.isNull()) + newStart = start; + Position newEnd = end.downstream().trailingWhitespacePosition(DOWNSTREAM, true); + if (newEnd.isNull()) + newEnd = end; + + newStart = rangeCompliantEquivalent(newStart); + newEnd = rangeCompliantEquivalent(newEnd); + + RefPtr<Range> range = m_frame->document()->createRange(); + int exception = 0; + range->setStart(newStart.node(), newStart.offset(), exception); + range->setEnd(newStart.node(), newStart.offset(), exception); + return [DOMRange _wrapRange:range.get()]; +} + +// Determines whether whitespace needs to be added around aString to preserve proper spacing and +// punctuation when it’s inserted into the receiver’s text over charRange. Returns by reference +// in beforeString and afterString any whitespace that should be added, unless either or both are +// nil. Both are returned as nil if aString is nil or if smart insertion and deletion are disabled. +- (void)smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString +{ + // give back nil pointers in case of early returns + if (beforeString) + *beforeString = nil; + if (afterString) + *afterString = nil; + + // inspect destination + Node *startContainer = [[rangeToReplace startContainer] _node]; + Node *endContainer = [[rangeToReplace endContainer] _node]; + + Position startPos(startContainer, [rangeToReplace startOffset]); + Position endPos(endContainer, [rangeToReplace endOffset]); + + VisiblePosition startVisiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY); + VisiblePosition endVisiblePos = VisiblePosition(endPos, VP_DEFAULT_AFFINITY); + + // this check also ensures startContainer, startPos, endContainer, and endPos are non-null + if (startVisiblePos.isNull() || endVisiblePos.isNull()) + return; + + bool addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfParagraph(startVisiblePos); + if (addLeadingSpace) + if (UChar previousChar = startVisiblePos.previous().characterAfter()) + addLeadingSpace = !isCharacterSmartReplaceExempt(previousChar, true); + + bool addTrailingSpace = endPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfParagraph(endVisiblePos); + if (addTrailingSpace) + if (UChar thisChar = endVisiblePos.characterAfter()) + addTrailingSpace = !isCharacterSmartReplaceExempt(thisChar, false); + + // inspect source + bool hasWhitespaceAtStart = false; + bool hasWhitespaceAtEnd = false; + unsigned pasteLength = [pasteString length]; + if (pasteLength > 0) { + NSCharacterSet *whiteSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + + if ([whiteSet characterIsMember:[pasteString characterAtIndex:0]]) { + hasWhitespaceAtStart = YES; + } + if ([whiteSet characterIsMember:[pasteString characterAtIndex:(pasteLength - 1)]]) { + hasWhitespaceAtEnd = YES; + } + } + + // issue the verdict + if (beforeString && addLeadingSpace && !hasWhitespaceAtStart) + *beforeString = @" "; + if (afterString && addTrailingSpace && !hasWhitespaceAtEnd) + *afterString = @" "; +} + +- (DOMDocumentFragment *)documentFragmentWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString +{ + if (!m_frame || !m_frame->document()) + return 0; + + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromMarkup(m_frame->document(), markupString, baseURLString).get()]; +} + +- (DOMDocumentFragment *)documentFragmentWithText:(NSString *)text inContext:(DOMRange *)context +{ + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromText([context _range], text).get()]; +} + +- (DOMDocumentFragment *)documentFragmentWithNodesAsParagraphs:(NSArray *)nodes +{ + if (!m_frame || !m_frame->document()) + return 0; + + NSEnumerator *nodeEnum = [nodes objectEnumerator]; + Vector<Node*> nodesVector; + DOMNode *node; + while ((node = [nodeEnum nextObject])) + nodesVector.append([node _node]); + + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromNodes(m_frame->document(), nodesVector).get()]; +} + +- (void)replaceSelectionWithFragment:(DOMDocumentFragment *)fragment selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle +{ + if (m_frame->selectionController()->isNone() || !fragment) + return; + + applyCommand(new ReplaceSelectionCommand(m_frame->document(), [fragment _documentFragment], selectReplacement, smartReplace, matchStyle)); + m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); +} + +- (void)replaceSelectionWithNode:(DOMNode *)node selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle +{ + DOMDocumentFragment *fragment = [DOMDocumentFragment _wrapDocumentFragment:m_frame->document()->createDocumentFragment().get()]; + [fragment appendChild:node]; + [self replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:matchStyle]; +} + +- (void)replaceSelectionWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace +{ + DOMDocumentFragment *fragment = [self documentFragmentWithMarkupString:markupString baseURLString:baseURLString]; + [self replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:NO]; +} + +- (void)replaceSelectionWithText:(NSString *)text selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace +{ + [self replaceSelectionWithFragment:[self documentFragmentWithText:text + inContext:[DOMRange _wrapRange:m_frame->selectionController()->toRange().get()]] + selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:YES]; +} + +- (void)insertParagraphSeparatorInQuotedContent +{ + if (m_frame->selectionController()->isNone()) + return; + + TypingCommand::insertParagraphSeparatorInQuotedContent(m_frame->document()); + m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); +} + +- (VisiblePosition)_visiblePositionForPoint:(NSPoint)point +{ + IntPoint outerPoint(point); + HitTestResult result = m_frame->eventHandler()->hitTestResultAtPoint(outerPoint, true); + Node* node = result.innerNode(); + if (!node) + return VisiblePosition(); + RenderObject* renderer = node->renderer(); + if (!renderer) + return VisiblePosition(); + VisiblePosition visiblePos = renderer->positionForCoordinates(result.localPoint().x(), result.localPoint().y()); + if (visiblePos.isNull()) + visiblePos = VisiblePosition(Position(node, 0)); + return visiblePos; +} + +- (DOMRange *)characterRangeAtPoint:(NSPoint)point +{ + VisiblePosition position = [self _visiblePositionForPoint:point]; + if (position.isNull()) + return nil; + + VisiblePosition previous = position.previous(); + if (previous.isNotNull()) { + DOMRange *previousCharacterRange = [DOMRange _wrapRange:makeRange(previous, position).get()]; + NSRect rect = [self firstRectForDOMRange:previousCharacterRange]; + if (NSPointInRect(point, rect)) + return previousCharacterRange; + } + + VisiblePosition next = position.next(); + if (next.isNotNull()) { + DOMRange *nextCharacterRange = [DOMRange _wrapRange:makeRange(position, next).get()]; + NSRect rect = [self firstRectForDOMRange:nextCharacterRange]; + if (NSPointInRect(point, rect)) + return nextCharacterRange; + } + + return nil; +} + +- (DOMCSSStyleDeclaration *)typingStyle +{ + if (!m_frame || !m_frame->typingStyle()) + return nil; + return [DOMCSSStyleDeclaration _wrapCSSStyleDeclaration:m_frame->typingStyle()->copy().get()]; +} + +- (void)setTypingStyle:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction +{ + if (!m_frame) + return; + m_frame->computeAndSetTypingStyle([style _CSSStyleDeclaration], undoAction); +} + +- (NSFont *)fontForSelection:(BOOL *)hasMultipleFonts +{ + bool multipleFonts = false; + NSFont *font = nil; + if (m_frame) { + const SimpleFontData* fd = m_frame->editor()->fontForSelection(multipleFonts); + if (fd) + font = fd->getNSFont(); + } + + if (hasMultipleFonts) + *hasMultipleFonts = multipleFonts; + return font; +} + +- (void)dragSourceMovedTo:(NSPoint)windowLoc +{ + if (m_frame) { + // FIXME: Fake modifier keys here. + PlatformMouseEvent event(IntPoint(windowLoc), globalPoint(windowLoc, [self window]), + LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); + m_frame->eventHandler()->dragSourceMovedTo(event); + } +} + +- (void)dragSourceEndedAt:(NSPoint)windowLoc operation:(NSDragOperation)operation +{ + if (m_frame) { + // FIXME: Fake modifier keys here. + PlatformMouseEvent event(IntPoint(windowLoc), globalPoint(windowLoc, [self window]), + LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); + m_frame->eventHandler()->dragSourceEndedAt(event, (DragOperation)operation); + } +} + +- (BOOL)getData:(NSData **)data andResponse:(NSURLResponse **)response forURL:(NSString *)url +{ + Document* doc = m_frame->document(); + if (!doc) + return NO; + + CachedResource* resource = doc->docLoader()->cachedResource(url); + if (!resource) + return NO; + + SharedBuffer* buffer = resource->data(); + if (buffer) + *data = [buffer->createNSData() autorelease]; + else + *data = nil; + + *response = resource->response().nsURLResponse(); + return YES; +} + +- (void)getAllResourceDatas:(NSArray **)datas andResponses:(NSArray **)responses +{ + Document* doc = m_frame->document(); + if (!doc) { + NSArray* emptyArray = [NSArray array]; + *datas = emptyArray; + *responses = emptyArray; + return; + } + + const HashMap<String, CachedResource*>& allResources = doc->docLoader()->allCachedResources(); + + NSMutableArray *d = [[NSMutableArray alloc] initWithCapacity:allResources.size()]; + NSMutableArray *r = [[NSMutableArray alloc] initWithCapacity:allResources.size()]; + + HashMap<String, CachedResource*>::const_iterator end = allResources.end(); + for (HashMap<String, CachedResource*>::const_iterator it = allResources.begin(); it != end; ++it) { + SharedBuffer* buffer = it->second->data(); + NSData *data; + if (buffer) + data = buffer->createNSData(); + else + data = [[NSData alloc] init]; + [d addObject:data]; + [data release]; + [r addObject:it->second->response().nsURLResponse()]; + } + + *datas = [d autorelease]; + *responses = [r autorelease]; +} + +- (BOOL)canProvideDocumentSource +{ + String mimeType = m_frame->loader()->responseMIMEType(); + + if (WebCore::DOMImplementation::isTextMIMEType(mimeType) || + Image::supportsType(mimeType) || + PluginInfoStore::supportsMIMEType(mimeType)) + return NO; + + return YES; +} + +- (BOOL)canSaveAsWebArchive +{ + // Currently, all documents that we can view source for + // (HTML and XML documents) can also be saved as web archives + return [self canProvideDocumentSource]; +} + +- (void)receivedData:(NSData *)data textEncodingName:(NSString *)textEncodingName +{ + // Set the encoding. This only needs to be done once, but it's harmless to do it again later. + String encoding; + if (m_frame) + encoding = m_frame->loader()->documentLoader()->overrideEncoding(); + bool userChosen = !encoding.isNull(); + if (encoding.isNull()) + encoding = textEncodingName; + m_frame->loader()->setEncoding(encoding, userChosen); + [self addData:data]; +} + +// ------------------- + +- (Frame*)_frame +{ + return m_frame; +} + +@end diff --git a/WebCore/page/mac/WebCoreFrameView.h b/WebCore/page/mac/WebCoreFrameView.h new file mode 100644 index 0000000..a478dca --- /dev/null +++ b/WebCore/page/mac/WebCoreFrameView.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@class WebCoreFrameBridge; + +// This protocol is a way for an NSScrollView to detect +// that the view it's embedded in is one that should be resized when the +// scroll view is resized. + +typedef enum { + WebCoreScrollbarAuto, + WebCoreScrollbarAlwaysOff, + WebCoreScrollbarAlwaysOn +} WebCoreScrollbarMode; + +@protocol WebCoreFrameView +- (void)setHorizontalScrollingMode:(WebCoreScrollbarMode)mode; +- (void)setVerticalScrollingMode:(WebCoreScrollbarMode)mode; +- (void)setScrollingMode:(WebCoreScrollbarMode)mode; + +- (WebCoreScrollbarMode)horizontalScrollingMode; +- (WebCoreScrollbarMode)verticalScrollingMode; + +- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint; + +@end + +// This protocol is a way for WebCore to gain access to its information +// about WebKit subclasses of NSView +@protocol WebCoreBridgeHolder +- (WebCoreFrameBridge *) webCoreBridge; +@end diff --git a/WebCore/page/mac/WebCoreKeyboardUIMode.h b/WebCore/page/mac/WebCoreKeyboardUIMode.h new file mode 100644 index 0000000..187cf09 --- /dev/null +++ b/WebCore/page/mac/WebCoreKeyboardUIMode.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WebCoreKeyboardUIMode_h +#define WebCoreKeyboardUIMode_h + +namespace WebCore { + + enum KeyboardUIMode { + KeyboardAccessDefault = 0x00000000, + KeyboardAccessFull = 0x00000001, + // this flag may be or'ed with either of the two above + KeyboardAccessTabsToLinks = 0x10000000 + }; + +} + +#endif diff --git a/WebCore/page/mac/WebCoreScriptDebugger.h b/WebCore/page/mac/WebCoreScriptDebugger.h new file mode 100644 index 0000000..fd56632 --- /dev/null +++ b/WebCore/page/mac/WebCoreScriptDebugger.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@class WebScriptObject; // from JavaScriptCore +@class WebCoreScriptCallFrame; // below + +#ifdef __cplusplus +class WebCoreScriptDebuggerImp; +namespace KJS { class ExecState; } +using KJS::ExecState; +#else +@class WebCoreScriptDebuggerImp; +@class ExecState; +#endif + + + +// "WebScriptDebugger" protocol - must be implemented by a delegate + +@protocol WebScriptDebugger + +- (WebScriptObject *)globalObject; // return the WebView's windowScriptObject +- (id)newWrapperForFrame:(WebCoreScriptCallFrame *)frame; // return a (retained) stack-frame object + +// debugger callbacks +- (void)parsedSource:(NSString *)source fromURL:(NSURL *)url sourceId:(int)sid startLine:(int)startLine errorLine:(int)errorLine errorMessage:(NSString *)errorMessage; +- (void)enteredFrame:(WebCoreScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno; +- (void)hitStatement:(WebCoreScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno; +- (void)leavingFrame:(WebCoreScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno; +- (void)exceptionRaised:(WebCoreScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno; + +@end + + + +@interface WebCoreScriptDebugger : NSObject +{ +@private + id<WebScriptDebugger> _delegate; // interface to WebKit (not retained) + WebScriptObject *_globalObj; // the global object's proxy (not retained) + WebCoreScriptCallFrame *_current; // top of stack + WebCoreScriptDebuggerImp *_debugger; // [KJS::Debugger] +} + +- (WebCoreScriptDebugger *)initWithDelegate:(id<WebScriptDebugger>)delegate; +- (id<WebScriptDebugger>)delegate; + +@end + + + +@interface WebCoreScriptCallFrame : NSObject +{ +@private + id _wrapper; // WebKit's version of this object + WebScriptObject *_globalObj; // the global object's proxy (not retained) + WebCoreScriptCallFrame *_caller; // previous stack frame + ExecState *_state; // [KJS::ExecState] +} + +- (id)wrapper; +- (WebCoreScriptCallFrame *)caller; + +- (NSArray *)scopeChain; +- (NSString *)functionName; +- (id)exception; +- (id)evaluateWebScript:(NSString *)script; + +@end diff --git a/WebCore/page/mac/WebCoreScriptDebugger.mm b/WebCore/page/mac/WebCoreScriptDebugger.mm new file mode 100644 index 0000000..ae68a63 --- /dev/null +++ b/WebCore/page/mac/WebCoreScriptDebugger.mm @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2005, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" +#import "WebCoreScriptDebugger.h" + +#import "KURL.h" +#import "PlatformString.h" +#import "WebCoreObjCExtras.h" +#import "WebScriptObjectPrivate.h" +#import <JavaScriptCore/ExecState.h> +#import <JavaScriptCore/JSGlobalObject.h> +#import <JavaScriptCore/debugger.h> +#import <JavaScriptCore/function.h> +#import <JavaScriptCore/interpreter.h> + +using namespace KJS; +using namespace WebCore; + +@interface WebCoreScriptDebugger (WebCoreScriptDebuggerInternal) + +- (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state; +- (WebCoreScriptCallFrame *)_leaveFrame; + +@end + +@interface WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal) + +- (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state; +- (void)_setWrapper:(id)wrapper; +- (id)_convertValueToObjcValue:(JSValue *)value; + +@end + +// convert UString to NSString +static NSString *toNSString(const UString& s) +{ + if (s.isEmpty()) + return nil; + return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(s.data()) length:s.size()]; +} + +// convert UString to NSURL +static NSURL *toNSURL(const UString& s) +{ + if (s.isEmpty()) + return nil; + return KURL(s); +} + +// C++ interface to KJS debugger callbacks + +class WebCoreScriptDebuggerImp : public KJS::Debugger { + + private: + WebCoreScriptDebugger *_objc; // our ObjC half + bool _nested; // true => this is a nested call + WebCoreScriptCallFrame *_current; // top stack frame (copy of same field from ObjC side) + + public: + // constructor + WebCoreScriptDebuggerImp(WebCoreScriptDebugger *objc, JSGlobalObject* globalObject) : _objc(objc) { + _nested = true; + _current = [_objc _enterFrame:globalObject->globalExec()]; + attach(globalObject); + [[_objc delegate] enteredFrame:_current sourceId:-1 line:-1]; + _nested = false; + } + + // callbacks - relay to delegate + virtual bool sourceParsed(ExecState *state, int sid, const UString &url, const UString &source, int lineNumber, int errorLine, const UString &errorMsg) { + if (!_nested) { + _nested = true; + [[_objc delegate] parsedSource:toNSString(source) fromURL:toNSURL(url) sourceId:sid startLine:lineNumber errorLine:errorLine errorMessage:toNSString(errorMsg)]; + _nested = false; + } + return true; + } + virtual bool callEvent(ExecState *state, int sid, int lineno, JSObject *func, const List &args) { + if (!_nested) { + _nested = true; + _current = [_objc _enterFrame:state]; + [[_objc delegate] enteredFrame:_current sourceId:sid line:lineno]; + _nested = false; + } + return true; + } + virtual bool atStatement(ExecState *state, int sid, int lineno, int lastLine) { + if (!_nested) { + _nested = true; + [[_objc delegate] hitStatement:_current sourceId:sid line:lineno]; + _nested = false; + } + return true; + } + virtual bool returnEvent(ExecState *state, int sid, int lineno, JSObject *func) { + if (!_nested) { + _nested = true; + [[_objc delegate] leavingFrame:_current sourceId:sid line:lineno]; + _current = [_objc _leaveFrame]; + _nested = false; + } + return true; + } + virtual bool exception(ExecState *state, int sid, int lineno, JSValue *exception) { + if (!_nested) { + _nested = true; + [[_objc delegate] exceptionRaised:_current sourceId:sid line:lineno]; + _nested = false; + } + return true; + } + +}; + +// WebCoreScriptDebugger +// +// This is the main (behind-the-scenes) debugger class in WebCore. +// +// The WebCoreScriptDebugger has two faces, one for Objective-C (this class), and another (WebCoreScriptDebuggerImp) +// for C++. The ObjC side creates the C++ side, which does the real work of attaching to the global object and +// forwarding the KJS debugger callbacks to the delegate. + +@implementation WebCoreScriptDebugger + +#ifndef BUILDING_ON_TIGER ++ (void)initialize +{ + WebCoreObjCFinalizeOnMainThread(self); +} +#endif + +- (WebCoreScriptDebugger *)initWithDelegate:(id<WebScriptDebugger>)delegate +{ + if ((self = [super init])) { + _delegate = delegate; + _globalObj = [_delegate globalObject]; + _debugger = new WebCoreScriptDebuggerImp(self, [_globalObj _rootObject]->globalObject()); + } + return self; +} + +- (void)dealloc +{ + [_current release]; + delete _debugger; + [super dealloc]; +} + +- (void)finalize +{ + delete _debugger; + [super finalize]; +} + +- (id<WebScriptDebugger>)delegate +{ + return _delegate; +} + +@end + +@implementation WebCoreScriptDebugger (WebCoreScriptDebuggerInternal) + +- (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state; +{ + WebCoreScriptCallFrame *callee = [[WebCoreScriptCallFrame alloc] _initWithGlobalObject:_globalObj caller:_current state:state]; + [callee _setWrapper:[_delegate newWrapperForFrame:callee]]; + return _current = callee; +} + +- (WebCoreScriptCallFrame *)_leaveFrame; +{ + WebCoreScriptCallFrame *caller = [[_current caller] retain]; + [_current release]; + return _current = caller; +} + +@end + +// WebCoreScriptCallFrame +// +// One of these is created to represent each stack frame. Additionally, there is a "global" +// frame to represent the outermost scope. This global frame is always the last frame in +// the chain of callers. +// +// The delegate can assign a "wrapper" to each frame object so it can relay calls through its +// own exported interface. This class is private to WebCore (and the delegate). + +@implementation WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal) + +- (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state +{ + if ((self = [super init])) { + _globalObj = globalObj; + _caller = caller; // (already retained) + _state = state; + } + return self; +} + +- (void)_setWrapper:(id)wrapper +{ + _wrapper = wrapper; // (already retained) +} + +- (id)_convertValueToObjcValue:(JSValue *)value +{ + if (!value) + return nil; + + if (value == [_globalObj _imp]) + return _globalObj; + + Bindings::RootObject* root1 = [_globalObj _originRootObject]; + if (!root1) + return nil; + + Bindings::RootObject* root2 = [_globalObj _rootObject]; + if (!root2) + return nil; + + return [WebScriptObject _convertValueToObjcValue:value originRootObject:root1 rootObject:root2]; +} + +@end + +@implementation WebCoreScriptCallFrame + +- (void)dealloc +{ + [_wrapper release]; + [_caller release]; + [super dealloc]; +} + +- (id)wrapper +{ + return _wrapper; +} + +- (WebCoreScriptCallFrame *)caller +{ + return _caller; +} + +// Returns an array of scope objects (most local first). +// The properties of each scope object are the variables for that scope. +// Note that the last entry in the array will _always_ be the global object (windowScriptObject), +// whose properties are the global variables. + +- (NSArray *)scopeChain +{ + if (!_state->scopeNode()) { // global frame + return [NSArray arrayWithObject:_globalObj]; + } + + ScopeChain chain = _state->scopeChain(); + NSMutableArray *scopes = [[NSMutableArray alloc] init]; + + while (!chain.isEmpty()) { + [scopes addObject:[self _convertValueToObjcValue:chain.top()]]; + chain.pop(); + } + + NSArray *result = [NSArray arrayWithArray:scopes]; + [scopes release]; + return result; +} + +// Returns the name of the function for this frame, if available. +// Returns nil for anonymous functions and for the global frame. + +- (NSString *)functionName +{ + if (_state->scopeNode()) { + FunctionImp* func = _state->function(); + if (func) { + Identifier fn = func->functionName(); + return toNSString(fn.ustring()); + } + } + return nil; +} + +// Returns the pending exception for this frame (nil if none). + +- (id)exception +{ + if (!_state->hadException()) return nil; + return [self _convertValueToObjcValue:_state->exception()]; +} + +// Evaluate some JavaScript code in the context of this frame. +// The code is evaluated as if by "eval", and the result is returned. +// If there is an (uncaught) exception, it is returned as though _it_ were the result. +// Calling this method on the global frame is not quite the same as calling the WebScriptObject +// method of the same name, due to the treatment of exceptions. + +// FIXME: If "script" contains var declarations, the machinery to handle local variables +// efficiently in JavaScriptCore will not work properly. This could lead to crashes or +// incorrect variable values. So this is not appropriate for evaluating arbitrary script. +- (id)evaluateWebScript:(NSString *)script +{ + JSLock lock; + + UString code = String(script); + + ExecState* state = _state; + JSGlobalObject* globalObject = state->dynamicGlobalObject(); + + // find "eval" + JSObject *eval = NULL; + if (state->scopeNode()) { // "eval" won't work without context (i.e. at global scope) + JSValue *v = globalObject->get(state, "eval"); + if (v->isObject() && static_cast<JSObject *>(v)->implementsCall()) + eval = static_cast<JSObject *>(v); + else + // no "eval" - fallback operates on global exec state + state = globalObject->globalExec(); + } + + JSValue *savedException = state->exception(); + state->clearException(); + + // evaluate + JSValue *result; + if (eval) { + List args; + args.append(jsString(code)); + result = eval->call(state, NULL, args); + } else + // no "eval", or no context (i.e. global scope) - use global fallback + result = Interpreter::evaluate(state, UString(), 0, code.data(), code.size(), globalObject).value(); + + if (state->hadException()) + result = state->exception(); // (may be redundant depending on which eval path was used) + state->setException(savedException); + + return [self _convertValueToObjcValue:result]; +} + +@end diff --git a/WebCore/page/mac/WebCoreViewFactory.h b/WebCore/page/mac/WebCoreViewFactory.h new file mode 100644 index 0000000..4cf21e3 --- /dev/null +++ b/WebCore/page/mac/WebCoreViewFactory.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003, 2005 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@class NSArray; +@class NSDictionary; +@class NSMenu; +@class NSString; +@class NSView; +@class WebCoreFrameBridge; +@class WebCoreTextMarker; +@class WebCoreTextMarkerRange; + +@protocol WebCoreViewFactory + +- (NSArray *)pluginsInfo; // array of id <WebCorePluginInfo> +- (void)refreshPlugins:(BOOL)reloadPages; +- (NSString *)pluginNameForMIMEType:(NSString *)MIMEType; +- (BOOL)pluginSupportsMIMEType:(NSString *)MIMEType; + +- (NSString *)inputElementAltText; +- (NSString *)resetButtonDefaultLabel; +- (NSString *)searchableIndexIntroduction; +- (NSString *)submitButtonDefaultLabel; +- (NSString *)fileButtonChooseFileLabel; +- (NSString *)fileButtonNoFileSelectedLabel; +- (NSString *)copyImageUnknownFileLabel; + +// Context menu item titles +- (NSString *)contextMenuItemTagOpenLinkInNewWindow; +- (NSString *)contextMenuItemTagDownloadLinkToDisk; +- (NSString *)contextMenuItemTagCopyLinkToClipboard; +- (NSString *)contextMenuItemTagOpenImageInNewWindow; +- (NSString *)contextMenuItemTagDownloadImageToDisk; +- (NSString *)contextMenuItemTagCopyImageToClipboard; +- (NSString *)contextMenuItemTagOpenFrameInNewWindow; +- (NSString *)contextMenuItemTagCopy; +- (NSString *)contextMenuItemTagGoBack; +- (NSString *)contextMenuItemTagGoForward; +- (NSString *)contextMenuItemTagStop; +- (NSString *)contextMenuItemTagReload; +- (NSString *)contextMenuItemTagCut; +- (NSString *)contextMenuItemTagPaste; +- (NSString *)contextMenuItemTagNoGuessesFound; +- (NSString *)contextMenuItemTagIgnoreSpelling; +- (NSString *)contextMenuItemTagLearnSpelling; +- (NSString *)contextMenuItemTagSearchInSpotlight; +- (NSString *)contextMenuItemTagSearchWeb; +- (NSString *)contextMenuItemTagLookUpInDictionary; +- (NSString *)contextMenuItemTagOpenLink; +- (NSString *)contextMenuItemTagIgnoreGrammar; +- (NSString *)contextMenuItemTagSpellingMenu; +- (NSString *)contextMenuItemTagShowSpellingPanel:(bool)show; +- (NSString *)contextMenuItemTagCheckSpelling; +- (NSString *)contextMenuItemTagCheckSpellingWhileTyping; +- (NSString *)contextMenuItemTagCheckGrammarWithSpelling; +- (NSString *)contextMenuItemTagFontMenu; +- (NSString *)contextMenuItemTagShowFonts; +- (NSString *)contextMenuItemTagBold; +- (NSString *)contextMenuItemTagItalic; +- (NSString *)contextMenuItemTagUnderline; +- (NSString *)contextMenuItemTagOutline; +- (NSString *)contextMenuItemTagStyles; +- (NSString *)contextMenuItemTagShowColors; +- (NSString *)contextMenuItemTagSpeechMenu; +- (NSString *)contextMenuItemTagStartSpeaking; +- (NSString *)contextMenuItemTagStopSpeaking; +- (NSString *)contextMenuItemTagWritingDirectionMenu; +- (NSString *)contextMenuItemTagDefaultDirection; +- (NSString *)contextMenuItemTagLeftToRight; +- (NSString *)contextMenuItemTagRightToLeft; +- (NSString *)contextMenuItemTagInspectElement; + +- (NSString *)searchMenuNoRecentSearchesText; +- (NSString *)searchMenuRecentSearchesText; +- (NSString *)searchMenuClearRecentSearchesText; + +- (NSString *)defaultLanguageCode; + +- (NSString *)imageTitleForFilename:(NSString *)filename size:(NSSize)size; + +- (BOOL)objectIsTextMarker:(id)object; +- (BOOL)objectIsTextMarkerRange:(id)object; + +- (WebCoreTextMarker *)textMarkerWithBytes:(const void *)bytes length:(size_t)length; +- (BOOL)getBytes:(void *)bytes fromTextMarker:(WebCoreTextMarker *)textMarker length:(size_t)length; + +- (WebCoreTextMarkerRange *)textMarkerRangeWithStart:(WebCoreTextMarker *)start end:(WebCoreTextMarker *)end; +- (WebCoreTextMarker *)startOfTextMarkerRange:(WebCoreTextMarkerRange *)range; +- (WebCoreTextMarker *)endOfTextMarkerRange:(WebCoreTextMarkerRange *)range; + +- (void)accessibilityHandleFocusChanged; + +- (AXUIElementRef)AXUIElementForElement:(id)element; +- (void)unregisterUniqueIdForUIElement:(id)element; + +- (WebCoreFrameBridge *)bridgeForView:(NSView *)aView; + +- (NSString *)AXWebAreaText; +- (NSString *)AXLinkText; +- (NSString *)AXListMarkerText; +- (NSString *)AXImageMapText; +- (NSString *)AXHeadingText; + +// FTP Directory Related +- (NSString *)unknownFileSizeText; + +@end + +@interface WebCoreViewFactory : NSObject +{ +} + ++ (WebCoreViewFactory *)sharedFactory; + +@end + +@interface WebCoreViewFactory (SubclassResponsibility) <WebCoreViewFactory> +@end + +@protocol WebCorePluginInfo <NSObject> +- (NSString *)name; +- (NSString *)filename; +- (NSString *)pluginDescription; +- (NSEnumerator *)MIMETypeEnumerator; +- (NSString *)descriptionForMIMEType:(NSString *)MIMEType; +- (NSArray *)extensionsForMIMEType:(NSString *)MIMEType; +@end + diff --git a/WebCore/page/mac/WebCoreViewFactory.m b/WebCore/page/mac/WebCoreViewFactory.m new file mode 100644 index 0000000..5398989 --- /dev/null +++ b/WebCore/page/mac/WebCoreViewFactory.m @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#import "WebCoreViewFactory.h" +#import <wtf/Assertions.h> + +@implementation WebCoreViewFactory + +static WebCoreViewFactory *sharedFactory; + ++ (WebCoreViewFactory *)sharedFactory +{ + return sharedFactory; +} + +- init +{ + [super init]; + + ASSERT(!sharedFactory); + sharedFactory = [self retain]; + + return self; +} + +@end diff --git a/WebCore/page/mac/WebDashboardRegion.h b/WebCore/page/mac/WebDashboardRegion.h new file mode 100644 index 0000000..81113e9 --- /dev/null +++ b/WebCore/page/mac/WebDashboardRegion.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +typedef enum { + WebDashboardRegionTypeNone, + WebDashboardRegionTypeCircle, + WebDashboardRegionTypeRectangle, + WebDashboardRegionTypeScrollerRectangle +} WebDashboardRegionType; + +@interface WebDashboardRegion : NSObject <NSCopying> +{ + NSRect rect; + NSRect clip; + WebDashboardRegionType type; +} +- initWithRect:(NSRect)rect clip:(NSRect)clip type:(WebDashboardRegionType)type; +- (NSRect)dashboardRegionClip; +- (NSRect)dashboardRegionRect; +- (WebDashboardRegionType)dashboardRegionType; +@end diff --git a/WebCore/page/mac/WebDashboardRegion.m b/WebCore/page/mac/WebDashboardRegion.m new file mode 100644 index 0000000..958e599 --- /dev/null +++ b/WebCore/page/mac/WebDashboardRegion.m @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#import "WebDashboardRegion.h" + +@implementation WebDashboardRegion +- initWithRect:(NSRect)r clip:(NSRect)c type:(WebDashboardRegionType)t +{ + self = [super init]; + rect = r; + clip = c; + type = t; + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + return [self retain]; +} + +- (NSRect)dashboardRegionClip +{ + return clip; +} + +- (NSRect)dashboardRegionRect +{ + return rect; +} + +- (WebDashboardRegionType)dashboardRegionType +{ + return type; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"rect:%@ clip:%@ type:%s", + NSStringFromRect(rect), + NSStringFromRect(clip), + type == WebDashboardRegionTypeNone ? "None" : + (type == WebDashboardRegionTypeCircle ? "Circle" : + (type == WebDashboardRegionTypeRectangle ? "Rectangle" : + (type == WebDashboardRegionTypeScrollerRectangle ? "ScrollerRectangle" : + "Unknown")))]; +} + +- (BOOL)isEqual:(id)other +{ + return NSEqualRects (rect, [other dashboardRegionRect]) && NSEqualRects (clip, [other dashboardRegionClip]) && type == [other dashboardRegionType]; +} + +@end diff --git a/WebCore/page/qt/DragControllerQt.cpp b/WebCore/page/qt/DragControllerQt.cpp new file mode 100644 index 0000000..62372d0 --- /dev/null +++ b/WebCore/page/qt/DragControllerQt.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragController.h" + +#include "DragData.h" +#include "Frame.h" +#include "FrameView.h" +#include "Page.h" + +namespace WebCore +{ + +// FIXME: These values are straight out of DragControllerMac, so probably have +// little correlation with Qt standards... +const int DragController::LinkDragBorderInset = 2; +const int DragController::MaxOriginalImageArea = 1500 * 1500; +const int DragController::DragIconRightInset = 7; +const int DragController::DragIconBottomInset = 3; + +const float DragController::DragImageAlpha = 0.75f; + + +bool DragController::isCopyKeyDown() +{ + return false; +} + +DragOperation DragController::dragOperation(DragData* dragData) +{ + //FIXME: This logic is incomplete + if (dragData->containsURL()) + return DragOperationCopy; + + return DragOperationNone; +} + +const IntSize& DragController::maxDragImageSize() +{ + static const IntSize maxDragImageSize(400, 400); + + return maxDragImageSize; +} + +} diff --git a/WebCore/page/qt/EventHandlerQt.cpp b/WebCore/page/qt/EventHandlerQt.cpp new file mode 100644 index 0000000..ce16f5b --- /dev/null +++ b/WebCore/page/qt/EventHandlerQt.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2006 Zack Rusin <zack@kde.org> + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2007 Trolltech ASA + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventHandler.h" + +#include "ClipboardQt.h" +#include "Cursor.h" +#include "Document.h" +#include "EventNames.h" +#include "FloatPoint.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HTMLFrameSetElement.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "KeyboardEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "Page.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformScrollBar.h" +#include "PlatformWheelEvent.h" +#include "RenderWidget.h" +#include "NotImplemented.h" + +namespace WebCore { + +using namespace EventNames; + +static bool isKeyboardOptionTab(KeyboardEvent* event) +{ + return event + && (event->type() == keydownEvent || event->type() == keypressEvent) + && event->altKey() + && event->keyIdentifier() == "U+0009"; +} + +bool EventHandler::invertSenseOfTabsToLinks(KeyboardEvent* event) const +{ + return isKeyboardOptionTab(event); +} + +bool EventHandler::tabsToAllControls(KeyboardEvent* event) const +{ + bool handlingOptionTab = isKeyboardOptionTab(event); + + return handlingOptionTab; +} + +void EventHandler::focusDocumentView() +{ + Page* page = m_frame->page(); + if (!page) + return; + page->focusController()->setFocusedFrame(m_frame); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults&) +{ + notImplemented(); + return false; +} + +bool EventHandler::eventActivatedView(const PlatformMouseEvent&) const +{ + //Qt has an activation event which is sent independently + // of mouse event so this thing will be a snafu to implement + // correctly + return false; +} + +bool EventHandler::passWheelEventToWidget(PlatformWheelEvent& event, Widget* widget) +{ + Q_ASSERT(widget); + if (!widget->isFrameView()) + return false; + + return static_cast<FrameView*>(widget)->frame()->eventHandler()->handleWheelEvent(event); +} + +Clipboard* EventHandler::createDraggingClipboard() const +{ + return new ClipboardQt(ClipboardWritable, true); +} + +bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMousePressEvent(mev.event()); + return true; +} + +bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode) +{ + subframe->eventHandler()->handleMouseMoveEvent(mev.event(), hoveredNode); + return true; +} + +bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMouseReleaseEvent(mev.event()); + return true; +} + +bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mev, PlatformScrollbar* scrollbar) +{ + if (!scrollbar || !scrollbar->isEnabled()) + return false; + return scrollbar->handleMousePressEvent(mev.event()); +} + +} diff --git a/WebCore/page/qt/FrameQt.cpp b/WebCore/page/qt/FrameQt.cpp new file mode 100644 index 0000000..7ae1dd9 --- /dev/null +++ b/WebCore/page/qt/FrameQt.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2006 Dirk Mueller <mueller@kde.org> + * Copyright (C) 2006 Zack Rusin <zack@kde.org> + * Copyright (C) 2006 George Staikos <staikos@kde.org> + * Copyright (C) 2006 Simon Hausmann <hausmann@kde.org> + * Copyright (C) 2006 Rob Buis <buis@kde.org> + * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2007 Trolltech ASA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Frame.h" + +#include "Element.h" +#include "RenderObject.h" +#include "RenderWidget.h" +#include "RenderLayer.h" +#include "Page.h" +#include "Document.h" +#include "HTMLElement.h" +#include "DOMWindow.h" +#include "FrameLoadRequest.h" +#include "FrameLoaderClientQt.h" +#include "DOMImplementation.h" +#include "ResourceHandleInternal.h" +#include "Document.h" +#include "Settings.h" +#include "Plugin.h" +#include "FrameView.h" +#include "FramePrivate.h" +#include "GraphicsContext.h" +#include "HTMLDocument.h" +#include "ResourceHandle.h" +#include "FrameLoader.h" +#include "PlatformMouseEvent.h" +#include "PlatformKeyboardEvent.h" +#include "PlatformWheelEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "SelectionController.h" +#include "kjs_proxy.h" +#include "TypingCommand.h" +#include "JSLock.h" +#include "kjs_window.h" +#include "runtime_root.h" +#include "runtime.h" +#include <QScrollArea> +#include "NotImplemented.h" + +namespace WebCore { + +// FIXME: Turned this off to fix buildbot. This function be either deleted or used. +#if 0 +static void doScroll(const RenderObject* r, bool isHorizontal, int multiplier) +{ + // FIXME: The scrolling done here should be done in the default handlers + // of the elements rather than here in the part. + if (!r) + return; + + //broken since it calls scroll on scrollbars + //and we have none now + //r->scroll(direction, KWQScrollWheel, multiplier); + if (!r->layer()) + return; + + int x = r->layer()->scrollXOffset(); + int y = r->layer()->scrollYOffset(); + if (isHorizontal) + x += multiplier; + else + y += multiplier; + + r->layer()->scrollToOffset(x, y, true, true); +} +#endif + +KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(WebCore::Widget* widget) +{ + QWidget* nativeWidget = widget->nativeWidget(); + if (!nativeWidget) + return 0; + return KJS::Bindings::Instance::createBindingForLanguageInstance(KJS::Bindings::Instance::QtLanguage, + nativeWidget, + bindingRootObject()); +} + +void Frame::clearPlatformScriptObjects() +{ +} + +DragImageRef Frame::dragImageForSelection() +{ + return 0; +} + +void Frame::dashboardRegionsChanged() +{ +} + +} +// vim: ts=4 sw=4 et diff --git a/WebCore/page/win/DragControllerWin.cpp b/WebCore/page/win/DragControllerWin.cpp new file mode 100644 index 0000000..41f3008 --- /dev/null +++ b/WebCore/page/win/DragControllerWin.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragController.h" + +#include "DragData.h" +#include "windows.h" +#include "SelectionController.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +const int DragController::LinkDragBorderInset = 2; +const int DragController::MaxOriginalImageArea = 1500 * 1500; +const int DragController::DragIconRightInset = 7; +const int DragController::DragIconBottomInset = 3; + +const float DragController::DragImageAlpha = 0.75f; + +DragOperation DragController::dragOperation(DragData* dragData) +{ + //FIXME: to match the macos behaviour we should return DragOperationNone + //if we are a modal window, we are the drag source, or the window is an attached sheet + //If this can be determined from within WebCore operationForDrag can be pulled into + //WebCore itself + ASSERT(dragData); + return dragData->containsURL() && !m_didInitiateDrag ? DragOperationCopy : DragOperationNone; +} + +bool DragController::isCopyKeyDown() { + return ::GetAsyncKeyState(VK_CONTROL); +} + +const IntSize& DragController::maxDragImageSize() +{ + static const IntSize maxDragImageSize(200, 200); + + return maxDragImageSize; +} + +} diff --git a/WebCore/page/win/EventHandlerWin.cpp b/WebCore/page/win/EventHandlerWin.cpp new file mode 100644 index 0000000..5e349e3 --- /dev/null +++ b/WebCore/page/win/EventHandlerWin.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EventHandler.h" + +#include "ClipboardWin.h" +#include "Cursor.h" +#include "FloatPoint.h" +#include "FocusController.h" +#include "FrameView.h" +#include "Frame.h" +#include "HitTestRequest.h" +#include "HitTestResult.h" +#include "MouseEventWithHitTestResults.h" +#include "Page.h" +#include "PlatformScrollbar.h" +#include "PlatformWheelEvent.h" +#include "SelectionController.h" +#include "WCDataObject.h" +#include "NotImplemented.h" + +namespace WebCore { + +bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMousePressEvent(mev.event()); + return true; +} + +bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode) +{ + if (m_mouseDownMayStartDrag && !m_mouseDownWasInSubframe) + return false; + subframe->eventHandler()->handleMouseMoveEvent(mev.event(), hoveredNode); + return true; +} + +bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + subframe->eventHandler()->handleMouseReleaseEvent(mev.event()); + return true; +} + +bool EventHandler::passWheelEventToWidget(PlatformWheelEvent& wheelEvent, Widget* widget) +{ + if (!widget->isFrameView()) + return false; + + return static_cast<FrameView*>(widget)->frame()->eventHandler()->handleWheelEvent(wheelEvent); +} + +bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mev, PlatformScrollbar* scrollbar) +{ + if (!scrollbar || !scrollbar->isEnabled()) + return false; + return scrollbar->handleMousePressEvent(mev.event()); +} + +bool EventHandler::tabsToAllControls(KeyboardEvent*) const +{ + return true; +} + +bool EventHandler::eventActivatedView(const PlatformMouseEvent& event) const +{ + return event.activatedWebView(); +} + +Clipboard* EventHandler::createDraggingClipboard() const +{ + COMPtr<WCDataObject> dataObject; + WCDataObject::createInstance(&dataObject); + return new ClipboardWin(true, dataObject.get(), ClipboardWritable); +} + +void EventHandler::focusDocumentView() +{ + Page* page = m_frame->page(); + if (!page) + return; + page->focusController()->setFocusedFrame(m_frame); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults&) +{ + notImplemented(); + return false; +} + +} diff --git a/WebCore/page/win/FrameCGWin.cpp b/WebCore/page/win/FrameCGWin.cpp new file mode 100644 index 0000000..eea6961 --- /dev/null +++ b/WebCore/page/win/FrameCGWin.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FrameWin.h" + +#include <windows.h> + +#include "FrameView.h" +#include "GraphicsContext.h" +#include "Settings.h" + +#include <CoreGraphics/CoreGraphics.h> + +using std::min; + +namespace WebCore { + +static void drawRectIntoContext(IntRect rect, FrameView* view, GraphicsContext* gc) +{ + IntSize offset = view->scrollOffset(); + rect.move(-offset.width(), -offset.height()); + rect = view->convertToContainingWindow(rect); + + gc->concatCTM(AffineTransform().translate(-rect.x(), -rect.y())); + + view->paint(gc, rect); +} + +HBITMAP imageFromSelection(Frame* frame, bool forceBlackText) +{ + frame->setPaintRestriction(forceBlackText ? PaintRestrictionSelectionOnlyBlackText : PaintRestrictionSelectionOnly); + FloatRect fr = frame->selectionRect(); + IntRect ir(static_cast<int>(fr.x()), static_cast<int>(fr.y()), + static_cast<int>(fr.width()), static_cast<int>(fr.height())); + + void* bits; + HDC hdc = CreateCompatibleDC(0); + int w = ir.width(); + int h = ir.height(); + BITMAPINFO bmp = { { sizeof(BITMAPINFOHEADER), w, h, 1, 32 } }; + + HBITMAP hbmp = CreateDIBSection(0, &bmp, DIB_RGB_COLORS, static_cast<void**>(&bits), 0, 0); + HBITMAP hbmpOld = static_cast<HBITMAP>(SelectObject(hdc, hbmp)); + CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(static_cast<void*>(bits), w, h, + 8, w * sizeof(RGBQUAD), deviceRGB, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(deviceRGB); + CGContextSaveGState(context); + + GraphicsContext gc(context); + + frame->document()->updateLayout(); + drawRectIntoContext(ir, frame->view(), &gc); + + CGContextRelease(context); + SelectObject(hdc, hbmpOld); + DeleteDC(hdc); + + frame->setPaintRestriction(PaintRestrictionNone); + + return hbmp; +} + +} // namespace WebCore diff --git a/WebCore/page/win/FrameCairoWin.cpp b/WebCore/page/win/FrameCairoWin.cpp new file mode 100644 index 0000000..a645a10 --- /dev/null +++ b/WebCore/page/win/FrameCairoWin.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FrameWin.h" + +#include "EditorClient.h" +#include "NotImplemented.h" + +using std::min; + +namespace WebCore { + +HBITMAP imageFromSelection(Frame* frame, bool forceBlackText) +{ + notImplemented(); + return 0; +} + +} // namespace WebCore diff --git a/WebCore/page/win/FrameWin.cpp b/WebCore/page/win/FrameWin.cpp new file mode 100644 index 0000000..6599a88 --- /dev/null +++ b/WebCore/page/win/FrameWin.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FrameWin.h" + +#include <winsock2.h> +#include <windows.h> + +#include "AffineTransform.h" +#include "FloatRect.h" +#include "Document.h" +#include "EditorClient.h" +#include "FrameLoader.h" +#include "FrameLoadRequest.h" +#include "FramePrivate.h" +#include "FrameView.h" +#include "HTMLIFrameElement.h" +#include "HTMLNames.h" +#include "HTMLTableCellElement.h" +#include "KeyboardEvent.h" +#include "NP_jsobject.h" +#include "NotImplemented.h" +#include "Page.h" +#include "Plugin.h" +#include "PluginDatabase.h" +#include "PluginView.h" +#include "RegularExpression.h" +#include "RenderFrame.h" +#include "RenderTableCell.h" +#include "RenderView.h" +#include "ResourceHandle.h" +#include "TextResourceDecoder.h" +#include "kjs_proxy.h" +#include "kjs_window.h" +#include "npruntime_impl.h" +#include "runtime_root.h" +#include "GraphicsContext.h" +#include "Settings.h" + +using std::min; +using namespace KJS::Bindings; + +namespace WebCore { + +using namespace HTMLNames; + +void Frame::clearPlatformScriptObjects() +{ +} + +KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(Widget* widget) +{ + // FIXME: Ideally we'd have an isPluginView() here but we can't add that to the open source tree right now. + if (widget->isFrameView()) + return 0; + + return static_cast<PluginView*>(widget)->bindingInstance(); +} + +void computePageRectsForFrame(Frame* frame, const IntRect& printRect, float headerHeight, float footerHeight, float userScaleFactor,Vector<IntRect>& pages, int& outPageHeight) +{ + ASSERT(frame); + + pages.clear(); + outPageHeight = 0; + + if (!frame->document() || !frame->view() || !frame->document()->renderer()) + return; + + RenderView* root = static_cast<RenderView *>(frame->document()->renderer()); + + if (!root) { + LOG_ERROR("document to be printed has no renderer"); + return; + } + + if (userScaleFactor <= 0) { + LOG_ERROR("userScaleFactor has bad value %.2f", userScaleFactor); + return; + } + + float ratio = static_cast<float>(printRect.height()) / static_cast<float>(printRect.width()); + + float pageWidth = static_cast<float>(root->docWidth()); + float pageHeight = pageWidth * ratio; + outPageHeight = static_cast<int>(pageHeight); // this is the height of the page adjusted by margins + pageHeight -= (headerHeight + footerHeight); + + if (pageHeight <= 0) { + LOG_ERROR("pageHeight has bad value %.2f", pageHeight); + return; + } + + float currPageHeight = pageHeight / userScaleFactor; + float docHeight = root->layer()->height(); + float docWidth = root->layer()->width(); + float currPageWidth = pageWidth / userScaleFactor; + + + // always return at least one page, since empty files should print a blank page + float printedPagesHeight = 0.0f; + do { + float proposedBottom = min(docHeight, printedPagesHeight + pageHeight); + frame->adjustPageHeight(&proposedBottom, printedPagesHeight, proposedBottom, printedPagesHeight); + currPageHeight = max(1.0f, proposedBottom - printedPagesHeight); + + pages.append(IntRect(0, printedPagesHeight, currPageWidth, currPageHeight)); + printedPagesHeight += currPageHeight; + } while (printedPagesHeight < docHeight); +} + +DragImageRef Frame::dragImageForSelection() +{ + if (selectionController()->isRange()) + return imageFromSelection(this, false); + + return 0; +} + +void Frame::dashboardRegionsChanged() +{ +} + +} // namespace WebCore diff --git a/WebCore/page/win/FrameWin.h b/WebCore/page/win/FrameWin.h new file mode 100644 index 0000000..405c7b2 --- /dev/null +++ b/WebCore/page/win/FrameWin.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FrameWin_H +#define FrameWin_H + +#include "Frame.h" + +// Forward declared so we don't need wingdi.h. +typedef struct HBITMAP__* HBITMAP; + +namespace WebCore { + + HBITMAP imageFromSelection(Frame* frame, bool forceWhiteText); + void computePageRectsForFrame(Frame*, const IntRect& printRect, float headerHeight, float footerHeight, float userScaleFactor,Vector<IntRect>& pages, int& pageHeight); + +} + +#endif diff --git a/WebCore/page/win/GlobalHistoryWin.cpp b/WebCore/page/win/GlobalHistoryWin.cpp new file mode 100644 index 0000000..c81f09b --- /dev/null +++ b/WebCore/page/win/GlobalHistoryWin.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "GlobalHistory.h" + +#include "WebCoreHistory.h" + +namespace WebCore { + +bool historyContains(const UChar* characters, unsigned length) +{ + WebCoreHistoryProvider* provider = WebCoreHistory::historyProvider(); + return provider && provider->containsURL(characters, length); +} + +} // namespace WebCore diff --git a/WebCore/page/win/PageWin.cpp b/WebCore/page/win/PageWin.cpp new file mode 100644 index 0000000..f4c744a --- /dev/null +++ b/WebCore/page/win/PageWin.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Page.h" + +#include "Frame.h" +#include "FrameView.h" +#include "FloatRect.h" +#include <windows.h> + +namespace WebCore { + +HINSTANCE Page::s_instanceHandle = 0; + +} // namespace WebCore diff --git a/WebCore/page/wx/DragControllerWx.cpp b/WebCore/page/wx/DragControllerWx.cpp new file mode 100644 index 0000000..659364f --- /dev/null +++ b/WebCore/page/wx/DragControllerWx.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragController.h" + +#include "DragData.h" +#include "Frame.h" +#include "FrameView.h" +#include "NotImplemented.h" +#include "Page.h" + +namespace WebCore { + +// FIXME: These values are straight out of DragControllerMac, so probably have +// little correlation with wx standards... +const int DragController::LinkDragBorderInset = 2; +const int DragController::MaxOriginalImageArea = 1500 * 1500; +const int DragController::DragIconRightInset = 7; +const int DragController::DragIconBottomInset = 3; + +const float DragController::DragImageAlpha = 0.75f; + +bool DragController::isCopyKeyDown() +{ + notImplemented(); + return false; +} + +DragOperation DragController::dragOperation(DragData* dragData) +{ + //FIXME: This logic is incomplete + if (dragData->containsURL()) + return DragOperationCopy; + + return DragOperationNone; +} + +const IntSize& DragController::maxDragImageSize() +{ + static const IntSize maxDragImageSize(400, 400); + + return maxDragImageSize; +} + +} diff --git a/WebCore/page/wx/EventHandlerWx.cpp b/WebCore/page/wx/EventHandlerWx.cpp new file mode 100644 index 0000000..6670c18 --- /dev/null +++ b/WebCore/page/wx/EventHandlerWx.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 Kevin Ollivier <kevino@theolliviers.com>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include "ClipboardWx.h" +#include "EventHandler.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameView.h" +#include "KeyboardEvent.h" +#include "MouseEventWithHitTestResults.h" +#include "Page.h" +#include "PlatformScrollBar.h" +#include "RenderWidget.h" + +namespace WebCore { + +bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + return passSubframeEventToSubframe(mev, subframe); +} + +bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, WebCore::HitTestResult* hittest) +{ + return passSubframeEventToSubframe(mev, subframe); +} + +bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe) +{ + return passSubframeEventToSubframe(mev, subframe); +} + +bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mouseEvent, PlatformScrollbar* scrollbar) +{ + return passMouseDownEventToWidget(scrollbar); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event) +{ + // Figure out which view to send the event to. + if (!event.targetNode() || !event.targetNode()->renderer() || !event.targetNode()->renderer()->isWidget()) + return false; + + return passMouseDownEventToWidget(static_cast<RenderWidget*>(event.targetNode()->renderer())->widget()); +} + +bool EventHandler::passWidgetMouseDownEventToWidget(RenderWidget* renderWidget) +{ + return passMouseDownEventToWidget(renderWidget->widget()); +} + +void EventHandler::focusDocumentView() +{ + if (Page* page = m_frame->page()) + page->focusController()->setFocusedFrame(m_frame); +} + +bool EventHandler::eventActivatedView(const PlatformMouseEvent&) const +{ + // wx sends activate events separate from mouse events. + // We'll have to test the exact order of events, + // but for the moment just return false. + return false; +} + +Clipboard* EventHandler::createDraggingClipboard() const +{ + return new ClipboardWx(ClipboardWritable, true); +} + +} |