Web Inspector: "Reload Web Inspector" button no longer partially works
[WebKit-https.git] / Source / WebKit / UIProcess / WKInspectorHighlightView.mm
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKInspectorHighlightView.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import <WebCore/FloatQuad.h>
32 #import <WebCore/GeometryUtilities.h>
33 #import <WebCore/InspectorOverlay.h>
34
35 @implementation WKInspectorHighlightView
36
37 - (instancetype)initWithFrame:(CGRect)frame
38 {
39     if (!(self = [super initWithFrame:frame]))
40         return nil;
41     _layers = [[NSMutableArray alloc] init];
42     return self;
43 }
44
45 - (void)dealloc
46 {
47     [self _removeAllLayers];
48     [_layers release];
49     [super dealloc];
50 }
51
52 - (void)_removeAllLayers
53 {
54     for (CAShapeLayer *layer in _layers)
55         [layer removeFromSuperlayer];
56     [_layers removeAllObjects];
57 }
58
59 - (void)_createLayers:(NSUInteger)numLayers
60 {
61     if ([_layers count] == numLayers)
62         return;
63
64     [self _removeAllLayers];
65
66     for (NSUInteger i = 0; i < numLayers; ++i) {
67         CAShapeLayer *layer = [[CAShapeLayer alloc] init];
68         [_layers addObject:layer];
69         [self.layer addSublayer:layer];
70         [layer release];
71     }
72 }
73
74 static bool findIntersectionOnLineBetweenPoints(const WebCore::FloatPoint& p1, const WebCore::FloatPoint& p2, const WebCore::FloatPoint& d1, const WebCore::FloatPoint& d2, WebCore::FloatPoint& intersection) 
75 {
76     // Do the lines intersect?
77     WebCore::FloatPoint temporaryIntersectionPoint;
78     if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint))
79         return false;
80
81     // Is the intersection between the two points on the line?
82     if (p1.x() >= p2.x()) {
83         if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x())
84             return false;
85     } else {
86         if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x())
87             return false;
88     }
89     if (p1.y() >= p2.y()) {
90         if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y())
91             return false;
92     } else {
93         if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y())
94             return false;
95     }
96
97     intersection = temporaryIntersectionPoint;
98     return true;
99 }
100
101 // This quad intersection works because the two quads are known to be at the same
102 // rotation and clockwise-ness.
103 static WebCore::FloatQuad quadIntersection(WebCore::FloatQuad bounds, WebCore::FloatQuad toClamp)
104 {
105     // Resulting points.
106     WebCore::FloatPoint p1, p2, p3, p4;
107     bool containsPoint1 = false;
108     bool containsPoint2 = false;
109     bool containsPoint3 = false;
110     bool containsPoint4 = false;
111     bool intersectForPoint1 = false;
112     bool intersectForPoint2 = false;
113     bool intersectForPoint3 = false;
114     bool intersectForPoint4 = false;
115
116     // Top / bottom vertical clamping.
117     if (bounds.containsPoint(toClamp.p1())) {
118         containsPoint1 = true;
119         p1 = toClamp.p1();
120     } else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1)))
121         p1 = toClamp.p1();
122
123     if (bounds.containsPoint(toClamp.p2())) {
124         containsPoint2 = true;
125         p2 = toClamp.p2();
126     } else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2)))
127         p2 = toClamp.p2();
128
129     if (bounds.containsPoint(toClamp.p3())) {
130         containsPoint3 = true;
131         p3 = toClamp.p3();
132     } else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3)))
133         p3 = toClamp.p3();
134
135     if (bounds.containsPoint(toClamp.p4())) {
136         containsPoint4 = true;
137         p4 = toClamp.p4();
138     } else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4)))
139         p4 = toClamp.p4();
140
141     // If only one of the points intersected on either the top or bottom line then we
142     // can clamp the other point on that line to the corner of the bounds.
143     if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) {
144         containsPoint1 = true;
145         p1 = bounds.p1();
146     } else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) {
147         containsPoint2 = true;
148         p2 = bounds.p2();
149     }
150     if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) {
151         containsPoint4 = true;
152         p4 = bounds.p4();
153     } else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) {
154         containsPoint3 = true;
155         p3 = bounds.p3();
156     }
157
158     // Now we only need to perform horizontal clamping for unadjusted points.
159     if (!containsPoint2 && !intersectForPoint2)
160         findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2);
161     if (!containsPoint3 && !intersectForPoint3)
162         findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3);
163     if (!containsPoint1 && !intersectForPoint1)
164         findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1);
165     if (!containsPoint4 && !intersectForPoint4)
166         findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4);
167
168     return WebCore::FloatQuad(p1, p2, p3, p4);
169 }
170
171 static void layerPathWithHole(CAShapeLayer *layer, const WebCore::FloatQuad& outerQuad, const WebCore::FloatQuad& holeQuad)
172 {
173     // Nothing to show.
174     if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) {
175         layer.path = NULL;
176         return;
177     }
178
179     // If there is a negative margin / padding then the outer box might not
180     // fully contain the hole box. In such cases we recalculate the hole to
181     // be the intersection of the two quads.
182     WebCore::FloatQuad innerHole;
183     if (outerQuad.containsQuad(holeQuad))
184         innerHole = holeQuad;
185     else
186         innerHole = quadIntersection(outerQuad, holeQuad);
187
188     // Clockwise inside rect (hole), Counter-Clockwise outside rect (fill).
189     CGMutablePathRef path = CGPathCreateMutable();
190     CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y());
191     CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y());
192     CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y());
193     CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y());
194     CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
195     CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
196     CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
197     CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
198     layer.path = path;
199     CGPathRelease(path);
200 }
201
202 static void layerPath(CAShapeLayer *layer, const WebCore::FloatQuad& outerQuad)
203 {
204     CGMutablePathRef path = CGPathCreateMutable();
205     CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
206     CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
207     CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
208     CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
209     layer.path = path;
210     CGPathRelease(path);
211 }
212
213 - (void)_layoutForNodeHighlight:(const WebCore::Highlight&)highlight offset:(unsigned)offset
214 {
215     ASSERT([_layers count] >= offset + 4);
216     ASSERT(highlight.quads.size() >= offset + 4);
217     if ([_layers count] < offset + 4 || highlight.quads.size() < offset + 4)
218         return;
219
220     CAShapeLayer *marginLayer = [_layers objectAtIndex:offset];
221     CAShapeLayer *borderLayer = [_layers objectAtIndex:offset + 1];
222     CAShapeLayer *paddingLayer = [_layers objectAtIndex:offset + 2];
223     CAShapeLayer *contentLayer = [_layers objectAtIndex:offset + 3];
224
225     WebCore::FloatQuad marginQuad = highlight.quads[offset];
226     WebCore::FloatQuad borderQuad = highlight.quads[offset + 1];
227     WebCore::FloatQuad paddingQuad = highlight.quads[offset + 2];
228     WebCore::FloatQuad contentQuad = highlight.quads[offset + 3];
229
230     marginLayer.fillColor = cachedCGColor(highlight.marginColor);
231     borderLayer.fillColor = cachedCGColor(highlight.borderColor);
232     paddingLayer.fillColor = cachedCGColor(highlight.paddingColor);
233     contentLayer.fillColor = cachedCGColor(highlight.contentColor);
234
235     layerPathWithHole(marginLayer, marginQuad, borderQuad);
236     layerPathWithHole(borderLayer, borderQuad, paddingQuad);
237     layerPathWithHole(paddingLayer, paddingQuad, contentQuad);
238     layerPath(contentLayer, contentQuad);
239 }
240
241 - (void)_layoutForNodeListHighlight:(const WebCore::Highlight&)highlight
242 {
243     if (!highlight.quads.size()) {
244         [self _removeAllLayers];
245         return;
246     }
247
248     unsigned nodeCount = highlight.quads.size() / 4;
249     [self _createLayers:nodeCount * 4];
250
251     for (unsigned i = 0; i < nodeCount; ++i)
252         [self _layoutForNodeHighlight:highlight offset:i * 4];
253 }
254
255 - (void)_layoutForRectsHighlight:(const WebCore::Highlight&)highlight
256 {
257     NSUInteger numLayers = highlight.quads.size();
258     if (!numLayers) {
259         [self _removeAllLayers];
260         return;
261     }
262
263     [self _createLayers:numLayers];
264
265     CGColorRef contentColor = cachedCGColor(highlight.contentColor);
266     for (NSUInteger i = 0; i < numLayers; ++i) {
267         CAShapeLayer *layer = [_layers objectAtIndex:i];
268         layer.fillColor = contentColor;
269         layerPath(layer, highlight.quads[i]);
270     }
271 }
272
273 - (void)update:(const WebCore::Highlight&)highlight
274 {
275     if (highlight.type == WebCore::HighlightType::Node || highlight.type == WebCore::HighlightType::NodeList)
276         [self _layoutForNodeListHighlight:highlight];
277     else if (highlight.type == WebCore::HighlightType::Rects)
278         [self _layoutForRectsHighlight:highlight];
279 }
280
281 @end
282
283 #endif