OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> | |
3 * 1999 Lars Knoll <knoll@kde.org> | |
4 * 1999 Antti Koivisto <koivisto@kde.org> | |
5 * 2000 Simon Hausmann <hausmann@kde.org> | |
6 * 2000 Stefan Schimanski <1Stein@gmx.de> | |
7 * 2001 George Staikos <staikos@kde.org> | |
8 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. | |
9 * Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com> | |
10 * Copyright (C) 2007 Trolltech ASA | |
11 * Copyright (C) 2008 Eric Seidel <eric@webkit.org> | |
12 * | |
13 * This library is free software; you can redistribute it and/or | |
14 * modify it under the terms of the GNU Library General Public | |
15 * License as published by the Free Software Foundation; either | |
16 * version 2 of the License, or (at your option) any later version. | |
17 * | |
18 * This library is distributed in the hope that it will be useful, | |
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 * Library General Public License for more details. | |
22 * | |
23 * You should have received a copy of the GNU Library General Public License | |
24 * along with this library; see the file COPYING.LIB. If not, write to | |
25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
26 * Boston, MA 02110-1301, USA. | |
27 */ | |
28 | |
29 #include "config.h" | |
30 #include "Frame.h" | |
31 #include "FramePrivate.h" | |
32 | |
33 #include "ApplyStyleCommand.h" | |
34 #include "BeforeUnloadEvent.h" | |
35 #include "CSSComputedStyleDeclaration.h" | |
36 #include "CSSProperty.h" | |
37 #include "CSSPropertyNames.h" | |
38 #include "CachedCSSStyleSheet.h" | |
39 #include "DOMWindow.h" | |
40 #include "DocLoader.h" | |
41 #include "DocumentType.h" | |
42 #include "EditingText.h" | |
43 #include "EditorClient.h" | |
44 #include "EventNames.h" | |
45 #include "FocusController.h" | |
46 #include "FrameLoader.h" | |
47 #include "FrameView.h" | |
48 #include "GraphicsContext.h" | |
49 #include "HTMLDocument.h" | |
50 #include "HTMLFormElement.h" | |
51 #include "HTMLFrameElementBase.h" | |
52 #include "HTMLFormControlElement.h" | |
53 #include "HTMLNames.h" | |
54 #include "HTMLTableCellElement.h" | |
55 #include "HitTestResult.h" | |
56 #include "Logging.h" | |
57 #include "markup.h" | |
58 #include "MediaFeatureNames.h" | |
59 #include "Navigator.h" | |
60 #include "NodeList.h" | |
61 #include "Page.h" | |
62 #include "RegularExpression.h" | |
63 #include "RenderPart.h" | |
64 #include "RenderTableCell.h" | |
65 #include "RenderTextControl.h" | |
66 #include "RenderTheme.h" | |
67 #include "RenderView.h" | |
68 #include "ScriptController.h" | |
69 #include "Settings.h" | |
70 #include "SystemTime.h" | |
71 #include "TextIterator.h" | |
72 #include "TextResourceDecoder.h" | |
73 #include "XMLNames.h" | |
74 #include "ScriptController.h" | |
75 #include "npruntime_impl.h" | |
76 #include "runtime_root.h" | |
77 #include "visible_units.h" | |
78 #include <wtf/RefCountedLeakCounter.h> | |
79 | |
80 #if USE(JSC) | |
81 #include "JSDOMWindowShell.h" | |
82 #endif | |
83 | |
84 #if FRAME_LOADS_USER_STYLESHEET | |
85 #include "UserStyleSheetLoader.h" | |
86 #endif | |
87 | |
88 #if ENABLE(SVG) | |
89 #include "SVGDocument.h" | |
90 #include "SVGDocumentExtensions.h" | |
91 #include "SVGNames.h" | |
92 #include "XLinkNames.h" | |
93 #endif | |
94 | |
95 using namespace std; | |
96 | |
97 namespace WebCore { | |
98 | |
99 using namespace EventNames; | |
100 using namespace HTMLNames; | |
101 | |
102 double Frame::s_currentPaintTimeStamp = 0.0; | |
103 | |
104 #ifndef NDEBUG | |
105 static WTF::RefCountedLeakCounter frameCounter("Frame"); | |
106 #endif | |
107 | |
108 static inline Frame* parentFromOwnerElement(HTMLFrameOwnerElement* ownerElement) | |
109 { | |
110 if (!ownerElement) | |
111 return 0; | |
112 return ownerElement->document()->frame(); | |
113 } | |
114 | |
115 Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient*
frameLoaderClient) | |
116 : d(new FramePrivate(page, parentFromOwnerElement(ownerElement), this, owner
Element, frameLoaderClient)) | |
117 { | |
118 AtomicString::init(); | |
119 EventNames::init(); | |
120 HTMLNames::init(); | |
121 QualifiedName::init(); | |
122 MediaFeatureNames::init(); | |
123 | |
124 #if ENABLE(SVG) | |
125 SVGNames::init(); | |
126 XLinkNames::init(); | |
127 #endif | |
128 | |
129 XMLNames::init(); | |
130 | |
131 if (!ownerElement) | |
132 page->setMainFrame(this); | |
133 else { | |
134 page->incrementFrameCount(); | |
135 // Make sure we will not end up with two frames referencing the same own
er element. | |
136 ASSERT((!(ownerElement->m_contentFrame)) || (ownerElement->m_contentFram
e->ownerElement() != ownerElement)); | |
137 ownerElement->m_contentFrame = this; | |
138 } | |
139 | |
140 #ifndef NDEBUG | |
141 frameCounter.increment(); | |
142 #endif | |
143 } | |
144 | |
145 Frame::~Frame() | |
146 { | |
147 setView(0); | |
148 loader()->clearRecordedFormValues(); | |
149 loader()->cancelAndClear(); | |
150 | |
151 // FIXME: We should not be doing all this work inside the destructor | |
152 | |
153 ASSERT(!d->m_lifeSupportTimer.isActive()); | |
154 | |
155 #ifndef NDEBUG | |
156 frameCounter.decrement(); | |
157 #endif | |
158 | |
159 d->m_script.disconnectFrame(); | |
160 | |
161 disconnectOwnerElement(); | |
162 | |
163 if (d->m_domWindow) | |
164 d->m_domWindow->disconnectFrame(); | |
165 | |
166 HashSet<DOMWindow*>::iterator end = d->m_liveFormerWindows.end(); | |
167 for (HashSet<DOMWindow*>::iterator it = d->m_liveFormerWindows.begin(); it !
= end; ++it) | |
168 (*it)->disconnectFrame(); | |
169 | |
170 if (d->m_view) { | |
171 d->m_view->hide(); | |
172 d->m_view->clearFrame(); | |
173 } | |
174 | |
175 ASSERT(!d->m_lifeSupportTimer.isActive()); | |
176 | |
177 #if FRAME_LOADS_USER_STYLESHEET | |
178 delete d->m_userStyleSheetLoader; | |
179 #endif | |
180 | |
181 delete d; | |
182 d = 0; | |
183 } | |
184 | |
185 void Frame::init() | |
186 { | |
187 d->m_loader.init(); | |
188 } | |
189 | |
190 FrameLoader* Frame::loader() const | |
191 { | |
192 return &d->m_loader; | |
193 } | |
194 | |
195 FrameView* Frame::view() const | |
196 { | |
197 return d->m_view.get(); | |
198 } | |
199 | |
200 void Frame::setView(FrameView* view) | |
201 { | |
202 // Detach the document now, so any onUnload handlers get run - if | |
203 // we wait until the view is destroyed, then things won't be | |
204 // hooked up enough for some JavaScript calls to work. | |
205 if (!view && d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { | |
206 // FIXME: We don't call willRemove here. Why is that OK? | |
207 d->m_doc->detach(); | |
208 if (d->m_view) | |
209 d->m_view->unscheduleRelayout(); | |
210 } | |
211 eventHandler()->clear(); | |
212 | |
213 d->m_view = view; | |
214 | |
215 // Only one form submission is allowed per view of a part. | |
216 // Since this part may be getting reused as a result of being | |
217 // pulled from the back/forward cache, reset this flag. | |
218 loader()->resetMultipleFormSubmissionProtection(); | |
219 } | |
220 | |
221 ScriptController* Frame::script() | |
222 { | |
223 return &d->m_script; | |
224 } | |
225 | |
226 Document* Frame::document() const | |
227 { | |
228 return d->m_doc.get(); | |
229 } | |
230 | |
231 void Frame::setDocument(PassRefPtr<Document> newDoc) | |
232 { | |
233 if (d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { | |
234 // FIXME: We don't call willRemove here. Why is that OK? | |
235 d->m_doc->detach(); | |
236 } | |
237 | |
238 d->m_doc = newDoc; | |
239 if (d->m_doc && selection()->isFocusedAndActive()) | |
240 setUseSecureKeyboardEntry(d->m_doc->useSecureKeyboardEntryWhenActive()); | |
241 | |
242 if (d->m_doc && !d->m_doc->attached()) | |
243 d->m_doc->attach(); | |
244 | |
245 // Update the cached 'document' property, which is now stale. | |
246 d->m_script.updateDocument(); | |
247 } | |
248 | |
249 Settings* Frame::settings() const | |
250 { | |
251 return d->m_page ? d->m_page->settings() : 0; | |
252 } | |
253 | |
254 String Frame::selectedText() const | |
255 { | |
256 return plainText(selection()->toRange().get()); | |
257 } | |
258 | |
259 IntRect Frame::firstRectForRange(Range* range) const | |
260 { | |
261 int extraWidthToEndOfLine = 0; | |
262 ExceptionCode ec = 0; | |
263 ASSERT(range->startContainer(ec)); | |
264 ASSERT(range->endContainer(ec)); | |
265 InlineBox* startInlineBox; | |
266 int startCaretOffset; | |
267 range->startPosition().getInlineBoxAndOffset(DOWNSTREAM, startInlineBox, sta
rtCaretOffset); | |
268 IntRect startCaretRect = range->startContainer(ec)->renderer()->caretRect(st
artInlineBox, startCaretOffset, &extraWidthToEndOfLine); | |
269 | |
270 InlineBox* endInlineBox; | |
271 int endCaretOffset; | |
272 range->endPosition().getInlineBoxAndOffset(UPSTREAM, endInlineBox, endCaretO
ffset); | |
273 IntRect endCaretRect = range->endContainer(ec)->renderer()->caretRect(endInl
ineBox, endCaretOffset); | |
274 | |
275 if (startCaretRect.y() == endCaretRect.y()) { | |
276 // start and end are on the same line | |
277 return IntRect(min(startCaretRect.x(), endCaretRect.x()), | |
278 startCaretRect.y(), | |
279 abs(endCaretRect.x() - startCaretRect.x()), | |
280 max(startCaretRect.height(), endCaretRect.height())); | |
281 } | |
282 | |
283 // start and end aren't on the same line, so go from start to the end of its
line | |
284 return IntRect(startCaretRect.x(), | |
285 startCaretRect.y(), | |
286 startCaretRect.width() + extraWidthToEndOfLine, | |
287 startCaretRect.height()); | |
288 } | |
289 | |
290 SelectionController* Frame::selection() const | |
291 { | |
292 return &d->m_selectionController; | |
293 } | |
294 | |
295 Editor* Frame::editor() const | |
296 { | |
297 return &d->m_editor; | |
298 } | |
299 | |
300 TextGranularity Frame::selectionGranularity() const | |
301 { | |
302 return d->m_selectionGranularity; | |
303 } | |
304 | |
305 void Frame::setSelectionGranularity(TextGranularity granularity) const | |
306 { | |
307 d->m_selectionGranularity = granularity; | |
308 } | |
309 | |
310 SelectionController* Frame::dragCaretController() const | |
311 { | |
312 return d->m_page->dragCaretController(); | |
313 } | |
314 | |
315 | |
316 AnimationController* Frame::animation() const | |
317 { | |
318 return &d->m_animationController; | |
319 } | |
320 | |
321 static RegularExpression* createRegExpForLabels(const Vector<String>& labels) | |
322 { | |
323 // REVIEW- version of this call in FrameMac.mm caches based on the NSArray p
trs being | |
324 // the same across calls. We can't do that. | |
325 | |
326 static RegularExpression wordRegExp = RegularExpression("\\w"); | |
327 String pattern("("); | |
328 unsigned int numLabels = labels.size(); | |
329 unsigned int i; | |
330 for (i = 0; i < numLabels; i++) { | |
331 String label = labels[i]; | |
332 | |
333 bool startsWithWordChar = false; | |
334 bool endsWithWordChar = false; | |
335 if (label.length() != 0) { | |
336 startsWithWordChar = wordRegExp.search(label.substring(0, 1)) >= 0; | |
337 endsWithWordChar = wordRegExp.search(label.substring(label.length()
- 1, 1)) >= 0; | |
338 } | |
339 | |
340 if (i != 0) | |
341 pattern.append("|"); | |
342 // Search for word boundaries only if label starts/ends with "word chara
cters". | |
343 // If we always searched for word boundaries, this wouldn't work for lan
guages | |
344 // such as Japanese. | |
345 if (startsWithWordChar) { | |
346 pattern.append("\\b"); | |
347 } | |
348 pattern.append(label); | |
349 if (endsWithWordChar) { | |
350 pattern.append("\\b"); | |
351 } | |
352 } | |
353 pattern.append(")"); | |
354 return new RegularExpression(pattern, false); | |
355 } | |
356 | |
357 String Frame::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellE
lement* cell) | |
358 { | |
359 RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer
()); | |
360 | |
361 if (cellRenderer && cellRenderer->isTableCell()) { | |
362 RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(ce
llRenderer); | |
363 | |
364 if (cellAboveRenderer) { | |
365 HTMLTableCellElement* aboveCell = | |
366 static_cast<HTMLTableCellElement*>(cellAboveRenderer->element())
; | |
367 | |
368 if (aboveCell) { | |
369 // search within the above cell we found for a match | |
370 for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNo
de(aboveCell)) { | |
371 if (n->isTextNode() && n->renderer() && n->renderer()->style
()->visibility() == VISIBLE) { | |
372 // For each text chunk, run the regexp | |
373 String nodeString = n->nodeValue(); | |
374 int pos = regExp->searchRev(nodeString); | |
375 if (pos >= 0) | |
376 return nodeString.substring(pos, regExp->matchedLeng
th()); | |
377 } | |
378 } | |
379 } | |
380 } | |
381 } | |
382 // Any reason in practice to search all cells in that are above cell? | |
383 return String(); | |
384 } | |
385 | |
386 String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element
* element) | |
387 { | |
388 OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels)); | |
389 // We stop searching after we've seen this many chars | |
390 const unsigned int charsSearchedThreshold = 500; | |
391 // This is the absolute max we search. We allow a little more slop than | |
392 // charsSearchedThreshold, to make it more likely that we'll search whole no
des. | |
393 const unsigned int maxCharsSearched = 600; | |
394 // If the starting element is within a table, the cell that contains it | |
395 HTMLTableCellElement* startingTableCell = 0; | |
396 bool searchedCellAbove = false; | |
397 | |
398 // walk backwards in the node tree, until another element, or form, or end o
f tree | |
399 int unsigned lengthSearched = 0; | |
400 Node* n; | |
401 for (n = element->traversePreviousNode(); | |
402 n && lengthSearched < charsSearchedThreshold; | |
403 n = n->traversePreviousNode()) | |
404 { | |
405 if (n->hasTagName(formTag) | |
406 || (n->isHTMLElement() | |
407 && static_cast<HTMLElement*>(n)->isGenericFormElement())) | |
408 { | |
409 // We hit another form element or the start of the form - bail out | |
410 break; | |
411 } else if (n->hasTagName(tdTag) && !startingTableCell) { | |
412 startingTableCell = static_cast<HTMLTableCellElement*>(n); | |
413 } else if (n->hasTagName(trTag) && startingTableCell) { | |
414 String result = searchForLabelsAboveCell(regExp.get(), startingTable
Cell); | |
415 if (!result.isEmpty()) | |
416 return result; | |
417 searchedCellAbove = true; | |
418 } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->v
isibility() == VISIBLE) { | |
419 // For each text chunk, run the regexp | |
420 String nodeString = n->nodeValue(); | |
421 // add 100 for slop, to make it more likely that we'll search whole
nodes | |
422 if (lengthSearched + nodeString.length() > maxCharsSearched) | |
423 nodeString = nodeString.right(charsSearchedThreshold - lengthSea
rched); | |
424 int pos = regExp->searchRev(nodeString); | |
425 if (pos >= 0) | |
426 return nodeString.substring(pos, regExp->matchedLength()); | |
427 lengthSearched += nodeString.length(); | |
428 } | |
429 } | |
430 | |
431 // If we started in a cell, but bailed because we found the start of the for
m or the | |
432 // previous element, we still might need to search the row above us for a la
bel. | |
433 if (startingTableCell && !searchedCellAbove) { | |
434 return searchForLabelsAboveCell(regExp.get(), startingTableCell); | |
435 } | |
436 return String(); | |
437 } | |
438 | |
439 String Frame::matchLabelsAgainstElement(const Vector<String>& labels, Element* e
lement) | |
440 { | |
441 String name = element->getAttribute(nameAttr); | |
442 if (name.isEmpty()) | |
443 return String(); | |
444 | |
445 // Make numbers and _'s in field names behave like word boundaries, e.g., "a
ddress2" | |
446 replace(name, RegularExpression("\\d"), " "); | |
447 name.replace('_', ' '); | |
448 | |
449 OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels)); | |
450 // Use the largest match we can find in the whole name string | |
451 int pos; | |
452 int length; | |
453 int bestPos = -1; | |
454 int bestLength = -1; | |
455 int start = 0; | |
456 do { | |
457 pos = regExp->search(name, start); | |
458 if (pos != -1) { | |
459 length = regExp->matchedLength(); | |
460 if (length >= bestLength) { | |
461 bestPos = pos; | |
462 bestLength = length; | |
463 } | |
464 start = pos + 1; | |
465 } | |
466 } while (pos != -1); | |
467 | |
468 if (bestPos != -1) | |
469 return name.substring(bestPos, bestLength); | |
470 return String(); | |
471 } | |
472 | |
473 const Selection& Frame::mark() const | |
474 { | |
475 return d->m_mark; | |
476 } | |
477 | |
478 void Frame::setMark(const Selection& s) | |
479 { | |
480 ASSERT(!s.base().node() || s.base().node()->document() == document()); | |
481 ASSERT(!s.extent().node() || s.extent().node()->document() == document()); | |
482 ASSERT(!s.start().node() || s.start().node()->document() == document()); | |
483 ASSERT(!s.end().node() || s.end().node()->document() == document()); | |
484 | |
485 d->m_mark = s; | |
486 } | |
487 | |
488 void Frame::notifyRendererOfSelectionChange(bool userTriggered) | |
489 { | |
490 RenderObject* renderer = 0; | |
491 if (selection()->rootEditableElement()) | |
492 renderer = selection()->rootEditableElement()->shadowAncestorNode()->ren
derer(); | |
493 | |
494 // If the current selection is in a textfield or textarea, notify the render
er that the selection has changed | |
495 if (renderer && (renderer->isTextArea() || renderer->isTextField())) | |
496 static_cast<RenderTextControl*>(renderer)->selectionChanged(userTriggere
d); | |
497 } | |
498 | |
499 void Frame::invalidateSelection() | |
500 { | |
501 selection()->setNeedsLayout(); | |
502 selectionLayoutChanged(); | |
503 } | |
504 | |
505 void Frame::setCaretVisible(bool flag) | |
506 { | |
507 if (d->m_caretVisible == flag) | |
508 return; | |
509 clearCaretRectIfNeeded(); | |
510 d->m_caretVisible = flag; | |
511 selectionLayoutChanged(); | |
512 } | |
513 | |
514 void Frame::clearCaretRectIfNeeded() | |
515 { | |
516 if (d->m_caretPaint) { | |
517 d->m_caretPaint = false; | |
518 selection()->invalidateCaretRect(); | |
519 } | |
520 } | |
521 | |
522 // Helper function that tells whether a particular node is an element that has a
n entire | |
523 // Frame and FrameView, a <frame>, <iframe>, or <object>. | |
524 static bool isFrameElement(const Node *n) | |
525 { | |
526 if (!n) | |
527 return false; | |
528 RenderObject *renderer = n->renderer(); | |
529 if (!renderer || !renderer->isWidget()) | |
530 return false; | |
531 Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); | |
532 return widget && widget->isFrameView(); | |
533 } | |
534 | |
535 void Frame::setFocusedNodeIfNeeded() | |
536 { | |
537 if (!document() || selection()->isNone() || !selection()->isFocusedAndActive
()) | |
538 return; | |
539 | |
540 Node* target = selection()->rootEditableElement(); | |
541 if (target) { | |
542 RenderObject* renderer = target->renderer(); | |
543 | |
544 // Walk up the render tree to search for a node to focus. | |
545 // Walking up the DOM tree wouldn't work for shadow trees, like those be
hind the engine-based text fields. | |
546 while (renderer) { | |
547 // We don't want to set focus on a subframe when selecting in a pare
nt frame, | |
548 // so add the !isFrameElement check here. There's probably a better
way to make this | |
549 // work in the long term, but this is the safest fix at this time. | |
550 if (target && target->isMouseFocusable() && !isFrameElement(target))
{ | |
551 page()->focusController()->setFocusedNode(target, this); | |
552 return; | |
553 } | |
554 renderer = renderer->parent(); | |
555 if (renderer) | |
556 target = renderer->element(); | |
557 } | |
558 document()->setFocusedNode(0); | |
559 } | |
560 } | |
561 | |
562 void Frame::selectionLayoutChanged() | |
563 { | |
564 bool caretRectChanged = selection()->recomputeCaretRect(); | |
565 | |
566 bool shouldBlink = d->m_caretVisible | |
567 && selection()->isCaret() && selection()->isContentEditable(); | |
568 | |
569 // If the caret moved, stop the blink timer so we can restart with a | |
570 // black caret in the new location. | |
571 if (caretRectChanged || !shouldBlink) | |
572 d->m_caretBlinkTimer.stop(); | |
573 | |
574 // Start blinking with a black caret. Be sure not to restart if we're | |
575 // already blinking in the right location. | |
576 if (shouldBlink && !d->m_caretBlinkTimer.isActive()) { | |
577 d->m_caretBlinkTimer.startRepeating(theme()->caretBlinkFrequency()); | |
578 if (!d->m_caretPaint) { | |
579 d->m_caretPaint = true; | |
580 selection()->invalidateCaretRect(); | |
581 } | |
582 } | |
583 | |
584 RenderView* canvas = contentRenderer(); | |
585 if (!canvas) | |
586 return; | |
587 | |
588 Selection selection = this->selection()->selection(); | |
589 | |
590 if (!selection.isRange()) | |
591 canvas->clearSelection(); | |
592 else { | |
593 // Use the rightmost candidate for the start of the selection, and the l
eftmost candidate for the end of the selection. | |
594 // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo'
, and that 'bar' is selected. If we pass [foo, 3] | |
595 // as the start of the selection, the selection painting code will think
that content on the line containing 'foo' is selected | |
596 // and will fill the gap before 'bar'. | |
597 Position startPos = selection.start(); | |
598 if (startPos.downstream().isCandidate()) | |
599 startPos = startPos.downstream(); | |
600 Position endPos = selection.end(); | |
601 if (endPos.upstream().isCandidate()) | |
602 endPos = endPos.upstream(); | |
603 | |
604 // We can get into a state where the selection endpoints map to the same
VisiblePosition when a selection is deleted | |
605 // because we don't yet notify the SelectionController of text removal. | |
606 if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart
() != selection.visibleEnd()) { | |
607 RenderObject *startRenderer = startPos.node()->renderer(); | |
608 RenderObject *endRenderer = endPos.node()->renderer(); | |
609 canvas->setSelection(startRenderer, startPos.offset(), endRenderer,
endPos.offset()); | |
610 } | |
611 } | |
612 } | |
613 | |
614 void Frame::caretBlinkTimerFired(Timer<Frame>*) | |
615 { | |
616 ASSERT(d->m_caretVisible); | |
617 ASSERT(selection()->isCaret()); | |
618 bool caretPaint = d->m_caretPaint; | |
619 if (selection()->isCaretBlinkingSuspended() && caretPaint) | |
620 return; | |
621 d->m_caretPaint = !caretPaint; | |
622 selection()->invalidateCaretRect(); | |
623 } | |
624 | |
625 void Frame::paintCaret(GraphicsContext* p, const IntRect& rect) const | |
626 { | |
627 if (d->m_caretPaint && d->m_caretVisible) | |
628 selection()->paintCaret(p, rect); | |
629 } | |
630 | |
631 void Frame::paintDragCaret(GraphicsContext* p, const IntRect& rect) const | |
632 { | |
633 SelectionController* dragCaretController = d->m_page->dragCaretController(); | |
634 ASSERT(dragCaretController->selection().isCaret()); | |
635 if (dragCaretController->selection().start().node()->document()->frame() ==
this) | |
636 dragCaretController->paintCaret(p, rect); | |
637 } | |
638 | |
639 float Frame::zoomFactor() const | |
640 { | |
641 return d->m_zoomFactor; | |
642 } | |
643 | |
644 bool Frame::isZoomFactorTextOnly() const | |
645 { | |
646 return d->m_page->settings()->zoomsTextOnly(); | |
647 } | |
648 | |
649 bool Frame::shouldApplyTextZoom() const | |
650 { | |
651 if (d->m_zoomFactor == 1.0f || !isZoomFactorTextOnly()) | |
652 return false; | |
653 #if ENABLE(SVG) | |
654 if (d->m_doc && d->m_doc->isSVGDocument()) | |
655 return false; | |
656 #endif | |
657 return true; | |
658 } | |
659 | |
660 bool Frame::shouldApplyPageZoom() const | |
661 { | |
662 if (d->m_zoomFactor == 1.0f || isZoomFactorTextOnly()) | |
663 return false; | |
664 #if ENABLE(SVG) | |
665 if (d->m_doc && d->m_doc->isSVGDocument()) | |
666 return false; | |
667 #endif | |
668 return true; | |
669 } | |
670 | |
671 void Frame::setZoomFactor(float percent, bool isTextOnly) | |
672 { | |
673 if (d->m_zoomFactor == percent && isZoomFactorTextOnly()) | |
674 return; | |
675 | |
676 #if ENABLE(SVG) | |
677 // SVG doesn't care if the zoom factor is text only. It will always apply a
| |
678 // zoom to the whole SVG. | |
679 if (d->m_doc && d->m_doc->isSVGDocument()) { | |
680 if (!static_cast<SVGDocument*>(d->m_doc.get())->zoomAndPanEnabled()) | |
681 return; | |
682 d->m_zoomFactor = percent; | |
683 d->m_page->settings()->setZoomsTextOnly(true); // We do this to avoid do
ing any scaling of CSS pixels, since the SVG has its own notion of zoom. | |
684 if (d->m_doc->renderer()) | |
685 d->m_doc->renderer()->repaint(); | |
686 return; | |
687 } | |
688 #endif | |
689 | |
690 d->m_zoomFactor = percent; | |
691 d->m_page->settings()->setZoomsTextOnly(isTextOnly); | |
692 | |
693 if (d->m_doc) | |
694 d->m_doc->recalcStyle(Node::Force); | |
695 | |
696 for (Frame* child = tree()->firstChild(); child; child = child->tree()->next
Sibling()) | |
697 child->setZoomFactor(d->m_zoomFactor, isTextOnly); | |
698 | |
699 if (d->m_doc && d->m_doc->renderer() && d->m_doc->renderer()->needsLayout()
&& view()->didFirstLayout()) | |
700 view()->layout(); | |
701 } | |
702 | |
703 void Frame::setPrinting(bool printing, float minPageWidth, float maxPageWidth, b
ool adjustViewSize) | |
704 { | |
705 if (!d->m_doc) | |
706 return; | |
707 | |
708 d->m_doc->setPrinting(printing); | |
709 view()->setMediaType(printing ? "print" : "screen"); | |
710 d->m_doc->updateStyleSelector(); | |
711 forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, adjustViewSize); | |
712 | |
713 for (Frame* child = tree()->firstChild(); child; child = child->tree()->next
Sibling()) | |
714 child->setPrinting(printing, minPageWidth, maxPageWidth, adjustViewSize)
; | |
715 } | |
716 | |
717 void Frame::setJSStatusBarText(const String& text) | |
718 { | |
719 d->m_kjsStatusBarText = text; | |
720 if (d->m_page) | |
721 d->m_page->chrome()->setStatusbarText(this, d->m_kjsStatusBarText); | |
722 } | |
723 | |
724 void Frame::setJSDefaultStatusBarText(const String& text) | |
725 { | |
726 d->m_kjsDefaultStatusBarText = text; | |
727 if (d->m_page) | |
728 d->m_page->chrome()->setStatusbarText(this, d->m_kjsDefaultStatusBarText
); | |
729 } | |
730 | |
731 String Frame::jsStatusBarText() const | |
732 { | |
733 return d->m_kjsStatusBarText; | |
734 } | |
735 | |
736 String Frame::jsDefaultStatusBarText() const | |
737 { | |
738 return d->m_kjsDefaultStatusBarText; | |
739 } | |
740 | |
741 void Frame::setNeedsReapplyStyles() | |
742 { | |
743 if (d->m_needsReapplyStyles) | |
744 return; | |
745 | |
746 d->m_needsReapplyStyles = true; | |
747 | |
748 // Invalidate the FrameView so that FrameView::layout will get called, | |
749 // which calls reapplyStyles. | |
750 FrameView* curView = view(); | |
751 if (curView) | |
752 curView->invalidate(); | |
753 } | |
754 | |
755 bool Frame::needsReapplyStyles() const | |
756 { | |
757 return d->m_needsReapplyStyles; | |
758 } | |
759 | |
760 void Frame::reapplyStyles() | |
761 { | |
762 d->m_needsReapplyStyles = false; | |
763 | |
764 // FIXME: This call doesn't really make sense in a method called | |
765 // "reapplyStyles". We should probably eventually move it into its own | |
766 // method. | |
767 if (d->m_doc) | |
768 d->m_doc->docLoader()->setAutoLoadImages(d->m_page && d->m_page->setting
s()->loadsImagesAutomatically()); | |
769 | |
770 #if FRAME_LOADS_USER_STYLESHEET | |
771 const KURL userStyleSheetLocation = d->m_page ? d->m_page->settings()->userS
tyleSheetLocation() : KURL(); | |
772 if (!userStyleSheetLocation.isEmpty()) | |
773 setUserStyleSheetLocation(userStyleSheetLocation); | |
774 else | |
775 setUserStyleSheet(String()); | |
776 #endif | |
777 | |
778 // FIXME: It's not entirely clear why the following is needed. | |
779 // The document automatically does this as required when you set the style s
heet. | |
780 // But we had problems when this code was removed. Details are in | |
781 // <http://bugs.webkit.org/show_bug.cgi?id=8079>. | |
782 if (d->m_doc) | |
783 d->m_doc->updateStyleSelector(); | |
784 } | |
785 | |
786 bool Frame::shouldChangeSelection(const Selection& newSelection) const | |
787 { | |
788 return shouldChangeSelection(selection()->selection(), newSelection, newSele
ction.affinity(), false); | |
789 } | |
790 | |
791 bool Frame::shouldChangeSelection(const Selection& oldSelection, const Selection
& newSelection, EAffinity affinity, bool stillSelecting) const | |
792 { | |
793 return editor()->client()->shouldChangeSelectedRange(oldSelection.toRange().
get(), newSelection.toRange().get(), | |
794 affinity, stillSelectin
g); | |
795 } | |
796 | |
797 bool Frame::shouldDeleteSelection(const Selection& selection) const | |
798 { | |
799 return editor()->client()->shouldDeleteRange(selection.toRange().get()); | |
800 } | |
801 | |
802 bool Frame::isContentEditable() const | |
803 { | |
804 if (d->m_editor.clientIsEditable()) | |
805 return true; | |
806 if (!d->m_doc) | |
807 return false; | |
808 return d->m_doc->inDesignMode(); | |
809 } | |
810 | |
811 #if !PLATFORM(MAC) | |
812 | |
813 void Frame::setUseSecureKeyboardEntry(bool) | |
814 { | |
815 } | |
816 | |
817 #endif | |
818 | |
819 void Frame::updateSecureKeyboardEntryIfActive() | |
820 { | |
821 if (selection()->isFocusedAndActive()) | |
822 setUseSecureKeyboardEntry(d->m_doc->useSecureKeyboardEntryWhenActive()); | |
823 } | |
824 | |
825 CSSMutableStyleDeclaration *Frame::typingStyle() const | |
826 { | |
827 return d->m_typingStyle.get(); | |
828 } | |
829 | |
830 void Frame::setTypingStyle(CSSMutableStyleDeclaration *style) | |
831 { | |
832 d->m_typingStyle = style; | |
833 } | |
834 | |
835 void Frame::clearTypingStyle() | |
836 { | |
837 d->m_typingStyle = 0; | |
838 } | |
839 | |
840 void Frame::computeAndSetTypingStyle(CSSStyleDeclaration *style, EditAction edit
ingAction) | |
841 { | |
842 if (!style || style->length() == 0) { | |
843 clearTypingStyle(); | |
844 return; | |
845 } | |
846 | |
847 // Calculate the current typing style. | |
848 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable(); | |
849 if (typingStyle()) { | |
850 typingStyle()->merge(mutableStyle.get()); | |
851 mutableStyle = typingStyle(); | |
852 } | |
853 | |
854 Node* node = selection()->selection().visibleStart().deepEquivalent().node()
; | |
855 computedStyle(node)->diff(mutableStyle.get()); | |
856 | |
857 // Handle block styles, substracting these from the typing style. | |
858 RefPtr<CSSMutableStyleDeclaration> blockStyle = mutableStyle->copyBlockPrope
rties(); | |
859 blockStyle->diff(mutableStyle.get()); | |
860 if (document() && blockStyle->length() > 0) | |
861 applyCommand(ApplyStyleCommand::create(document(), blockStyle.get(), edi
tingAction)); | |
862 | |
863 // Set the remaining style as the typing style. | |
864 d->m_typingStyle = mutableStyle.release(); | |
865 } | |
866 | |
867 String Frame::selectionStartStylePropertyValue(int stylePropertyID) const | |
868 { | |
869 Node *nodeToRemove; | |
870 RefPtr<CSSStyleDeclaration> selectionStyle = selectionComputedStyle(nodeToRe
move); | |
871 if (!selectionStyle) | |
872 return String(); | |
873 | |
874 String value = selectionStyle->getPropertyValue(stylePropertyID); | |
875 | |
876 if (nodeToRemove) { | |
877 ExceptionCode ec = 0; | |
878 nodeToRemove->remove(ec); | |
879 ASSERT(ec == 0); | |
880 } | |
881 | |
882 return value; | |
883 } | |
884 | |
885 PassRefPtr<CSSComputedStyleDeclaration> Frame::selectionComputedStyle(Node*& nod
eToRemove) const | |
886 { | |
887 nodeToRemove = 0; | |
888 | |
889 if (!document()) | |
890 return 0; | |
891 | |
892 if (selection()->isNone()) | |
893 return 0; | |
894 | |
895 RefPtr<Range> range(selection()->toRange()); | |
896 Position pos = range->editingStartPosition(); | |
897 | |
898 Element *elem = pos.element(); | |
899 if (!elem) | |
900 return 0; | |
901 | |
902 RefPtr<Element> styleElement = elem; | |
903 ExceptionCode ec = 0; | |
904 | |
905 if (d->m_typingStyle) { | |
906 styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec
); | |
907 ASSERT(ec == 0); | |
908 | |
909 styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl()
, ec); | |
910 ASSERT(ec == 0); | |
911 | |
912 styleElement->appendChild(document()->createEditingTextNode(""), ec); | |
913 ASSERT(ec == 0); | |
914 | |
915 if (elem->renderer() && elem->renderer()->canHaveChildren()) { | |
916 elem->appendChild(styleElement, ec); | |
917 } else { | |
918 Node *parent = elem->parent(); | |
919 Node *next = elem->nextSibling(); | |
920 | |
921 if (next) { | |
922 parent->insertBefore(styleElement, next, ec); | |
923 } else { | |
924 parent->appendChild(styleElement, ec); | |
925 } | |
926 } | |
927 ASSERT(ec == 0); | |
928 | |
929 nodeToRemove = styleElement.get(); | |
930 } | |
931 | |
932 return computedStyle(styleElement.release()); | |
933 } | |
934 | |
935 void Frame::textFieldDidBeginEditing(Element* e) | |
936 { | |
937 if (editor()->client()) | |
938 editor()->client()->textFieldDidBeginEditing(e); | |
939 } | |
940 | |
941 void Frame::textFieldDidEndEditing(Element* e) | |
942 { | |
943 if (editor()->client()) | |
944 editor()->client()->textFieldDidEndEditing(e); | |
945 } | |
946 | |
947 void Frame::textDidChangeInTextField(Element* e) | |
948 { | |
949 if (editor()->client()) | |
950 editor()->client()->textDidChangeInTextField(e); | |
951 } | |
952 | |
953 bool Frame::doTextFieldCommandFromEvent(Element* e, KeyboardEvent* ke) | |
954 { | |
955 if (editor()->client()) | |
956 return editor()->client()->doTextFieldCommandFromEvent(e, ke); | |
957 | |
958 return false; | |
959 } | |
960 | |
961 void Frame::textWillBeDeletedInTextField(Element* input) | |
962 { | |
963 if (editor()->client()) | |
964 editor()->client()->textWillBeDeletedInTextField(input); | |
965 } | |
966 | |
967 void Frame::textDidChangeInTextArea(Element* e) | |
968 { | |
969 if (editor()->client()) | |
970 editor()->client()->textDidChangeInTextArea(e); | |
971 } | |
972 | |
973 void Frame::applyEditingStyleToBodyElement() const | |
974 { | |
975 if (!d->m_doc) | |
976 return; | |
977 | |
978 RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body"); | |
979 unsigned len = list->length(); | |
980 for (unsigned i = 0; i < len; i++) { | |
981 applyEditingStyleToElement(static_cast<Element*>(list->item(i))); | |
982 } | |
983 } | |
984 | |
985 void Frame::removeEditingStyleFromBodyElement() const | |
986 { | |
987 if (!d->m_doc) | |
988 return; | |
989 | |
990 RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body"); | |
991 unsigned len = list->length(); | |
992 for (unsigned i = 0; i < len; i++) { | |
993 removeEditingStyleFromElement(static_cast<Element*>(list->item(i))); | |
994 } | |
995 } | |
996 | |
997 void Frame::applyEditingStyleToElement(Element* element) const | |
998 { | |
999 if (!element) | |
1000 return; | |
1001 | |
1002 CSSStyleDeclaration* style = element->style(); | |
1003 ASSERT(style); | |
1004 | |
1005 ExceptionCode ec = 0; | |
1006 style->setProperty(CSSPropertyWordWrap, "break-word", false, ec); | |
1007 ASSERT(ec == 0); | |
1008 style->setProperty(CSSPropertyWebkitNbspMode, "space", false, ec); | |
1009 ASSERT(ec == 0); | |
1010 style->setProperty(CSSPropertyWebkitLineBreak, "after-white-space", false, e
c); | |
1011 ASSERT(ec == 0); | |
1012 } | |
1013 | |
1014 void Frame::removeEditingStyleFromElement(Element*) const | |
1015 { | |
1016 } | |
1017 | |
1018 #ifndef NDEBUG | |
1019 static HashSet<Frame*>& keepAliveSet() | |
1020 { | |
1021 static HashSet<Frame*> staticKeepAliveSet; | |
1022 return staticKeepAliveSet; | |
1023 } | |
1024 #endif | |
1025 | |
1026 void Frame::keepAlive() | |
1027 { | |
1028 if (d->m_lifeSupportTimer.isActive()) | |
1029 return; | |
1030 #ifndef NDEBUG | |
1031 keepAliveSet().add(this); | |
1032 #endif | |
1033 ref(); | |
1034 d->m_lifeSupportTimer.startOneShot(0); | |
1035 } | |
1036 | |
1037 #ifndef NDEBUG | |
1038 void Frame::cancelAllKeepAlive() | |
1039 { | |
1040 HashSet<Frame*>::iterator end = keepAliveSet().end(); | |
1041 for (HashSet<Frame*>::iterator it = keepAliveSet().begin(); it != end; ++it)
{ | |
1042 Frame* frame = *it; | |
1043 frame->d->m_lifeSupportTimer.stop(); | |
1044 frame->deref(); | |
1045 } | |
1046 keepAliveSet().clear(); | |
1047 } | |
1048 #endif | |
1049 | |
1050 void Frame::lifeSupportTimerFired(Timer<Frame>*) | |
1051 { | |
1052 #ifndef NDEBUG | |
1053 keepAliveSet().remove(this); | |
1054 #endif | |
1055 deref(); | |
1056 } | |
1057 | |
1058 void Frame::clearDOMWindow() | |
1059 { | |
1060 if (d->m_domWindow) { | |
1061 d->m_liveFormerWindows.add(d->m_domWindow.get()); | |
1062 d->m_domWindow->clear(); | |
1063 } | |
1064 d->m_script.clearPluginObjects(); | |
1065 } | |
1066 | |
1067 RenderView* Frame::contentRenderer() const | |
1068 { | |
1069 Document* doc = document(); | |
1070 if (!doc) | |
1071 return 0; | |
1072 RenderObject* object = doc->renderer(); | |
1073 if (!object) | |
1074 return 0; | |
1075 ASSERT(object->isRenderView()); | |
1076 return static_cast<RenderView*>(object); | |
1077 } | |
1078 | |
1079 HTMLFrameOwnerElement* Frame::ownerElement() const | |
1080 { | |
1081 return d->m_ownerElement; | |
1082 } | |
1083 | |
1084 RenderPart* Frame::ownerRenderer() const | |
1085 { | |
1086 HTMLFrameOwnerElement* ownerElement = d->m_ownerElement; | |
1087 if (!ownerElement) | |
1088 return 0; | |
1089 RenderObject* object = ownerElement->renderer(); | |
1090 if (!object) | |
1091 return 0; | |
1092 // FIXME: If <object> is ever fixed to disassociate itself from frames | |
1093 // that it has started but canceled, then this can turn into an ASSERT | |
1094 // since d->m_ownerElement would be 0 when the load is canceled. | |
1095 // https://bugs.webkit.org/show_bug.cgi?id=18585 | |
1096 if (!object->isRenderPart()) | |
1097 return 0; | |
1098 return static_cast<RenderPart*>(object); | |
1099 } | |
1100 | |
1101 bool Frame::isDisconnected() const | |
1102 { | |
1103 return d->m_isDisconnected; | |
1104 } | |
1105 | |
1106 void Frame::setIsDisconnected(bool isDisconnected) | |
1107 { | |
1108 d->m_isDisconnected = isDisconnected; | |
1109 } | |
1110 | |
1111 bool Frame::excludeFromTextSearch() const | |
1112 { | |
1113 return d->m_excludeFromTextSearch; | |
1114 } | |
1115 | |
1116 void Frame::setExcludeFromTextSearch(bool exclude) | |
1117 { | |
1118 d->m_excludeFromTextSearch = exclude; | |
1119 } | |
1120 | |
1121 // returns FloatRect because going through IntRect would truncate any floats | |
1122 FloatRect Frame::selectionRect(bool clipToVisibleContent) const | |
1123 { | |
1124 RenderView* root = contentRenderer(); | |
1125 if (!root) | |
1126 return IntRect(); | |
1127 | |
1128 IntRect selectionRect = root->selectionRect(clipToVisibleContent); | |
1129 return clipToVisibleContent ? intersection(selectionRect, d->m_view->visible
ContentRect()) : selectionRect; | |
1130 } | |
1131 | |
1132 void Frame::selectionTextRects(Vector<FloatRect>& rects, bool clipToVisibleConte
nt) const | |
1133 { | |
1134 RenderView* root = contentRenderer(); | |
1135 if (!root) | |
1136 return; | |
1137 | |
1138 RefPtr<Range> selectedRange = selection()->toRange(); | |
1139 | |
1140 Vector<IntRect> intRects; | |
1141 selectedRange->addLineBoxRects(intRects, true); | |
1142 | |
1143 unsigned size = intRects.size(); | |
1144 FloatRect visibleContentRect = d->m_view->visibleContentRect(); | |
1145 for (unsigned i = 0; i < size; ++i) | |
1146 if (clipToVisibleContent) | |
1147 rects.append(intersection(intRects[i], visibleContentRect)); | |
1148 else | |
1149 rects.append(intRects[i]); | |
1150 } | |
1151 | |
1152 | |
1153 bool Frame::isFrameSet() const | |
1154 { | |
1155 Document* document = d->m_doc.get(); | |
1156 if (!document || !document->isHTMLDocument()) | |
1157 return false; | |
1158 Node *body = static_cast<HTMLDocument*>(document)->body(); | |
1159 return body && body->renderer() && body->hasTagName(framesetTag); | |
1160 } | |
1161 | |
1162 // Scans logically forward from "start", including any child frames | |
1163 static HTMLFormElement *scanForForm(Node *start) | |
1164 { | |
1165 Node *n; | |
1166 for (n = start; n; n = n->traverseNextNode()) { | |
1167 if (n->hasTagName(formTag)) | |
1168 return static_cast<HTMLFormElement*>(n); | |
1169 else if (n->isHTMLElement() && static_cast<HTMLElement*>(n)->isGenericFo
rmElement()) | |
1170 return static_cast<HTMLFormControlElement*>(n)->form(); | |
1171 else if (n->hasTagName(frameTag) || n->hasTagName(iframeTag)) { | |
1172 Node *childDoc = static_cast<HTMLFrameElementBase*>(n)->contentDocum
ent(); | |
1173 if (HTMLFormElement *frameResult = scanForForm(childDoc)) | |
1174 return frameResult; | |
1175 } | |
1176 } | |
1177 return 0; | |
1178 } | |
1179 | |
1180 // We look for either the form containing the current focus, or for one immediat
ely after it | |
1181 HTMLFormElement *Frame::currentForm() const | |
1182 { | |
1183 // start looking either at the active (first responder) node, or where the s
election is | |
1184 Node *start = d->m_doc ? d->m_doc->focusedNode() : 0; | |
1185 if (!start) | |
1186 start = selection()->start().node(); | |
1187 | |
1188 // try walking up the node tree to find a form element | |
1189 Node *n; | |
1190 for (n = start; n; n = n->parentNode()) { | |
1191 if (n->hasTagName(formTag)) | |
1192 return static_cast<HTMLFormElement*>(n); | |
1193 else if (n->isHTMLElement() | |
1194 && static_cast<HTMLElement*>(n)->isGenericFormElement()) | |
1195 return static_cast<HTMLFormControlElement*>(n)->form(); | |
1196 } | |
1197 | |
1198 // try walking forward in the node tree to find a form element | |
1199 return start ? scanForForm(start) : 0; | |
1200 } | |
1201 | |
1202 // FIXME: should this go in SelectionController? | |
1203 void Frame::revealSelection(const RenderLayer::ScrollAlignment& alignment) const | |
1204 { | |
1205 IntRect rect; | |
1206 | |
1207 switch (selection()->state()) { | |
1208 case Selection::NONE: | |
1209 return; | |
1210 | |
1211 case Selection::CARET: | |
1212 rect = selection()->caretRect(); | |
1213 break; | |
1214 | |
1215 case Selection::RANGE: | |
1216 rect = enclosingIntRect(selectionRect(false)); | |
1217 break; | |
1218 } | |
1219 | |
1220 Position start = selection()->start(); | |
1221 | |
1222 ASSERT(start.node()); | |
1223 if (start.node() && start.node()->renderer()) { | |
1224 // FIXME: This code only handles scrolling the startContainer's layer, b
ut | |
1225 // the selection rect could intersect more than just that. | |
1226 // See <rdar://problem/4799899>. | |
1227 if (RenderLayer *layer = start.node()->renderer()->enclosingLayer()) | |
1228 layer->scrollRectToVisible(rect, false, alignment, alignment); | |
1229 } | |
1230 } | |
1231 | |
1232 void Frame::revealCaret(const RenderLayer::ScrollAlignment& alignment) const | |
1233 { | |
1234 if (selection()->isNone()) | |
1235 return; | |
1236 | |
1237 Position extent = selection()->extent(); | |
1238 if (extent.node() && extent.node()->renderer()) { | |
1239 IntRect extentRect = VisiblePosition(extent).caretRect(); | |
1240 RenderLayer* layer = extent.node()->renderer()->enclosingLayer(); | |
1241 if (layer) | |
1242 layer->scrollRectToVisible(extentRect, false, alignment, alignment); | |
1243 } | |
1244 } | |
1245 | |
1246 // FIXME: why is this here instead of on the FrameView? | |
1247 void Frame::paint(GraphicsContext* p, const IntRect& rect) | |
1248 { | |
1249 #ifndef NDEBUG | |
1250 bool fillWithRed; | |
1251 if (!document() || document()->printing()) | |
1252 fillWithRed = false; // Printing, don't fill with red (can't remember wh
y). | |
1253 else if (document()->ownerElement()) | |
1254 fillWithRed = false; // Subframe, don't fill with red. | |
1255 else if (view() && view()->isTransparent()) | |
1256 fillWithRed = false; // Transparent, don't fill with red. | |
1257 else if (d->m_paintRestriction == PaintRestrictionSelectionOnly || d->m_pain
tRestriction == PaintRestrictionSelectionOnlyBlackText) | |
1258 fillWithRed = false; // Selections are transparent, don't fill with red. | |
1259 else if (d->m_elementToDraw) | |
1260 fillWithRed = false; // Element images are transparent, don't fill with
red. | |
1261 else | |
1262 fillWithRed = true; | |
1263 | |
1264 if (fillWithRed) | |
1265 p->fillRect(rect, Color(0xFF, 0, 0)); | |
1266 #endif | |
1267 | |
1268 bool isTopLevelPainter = !s_currentPaintTimeStamp; | |
1269 if (isTopLevelPainter) | |
1270 s_currentPaintTimeStamp = currentTime(); | |
1271 | |
1272 if (contentRenderer()) { | |
1273 ASSERT(d->m_view && !d->m_view->needsLayout()); | |
1274 ASSERT(!d->m_isPainting); | |
1275 | |
1276 d->m_isPainting = true; | |
1277 | |
1278 // d->m_elementToDraw is used to draw only one element | |
1279 RenderObject* eltRenderer = d->m_elementToDraw ? d->m_elementToDraw->ren
derer() : 0; | |
1280 if (d->m_paintRestriction == PaintRestrictionNone) | |
1281 document()->invalidateRenderedRectsForMarkersInRect(rect); | |
1282 contentRenderer()->layer()->paint(p, rect, d->m_paintRestriction, eltRen
derer); | |
1283 | |
1284 d->m_isPainting = false; | |
1285 | |
1286 #if ENABLE(DASHBOARD_SUPPORT) | |
1287 // Regions may have changed as a result of the visibility/z-index of ele
ment changing. | |
1288 if (document()->dashboardRegionsDirty()) | |
1289 view()->updateDashboardRegions(); | |
1290 #endif | |
1291 } else | |
1292 LOG_ERROR("called Frame::paint with nil renderer"); | |
1293 | |
1294 if (isTopLevelPainter) | |
1295 s_currentPaintTimeStamp = 0; | |
1296 } | |
1297 | |
1298 void Frame::setPaintRestriction(PaintRestriction pr) | |
1299 { | |
1300 d->m_paintRestriction = pr; | |
1301 } | |
1302 | |
1303 bool Frame::isPainting() const | |
1304 { | |
1305 return d->m_isPainting; | |
1306 } | |
1307 | |
1308 void Frame::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, fl
oat bottomLimit) | |
1309 { | |
1310 RenderView* root = contentRenderer(); | |
1311 if (root) { | |
1312 // Use a context with painting disabled. | |
1313 GraphicsContext context((PlatformGraphicsContext*)0); | |
1314 root->setTruncatedAt((int)floorf(oldBottom)); | |
1315 IntRect dirtyRect(0, (int)floorf(oldTop), root->docWidth(), (int)ceilf(o
ldBottom - oldTop)); | |
1316 root->layer()->paint(&context, dirtyRect); | |
1317 *newBottom = root->bestTruncatedAt(); | |
1318 if (*newBottom == 0) | |
1319 *newBottom = oldBottom; | |
1320 } else | |
1321 *newBottom = oldBottom; | |
1322 } | |
1323 | |
1324 Frame* Frame::frameForWidget(const Widget* widget) | |
1325 { | |
1326 ASSERT_ARG(widget, widget); | |
1327 | |
1328 if (RenderWidget* renderer = RenderWidget::find(widget)) | |
1329 if (Node* node = renderer->node()) | |
1330 return node->document()->frame(); | |
1331 | |
1332 // Assume all widgets are either a FrameView or owned by a RenderWidget. | |
1333 // FIXME: That assumption is not right for scroll bars! | |
1334 ASSERT(widget->isFrameView()); | |
1335 return static_cast<const FrameView*>(widget)->frame(); | |
1336 } | |
1337 | |
1338 void Frame::forceLayout(bool allowSubtree) | |
1339 { | |
1340 FrameView *v = d->m_view.get(); | |
1341 if (v) { | |
1342 v->layout(allowSubtree); | |
1343 // We cannot unschedule a pending relayout, since the force can be calle
d with | |
1344 // a tiny rectangle from a drawRect update. By unscheduling we in effec
t | |
1345 // "validate" and stop the necessary full repaint from occurring. Basic
ally any basic | |
1346 // append/remove DHTML is broken by this call. For now, I have removed
the optimization | |
1347 // until we have a better invalidation stategy. -dwh | |
1348 //v->unscheduleRelayout(); | |
1349 } | |
1350 } | |
1351 | |
1352 void Frame::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth
, bool adjustViewSize) | |
1353 { | |
1354 // Dumping externalRepresentation(m_frame->renderer()).ascii() is a good tri
ck to see | |
1355 // the state of things before and after the layout | |
1356 RenderView *root = static_cast<RenderView*>(document()->renderer()); | |
1357 if (root) { | |
1358 // This magic is basically copied from khtmlview::print | |
1359 int pageW = (int)ceilf(minPageWidth); | |
1360 root->setWidth(pageW); | |
1361 root->setNeedsLayoutAndPrefWidthsRecalc(); | |
1362 forceLayout(); | |
1363 | |
1364 // If we don't fit in the minimum page width, we'll lay out again. If we
don't fit in the | |
1365 // maximum page width, we will lay out to the maximum page width and cli
p extra content. | |
1366 // FIXME: We are assuming a shrink-to-fit printing implementation. A cr
opping | |
1367 // implementation should not do this! | |
1368 int rightmostPos = root->rightmostPosition(); | |
1369 if (rightmostPos > minPageWidth) { | |
1370 pageW = min(rightmostPos, (int)ceilf(maxPageWidth)); | |
1371 root->setWidth(pageW); | |
1372 root->setNeedsLayoutAndPrefWidthsRecalc(); | |
1373 forceLayout(); | |
1374 } | |
1375 } | |
1376 | |
1377 if (adjustViewSize && view()) | |
1378 view()->adjustViewSize(); | |
1379 } | |
1380 | |
1381 void Frame::sendResizeEvent() | |
1382 { | |
1383 if (Document* doc = document()) | |
1384 doc->dispatchWindowEvent(EventNames::resizeEvent, false, false); | |
1385 } | |
1386 | |
1387 void Frame::sendScrollEvent() | |
1388 { | |
1389 FrameView* v = d->m_view.get(); | |
1390 if (!v) | |
1391 return; | |
1392 v->setWasScrolledByUser(true); | |
1393 Document* doc = document(); | |
1394 if (!doc) | |
1395 return; | |
1396 doc->dispatchHTMLEvent(scrollEvent, true, false); | |
1397 } | |
1398 | |
1399 void Frame::clearTimers(FrameView *view, Document *document) | |
1400 { | |
1401 if (view) { | |
1402 view->unscheduleRelayout(); | |
1403 if (view->frame()) { | |
1404 if (document && document->renderer() && document->renderer()->hasLay
er()) | |
1405 document->renderer()->layer()->suspendMarquees(); | |
1406 view->frame()->animation()->suspendAnimations(document); | |
1407 view->frame()->eventHandler()->stopAutoscrollTimer(); | |
1408 } | |
1409 } | |
1410 } | |
1411 | |
1412 void Frame::clearTimers() | |
1413 { | |
1414 clearTimers(d->m_view.get(), document()); | |
1415 } | |
1416 | |
1417 RenderStyle *Frame::styleForSelectionStart(Node *&nodeToRemove) const | |
1418 { | |
1419 nodeToRemove = 0; | |
1420 | |
1421 if (!document()) | |
1422 return 0; | |
1423 if (selection()->isNone()) | |
1424 return 0; | |
1425 | |
1426 Position pos = selection()->selection().visibleStart().deepEquivalent(); | |
1427 if (!pos.isCandidate()) | |
1428 return 0; | |
1429 Node *node = pos.node(); | |
1430 if (!node) | |
1431 return 0; | |
1432 | |
1433 if (!d->m_typingStyle) | |
1434 return node->renderer()->style(); | |
1435 | |
1436 ExceptionCode ec = 0; | |
1437 RefPtr<Element> styleElement = document()->createElementNS(xhtmlNamespaceURI
, "span", ec); | |
1438 ASSERT(ec == 0); | |
1439 | |
1440 String styleText = d->m_typingStyle->cssText() + " display: inline"; | |
1441 styleElement->setAttribute(styleAttr, styleText.impl(), ec); | |
1442 ASSERT(ec == 0); | |
1443 | |
1444 styleElement->appendChild(document()->createEditingTextNode(""), ec); | |
1445 ASSERT(ec == 0); | |
1446 | |
1447 node->parentNode()->appendChild(styleElement, ec); | |
1448 ASSERT(ec == 0); | |
1449 | |
1450 nodeToRemove = styleElement.get(); | |
1451 return styleElement->renderer() ? styleElement->renderer()->style() : 0; | |
1452 } | |
1453 | |
1454 void Frame::setSelectionFromNone() | |
1455 { | |
1456 // Put a caret inside the body if the entire frame is editable (either the | |
1457 // entire WebView is editable or designMode is on for this document). | |
1458 Document *doc = document(); | |
1459 if (!doc || !selection()->isNone() || !isContentEditable()) | |
1460 return; | |
1461 | |
1462 Node* node = doc->documentElement(); | |
1463 while (node && !node->hasTagName(bodyTag)) | |
1464 node = node->traverseNextNode(); | |
1465 if (node) | |
1466 selection()->setSelection(Selection(Position(node, 0), DOWNSTREAM)); | |
1467 } | |
1468 | |
1469 bool Frame::inViewSourceMode() const | |
1470 { | |
1471 return d->m_inViewSourceMode; | |
1472 } | |
1473 | |
1474 void Frame::setInViewSourceMode(bool mode) const | |
1475 { | |
1476 d->m_inViewSourceMode = mode; | |
1477 } | |
1478 | |
1479 UChar Frame::backslashAsCurrencySymbol() const | |
1480 { | |
1481 Document *doc = document(); | |
1482 if (!doc) | |
1483 return '\\'; | |
1484 TextResourceDecoder *decoder = doc->decoder(); | |
1485 if (!decoder) | |
1486 return '\\'; | |
1487 | |
1488 return decoder->encoding().backslashAsCurrencySymbol(); | |
1489 } | |
1490 | |
1491 // Searches from the beginning of the document if nothing is selected. | |
1492 bool Frame::findString(const String& target, bool forward, bool caseFlag, bool w
rapFlag, bool startInSelection) | |
1493 { | |
1494 if (target.isEmpty() || !document()) | |
1495 return false; | |
1496 | |
1497 if (excludeFromTextSearch()) | |
1498 return false; | |
1499 | |
1500 // Start from an edge of the selection, if there's a selection that's not in
shadow content. Which edge | |
1501 // is used depends on whether we're searching forward or backward, and wheth
er startInSelection is set. | |
1502 RefPtr<Range> searchRange(rangeOfContents(document())); | |
1503 Selection selection = this->selection()->selection(); | |
1504 | |
1505 if (forward) | |
1506 setStart(searchRange.get(), startInSelection ? selection.visibleStart()
: selection.visibleEnd()); | |
1507 else | |
1508 setEnd(searchRange.get(), startInSelection ? selection.visibleEnd() : se
lection.visibleStart()); | |
1509 | |
1510 Node* shadowTreeRoot = selection.shadowTreeRootNode(); | |
1511 if (shadowTreeRoot) { | |
1512 ExceptionCode ec = 0; | |
1513 if (forward) | |
1514 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount()
, ec); | |
1515 else | |
1516 searchRange->setStart(shadowTreeRoot, 0, ec); | |
1517 } | |
1518 | |
1519 RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, forward,
caseFlag)); | |
1520 // If we started in the selection and the found range exactly matches the ex
isting selection, find again. | |
1521 // Build a selection with the found range to remove collapsed whitespace. | |
1522 // Compare ranges instead of selection objects to ignore the way that the cu
rrent selection was made. | |
1523 if (startInSelection && *Selection(resultRange.get()).toRange() == *selectio
n.toRange()) { | |
1524 searchRange = rangeOfContents(document()); | |
1525 if (forward) | |
1526 setStart(searchRange.get(), selection.visibleEnd()); | |
1527 else | |
1528 setEnd(searchRange.get(), selection.visibleStart()); | |
1529 | |
1530 if (shadowTreeRoot) { | |
1531 ExceptionCode ec = 0; | |
1532 if (forward) | |
1533 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCou
nt(), ec); | |
1534 else | |
1535 searchRange->setStart(shadowTreeRoot, 0, ec); | |
1536 } | |
1537 | |
1538 resultRange = findPlainText(searchRange.get(), target, forward, caseFlag
); | |
1539 } | |
1540 | |
1541 ExceptionCode exception = 0; | |
1542 | |
1543 // If nothing was found in the shadow tree, search in main content following
the shadow tree. | |
1544 if (resultRange->collapsed(exception) && shadowTreeRoot) { | |
1545 searchRange = rangeOfContents(document()); | |
1546 if (forward) | |
1547 searchRange->setStartAfter(shadowTreeRoot->shadowParentNode(), excep
tion); | |
1548 else | |
1549 searchRange->setEndBefore(shadowTreeRoot->shadowParentNode(), except
ion); | |
1550 | |
1551 resultRange = findPlainText(searchRange.get(), target, forward, caseFlag
); | |
1552 } | |
1553 | |
1554 if (!editor()->insideVisibleArea(resultRange.get())) { | |
1555 resultRange = editor()->nextVisibleRange(resultRange.get(), target, forw
ard, caseFlag, wrapFlag); | |
1556 if (!resultRange) | |
1557 return false; | |
1558 } | |
1559 | |
1560 // If we didn't find anything and we're wrapping, search again in the entire
document (this will | |
1561 // redundantly re-search the area already searched in some cases). | |
1562 if (resultRange->collapsed(exception) && wrapFlag) { | |
1563 searchRange = rangeOfContents(document()); | |
1564 resultRange = findPlainText(searchRange.get(), target, forward, caseFlag
); | |
1565 // We used to return false here if we ended up with the same range that
we started with | |
1566 // (e.g., the selection was already the only instance of this text). But
we decided that | |
1567 // this should be a success case instead, so we'll just fall through in
that case. | |
1568 } | |
1569 | |
1570 if (resultRange->collapsed(exception)) | |
1571 return false; | |
1572 | |
1573 this->selection()->setSelection(Selection(resultRange.get(), DOWNSTREAM)); | |
1574 revealSelection(); | |
1575 return true; | |
1576 } | |
1577 | |
1578 unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag, unsig
ned limit) | |
1579 { | |
1580 if (target.isEmpty() || !document()) | |
1581 return 0; | |
1582 | |
1583 RefPtr<Range> searchRange(rangeOfContents(document())); | |
1584 | |
1585 ExceptionCode exception = 0; | |
1586 unsigned matchCount = 0; | |
1587 do { | |
1588 RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true,
caseFlag)); | |
1589 if (resultRange->collapsed(exception)) { | |
1590 if (!resultRange->startContainer()->isInShadowTree()) | |
1591 break; | |
1592 | |
1593 searchRange = rangeOfContents(document()); | |
1594 searchRange->setStartAfter(resultRange->startContainer()->shadowAnce
storNode(), exception); | |
1595 continue; | |
1596 } | |
1597 | |
1598 // A non-collapsed result range can in some funky whitespace cases still
not | |
1599 // advance the range's start position (4509328). Break to avoid infinite
loop. | |
1600 VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTR
EAM); | |
1601 if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) | |
1602 break; | |
1603 | |
1604 // Only treat the result as a match if it is visible | |
1605 if (editor()->insideVisibleArea(resultRange.get())) { | |
1606 ++matchCount; | |
1607 document()->addMarker(resultRange.get(), DocumentMarker::TextMatch); | |
1608 } | |
1609 | |
1610 // Stop looking if we hit the specified limit. A limit of 0 means no lim
it. | |
1611 if (limit > 0 && matchCount >= limit) | |
1612 break; | |
1613 | |
1614 setStart(searchRange.get(), newStart); | |
1615 Node* shadowTreeRoot = searchRange->shadowTreeRootNode(); | |
1616 if (searchRange->collapsed(exception) && shadowTreeRoot) | |
1617 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount()
, exception); | |
1618 } while (true); | |
1619 | |
1620 // Do a "fake" paint in order to execute the code that computes the rendered
rect for | |
1621 // each text match. | |
1622 Document* doc = document(); | |
1623 if (doc && d->m_view && contentRenderer()) { | |
1624 doc->updateLayout(); // Ensure layout is up to date. | |
1625 IntRect visibleRect(enclosingIntRect(d->m_view->visibleContentRect())); | |
1626 if (!visibleRect.isEmpty()) { | |
1627 GraphicsContext context((PlatformGraphicsContext*)0); | |
1628 context.setPaintingDisabled(true); | |
1629 paint(&context, visibleRect); | |
1630 } | |
1631 } | |
1632 | |
1633 return matchCount; | |
1634 } | |
1635 | |
1636 bool Frame::markedTextMatchesAreHighlighted() const | |
1637 { | |
1638 return d->m_highlightTextMatches; | |
1639 } | |
1640 | |
1641 void Frame::setMarkedTextMatchesAreHighlighted(bool flag) | |
1642 { | |
1643 if (flag == d->m_highlightTextMatches || !document()) | |
1644 return; | |
1645 | |
1646 d->m_highlightTextMatches = flag; | |
1647 document()->repaintMarkers(DocumentMarker::TextMatch); | |
1648 } | |
1649 | |
1650 FrameTree* Frame::tree() const | |
1651 { | |
1652 return &d->m_treeNode; | |
1653 } | |
1654 | |
1655 DOMWindow* Frame::domWindow() const | |
1656 { | |
1657 if (!d->m_domWindow) | |
1658 d->m_domWindow = DOMWindow::create(const_cast<Frame*>(this)); | |
1659 | |
1660 return d->m_domWindow.get(); | |
1661 } | |
1662 | |
1663 void Frame::clearFormerDOMWindow(DOMWindow* window) | |
1664 { | |
1665 d->m_liveFormerWindows.remove(window); | |
1666 } | |
1667 | |
1668 Page* Frame::page() const | |
1669 { | |
1670 return d->m_page; | |
1671 } | |
1672 | |
1673 EventHandler* Frame::eventHandler() const | |
1674 { | |
1675 return &d->m_eventHandler; | |
1676 } | |
1677 | |
1678 void Frame::pageDestroyed() | |
1679 { | |
1680 if (Frame* parent = tree()->parent()) | |
1681 parent->loader()->checkLoadComplete(); | |
1682 | |
1683 // FIXME: It's unclear as to why this is called more than once, but it is, | |
1684 // so page() could be NULL. | |
1685 if (page() && page()->focusController()->focusedFrame() == this) | |
1686 page()->focusController()->setFocusedFrame(0); | |
1687 | |
1688 // This will stop any JS timers | |
1689 script()->disconnectFrame(); | |
1690 | |
1691 script()->clearScriptObjects(); | |
1692 | |
1693 d->m_page = 0; | |
1694 } | |
1695 | |
1696 void Frame::disconnectOwnerElement() | |
1697 { | |
1698 if (d->m_ownerElement) { | |
1699 if (Document* doc = document()) | |
1700 doc->clearAXObjectCache(); | |
1701 d->m_ownerElement->m_contentFrame = 0; | |
1702 if (d->m_page) | |
1703 d->m_page->decrementFrameCount(); | |
1704 } | |
1705 d->m_ownerElement = 0; | |
1706 } | |
1707 | |
1708 String Frame::documentTypeString() const | |
1709 { | |
1710 if (Document* doc = document()) { | |
1711 if (DocumentType* doctype = doc->doctype()) | |
1712 return createMarkup(doctype); | |
1713 } | |
1714 | |
1715 return String(); | |
1716 } | |
1717 | |
1718 bool Frame::prohibitsScrolling() const | |
1719 { | |
1720 return d->m_prohibitsScrolling; | |
1721 } | |
1722 | |
1723 void Frame::setProhibitsScrolling(bool prohibit) | |
1724 { | |
1725 d->m_prohibitsScrolling = prohibit; | |
1726 } | |
1727 | |
1728 void Frame::focusWindow() | |
1729 { | |
1730 if (!page()) | |
1731 return; | |
1732 | |
1733 // If we're a top level window, bring the window to the front. | |
1734 if (!tree()->parent()) | |
1735 page()->chrome()->focus(); | |
1736 | |
1737 eventHandler()->focusDocumentView(); | |
1738 } | |
1739 | |
1740 void Frame::unfocusWindow() | |
1741 { | |
1742 if (!page()) | |
1743 return; | |
1744 | |
1745 // If we're a top level window, deactivate the window. | |
1746 if (!tree()->parent()) | |
1747 page()->chrome()->unfocus(); | |
1748 } | |
1749 | |
1750 bool Frame::shouldClose() | |
1751 { | |
1752 Chrome* chrome = page() ? page()->chrome() : 0; | |
1753 if (!chrome || !chrome->canRunBeforeUnloadConfirmPanel()) | |
1754 return true; | |
1755 | |
1756 RefPtr<Document> doc = document(); | |
1757 if (!doc) | |
1758 return true; | |
1759 HTMLElement* body = doc->body(); | |
1760 if (!body) | |
1761 return true; | |
1762 | |
1763 loader()->setFiringUnloadEvents(true); | |
1764 | |
1765 RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create(); | |
1766 beforeUnloadEvent->setTarget(doc); | |
1767 doc->handleWindowEvent(beforeUnloadEvent.get(), false); | |
1768 | |
1769 if (!beforeUnloadEvent->defaultPrevented() && doc) | |
1770 doc->defaultEventHandler(beforeUnloadEvent.get()); | |
1771 | |
1772 loader()->setFiringUnloadEvents(false); | |
1773 | |
1774 if (beforeUnloadEvent->result().isNull()) | |
1775 return true; | |
1776 | |
1777 String text = beforeUnloadEvent->result(); | |
1778 text.replace('\\', backslashAsCurrencySymbol()); | |
1779 | |
1780 return chrome->runBeforeUnloadConfirmPanel(text, this); | |
1781 } | |
1782 | |
1783 void Frame::scheduleClose() | |
1784 { | |
1785 if (!shouldClose()) | |
1786 return; | |
1787 | |
1788 Chrome* chrome = page() ? page()->chrome() : 0; | |
1789 if (chrome) | |
1790 chrome->closeWindowSoon(); | |
1791 } | |
1792 | |
1793 void Frame::respondToChangedSelection(const Selection& oldSelection, bool closeT
yping) | |
1794 { | |
1795 if (document()) { | |
1796 bool isContinuousSpellCheckingEnabled = editor()->isContinuousSpellCheck
ingEnabled(); | |
1797 bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabl
ed && editor()->isGrammarCheckingEnabled(); | |
1798 if (isContinuousSpellCheckingEnabled) { | |
1799 Selection newAdjacentWords; | |
1800 Selection newSelectedSentence; | |
1801 if (selection()->selection().isContentEditable()) { | |
1802 VisiblePosition newStart(selection()->selection().visibleStart()
); | |
1803 newAdjacentWords = Selection(startOfWord(newStart, LeftWordIfOnB
oundary), endOfWord(newStart, RightWordIfOnBoundary)); | |
1804 if (isContinuousGrammarCheckingEnabled) | |
1805 newSelectedSentence = Selection(startOfSentence(newStart), e
ndOfSentence(newStart)); | |
1806 } | |
1807 | |
1808 // When typing we check spelling elsewhere, so don't redo it here. | |
1809 // If this is a change in selection resulting from a delete operatio
n, | |
1810 // oldSelection may no longer be in the document. | |
1811 if (closeTyping && oldSelection.isContentEditable() && oldSelection.
start().node() && oldSelection.start().node()->inDocument()) { | |
1812 VisiblePosition oldStart(oldSelection.visibleStart()); | |
1813 Selection oldAdjacentWords = Selection(startOfWord(oldStart, Lef
tWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); | |
1814 if (oldAdjacentWords != newAdjacentWords) { | |
1815 editor()->markMisspellings(oldAdjacentWords); | |
1816 if (isContinuousGrammarCheckingEnabled) { | |
1817 Selection oldSelectedSentence = Selection(startOfSentenc
e(oldStart), endOfSentence(oldStart)); | |
1818 if (oldSelectedSentence != newSelectedSentence) | |
1819 editor()->markBadGrammar(oldSelectedSentence); | |
1820 } | |
1821 } | |
1822 } | |
1823 | |
1824 // This only erases markers that are in the first unit (word or sent
ence) of the selection. | |
1825 // Perhaps peculiar, but it matches AppKit. | |
1826 if (RefPtr<Range> wordRange = newAdjacentWords.toRange()) | |
1827 document()->removeMarkers(wordRange.get(), DocumentMarker::Spell
ing); | |
1828 if (RefPtr<Range> sentenceRange = newSelectedSentence.toRange()) | |
1829 document()->removeMarkers(sentenceRange.get(), DocumentMarker::G
rammar); | |
1830 } | |
1831 | |
1832 // When continuous spell checking is off, existing markers disappear aft
er the selection changes. | |
1833 if (!isContinuousSpellCheckingEnabled) | |
1834 document()->removeMarkers(DocumentMarker::Spelling); | |
1835 if (!isContinuousGrammarCheckingEnabled) | |
1836 document()->removeMarkers(DocumentMarker::Grammar); | |
1837 } | |
1838 | |
1839 editor()->respondToChangedSelection(oldSelection); | |
1840 } | |
1841 | |
1842 VisiblePosition Frame::visiblePositionForPoint(const IntPoint& framePoint) | |
1843 { | |
1844 HitTestResult result = eventHandler()->hitTestResultAtPoint(framePoint, true
); | |
1845 Node* node = result.innerNode(); | |
1846 if (!node) | |
1847 return VisiblePosition(); | |
1848 RenderObject* renderer = node->renderer(); | |
1849 if (!renderer) | |
1850 return VisiblePosition(); | |
1851 VisiblePosition visiblePos = renderer->positionForCoordinates(result.localPo
int().x(), result.localPoint().y()); | |
1852 if (visiblePos.isNull()) | |
1853 visiblePos = VisiblePosition(Position(node, 0)); | |
1854 return visiblePos; | |
1855 } | |
1856 | |
1857 Document* Frame::documentAtPoint(const IntPoint& point) | |
1858 { | |
1859 if (!view()) | |
1860 return 0; | |
1861 | |
1862 IntPoint pt = view()->windowToContents(point); | |
1863 HitTestResult result = HitTestResult(pt); | |
1864 | |
1865 if (contentRenderer()) | |
1866 result = eventHandler()->hitTestResultAtPoint(pt, false); | |
1867 return result.innerNode() ? result.innerNode()->document() : 0; | |
1868 } | |
1869 | |
1870 FramePrivate::FramePrivate(Page* page, Frame* parent, Frame* thisFrame, HTMLFram
eOwnerElement* ownerElement, | |
1871 FrameLoaderClient* frameLoaderClient) | |
1872 : m_page(page) | |
1873 , m_treeNode(thisFrame, parent) | |
1874 , m_loader(thisFrame, frameLoaderClient) | |
1875 , m_ownerElement(ownerElement) | |
1876 , m_script(thisFrame) | |
1877 , m_zoomFactor(parent ? parent->d->m_zoomFactor : 1.0f) | |
1878 , m_selectionGranularity(CharacterGranularity) | |
1879 , m_selectionController(thisFrame) | |
1880 , m_caretBlinkTimer(thisFrame, &Frame::caretBlinkTimerFired) | |
1881 , m_editor(thisFrame) | |
1882 , m_eventHandler(thisFrame) | |
1883 , m_animationController(thisFrame) | |
1884 , m_lifeSupportTimer(thisFrame, &Frame::lifeSupportTimerFired) | |
1885 , m_paintRestriction(PaintRestrictionNone) | |
1886 , m_caretVisible(false) | |
1887 , m_caretPaint(true) | |
1888 , m_isPainting(false) | |
1889 , m_highlightTextMatches(false) | |
1890 , m_inViewSourceMode(false) | |
1891 , m_prohibitsScrolling(false) | |
1892 , m_needsReapplyStyles(false) | |
1893 , m_isDisconnected(false) | |
1894 , m_excludeFromTextSearch(false) | |
1895 #if FRAME_LOADS_USER_STYLESHEET | |
1896 , m_userStyleSheetLoader(0) | |
1897 #endif | |
1898 { | |
1899 } | |
1900 | |
1901 FramePrivate::~FramePrivate() | |
1902 { | |
1903 } | |
1904 | |
1905 } // namespace WebCore | |
OLD | NEW |