1 /* 2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 4 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 5 * Copyright (C) 2009 Adam Barth. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 #include "RedirectScheduler.h" 34 35 #include "BackForwardList.h" 36 #include "DocumentLoader.h" 37 #include "Event.h" 38 #include "FormState.h" 39 #include "Frame.h" 40 #include "FrameLoadRequest.h" 41 #include "FrameLoader.h" 42 #include "HistoryItem.h" 43 #include "HTMLFormElement.h" 44 #include "HTMLFrameOwnerElement.h" 45 #include "Page.h" 46 #include <wtf/CurrentTime.h> 47 48 namespace WebCore { 49 50 struct ScheduledRedirection : Noncopyable { 51 enum Type { redirection, locationChange, historyNavigation, formSubmission }; 52 53 const Type type; 54 const double delay; 55 const String url; 56 const String referrer; 57 const FrameLoadRequest frameRequest; 58 const RefPtr<Event> event; 59 const RefPtr<FormState> formState; 60 const int historySteps; 61 const bool lockHistory; 62 const bool lockBackForwardList; 63 const bool wasUserGesture; 64 const bool wasRefresh; 65 const bool wasDuringLoad; 66 bool toldClient; 67 68 ScheduledRedirection(double delay, const String& url, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh) 69 : type(redirection) 70 , delay(delay) 71 , url(url) 72 , historySteps(0) 73 , lockHistory(lockHistory) 74 , lockBackForwardList(lockBackForwardList) 75 , wasUserGesture(wasUserGesture) 76 , wasRefresh(refresh) 77 , wasDuringLoad(false) 78 , toldClient(false) 79 { 80 ASSERT(!url.isEmpty()); 81 } 82 83 ScheduledRedirection(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture, bool refresh, bool duringLoad) 84 : type(locationChange) 85 , delay(0) 86 , url(url) 87 , referrer(referrer) 88 , historySteps(0) 89 , lockHistory(lockHistory) 90 , lockBackForwardList(lockBackForwardList) 91 , wasUserGesture(wasUserGesture) 92 , wasRefresh(refresh) 93 , wasDuringLoad(duringLoad) 94 , toldClient(false) 95 { 96 ASSERT(!url.isEmpty()); 97 } 98 99 explicit ScheduledRedirection(int historyNavigationSteps) 100 : type(historyNavigation) 101 , delay(0) 102 , historySteps(historyNavigationSteps) 103 , lockHistory(false) 104 , lockBackForwardList(false) 105 , wasUserGesture(false) 106 , wasRefresh(false) 107 , wasDuringLoad(false) 108 , toldClient(false) 109 { 110 } 111 112 ScheduledRedirection(const FrameLoadRequest& frameRequest, 113 bool lockHistory, bool lockBackForwardList, PassRefPtr<Event> event, PassRefPtr<FormState> formState, 114 bool duringLoad) 115 : type(formSubmission) 116 , delay(0) 117 , frameRequest(frameRequest) 118 , event(event) 119 , formState(formState) 120 , historySteps(0) 121 , lockHistory(lockHistory) 122 , lockBackForwardList(lockBackForwardList) 123 , wasUserGesture(false) 124 , wasRefresh(false) 125 , wasDuringLoad(duringLoad) 126 , toldClient(false) 127 { 128 ASSERT(!frameRequest.isEmpty()); 129 ASSERT(this->formState); 130 } 131 }; 132 133 RedirectScheduler::RedirectScheduler(Frame* frame) 134 : m_frame(frame) 135 , m_timer(this, &RedirectScheduler::timerFired) 136 { 137 } 138 139 RedirectScheduler::~RedirectScheduler() 140 { 141 } 142 143 bool RedirectScheduler::redirectScheduledDuringLoad() 144 { 145 return m_scheduledRedirection && m_scheduledRedirection->wasDuringLoad; 146 } 147 148 void RedirectScheduler::clear() 149 { 150 m_timer.stop(); 151 m_scheduledRedirection.clear(); 152 } 153 154 void RedirectScheduler::scheduleRedirect(double delay, const String& url) 155 { 156 if (delay < 0 || delay > INT_MAX / 1000) 157 return; 158 159 if (!m_frame->page()) 160 return; 161 162 if (url.isEmpty()) 163 return; 164 165 // We want a new history item if the refresh timeout is > 1 second. 166 if (!m_scheduledRedirection || delay <= m_scheduledRedirection->delay) 167 schedule(new ScheduledRedirection(delay, url, true, delay <= 1, false, false)); 168 } 169 170 bool RedirectScheduler::mustLockBackForwardList(Frame* targetFrame) 171 { 172 // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item. 173 // The definition of "during load" is any time before all handlers for the load event have been run. 174 // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this. 175 176 for (Frame* ancestor = targetFrame->tree()->parent(); ancestor; ancestor = ancestor->tree()->parent()) { 177 Document* document = ancestor->document(); 178 if (!ancestor->loader()->isComplete() || (document && document->processingLoadEvent())) 179 return true; 180 } 181 return false; 182 } 183 184 void RedirectScheduler::scheduleLocationChange(const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool wasUserGesture) 185 { 186 if (!m_frame->page()) 187 return; 188 189 if (url.isEmpty()) 190 return; 191 192 lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame); 193 194 FrameLoader* loader = m_frame->loader(); 195 196 // If the URL we're going to navigate to is the same as the current one, except for the 197 // fragment part, we don't need to schedule the location change. 198 KURL parsedURL(ParsedURLString, url); 199 if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(loader->url(), parsedURL)) { 200 loader->changeLocation(loader->completeURL(url), referrer, lockHistory, lockBackForwardList, wasUserGesture); 201 return; 202 } 203 204 // Handle a location change of a page with no document as a special case. 205 // This may happen when a frame changes the location of another frame. 206 bool duringLoad = !loader->committedFirstRealDocumentLoad(); 207 208 schedule(new ScheduledRedirection(url, referrer, lockHistory, lockBackForwardList, wasUserGesture, false, duringLoad)); 209 } 210 211 void RedirectScheduler::scheduleFormSubmission(const FrameLoadRequest& frameRequest, 212 bool lockHistory, PassRefPtr<Event> event, PassRefPtr<FormState> formState) 213 { 214 ASSERT(m_frame->page()); 215 ASSERT(!frameRequest.isEmpty()); 216 217 // FIXME: Do we need special handling for form submissions where the URL is the same 218 // as the current one except for the fragment part? See scheduleLocationChange above. 219 220 // Handle a location change of a page with no document as a special case. 221 // This may happen when a frame changes the location of another frame. 222 bool duringLoad = !m_frame->loader()->committedFirstRealDocumentLoad(); 223 224 // If this is a child frame and the form submission was triggered by a script, lock the back/forward list 225 // to match IE and Opera. 226 // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this. 227 228 bool lockBackForwardList = mustLockBackForwardList(m_frame) || (formState->formSubmissionTrigger() == SubmittedByJavaScript && m_frame->tree()->parent()); 229 230 schedule(new ScheduledRedirection(frameRequest, lockHistory, lockBackForwardList, event, formState, duringLoad)); 231 } 232 233 void RedirectScheduler::scheduleRefresh(bool wasUserGesture) 234 { 235 if (!m_frame->page()) 236 return; 237 238 const KURL& url = m_frame->loader()->url(); 239 240 if (url.isEmpty()) 241 return; 242 243 schedule(new ScheduledRedirection(url.string(), m_frame->loader()->outgoingReferrer(), true, true, wasUserGesture, true, false)); 244 } 245 246 bool RedirectScheduler::locationChangePending() 247 { 248 if (!m_scheduledRedirection) 249 return false; 250 251 switch (m_scheduledRedirection->type) { 252 case ScheduledRedirection::redirection: 253 return false; 254 case ScheduledRedirection::historyNavigation: 255 case ScheduledRedirection::locationChange: 256 case ScheduledRedirection::formSubmission: 257 return true; 258 } 259 ASSERT_NOT_REACHED(); 260 return false; 261 } 262 263 void RedirectScheduler::scheduleHistoryNavigation(int steps) 264 { 265 if (!m_frame->page()) 266 return; 267 268 // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled 269 // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether. 270 HistoryItem* specifiedEntry = m_frame->page()->backForwardList()->itemAtIndex(steps); 271 if (!specifiedEntry) { 272 cancel(); 273 return; 274 } 275 276 #if !ENABLE(HISTORY_ALWAYS_ASYNC) 277 // If the specified entry and the current entry have the same document, this is either a state object traversal or a fragment 278 // traversal (or both) and should be performed synchronously. 279 HistoryItem* currentEntry = m_frame->loader()->history()->currentItem(); 280 if (currentEntry != specifiedEntry && currentEntry->documentSequenceNumber() == specifiedEntry->documentSequenceNumber()) { 281 m_frame->loader()->history()->goToItem(specifiedEntry, FrameLoadTypeIndexedBackForward); 282 return; 283 } 284 #endif 285 286 // In all other cases, schedule the history traversal to occur asynchronously. 287 schedule(new ScheduledRedirection(steps)); 288 } 289 290 void RedirectScheduler::timerFired(Timer<RedirectScheduler>*) 291 { 292 if (!m_frame->page()) 293 return; 294 295 if (m_frame->page()->defersLoading()) 296 return; 297 298 OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release()); 299 FrameLoader* loader = m_frame->loader(); 300 301 switch (redirection->type) { 302 case ScheduledRedirection::redirection: 303 case ScheduledRedirection::locationChange: 304 loader->changeLocation(KURL(ParsedURLString, redirection->url), redirection->referrer, 305 redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, redirection->wasRefresh); 306 return; 307 case ScheduledRedirection::historyNavigation: 308 if (redirection->historySteps == 0) { 309 // Special case for go(0) from a frame -> reload only the frame 310 loader->urlSelected(loader->url(), "", 0, redirection->lockHistory, redirection->lockBackForwardList, redirection->wasUserGesture, SendReferrer); 311 return; 312 } 313 // go(i!=0) from a frame navigates into the history of the frame only, 314 // in both IE and NS (but not in Mozilla). We can't easily do that. 315 m_frame->page()->goBackOrForward(redirection->historySteps); 316 return; 317 case ScheduledRedirection::formSubmission: 318 // The submitForm function will find a target frame before using the redirection timer. 319 // Now that the timer has fired, we need to repeat the security check which normally is done when 320 // selecting a target, in case conditions have changed. Other code paths avoid this by targeting 321 // without leaving a time window. If we fail the check just silently drop the form submission. 322 if (!redirection->formState->sourceFrame()->loader()->shouldAllowNavigation(m_frame)) 323 return; 324 loader->loadFrameRequest(redirection->frameRequest, redirection->lockHistory, redirection->lockBackForwardList, 325 redirection->event, redirection->formState, SendReferrer); 326 return; 327 } 328 329 ASSERT_NOT_REACHED(); 330 } 331 332 void RedirectScheduler::schedule(PassOwnPtr<ScheduledRedirection> redirection) 333 { 334 ASSERT(m_frame->page()); 335 FrameLoader* loader = m_frame->loader(); 336 337 // If a redirect was scheduled during a load, then stop the current load. 338 // Otherwise when the current load transitions from a provisional to a 339 // committed state, pending redirects may be cancelled. 340 if (redirection->wasDuringLoad) { 341 if (DocumentLoader* provisionalDocumentLoader = loader->provisionalDocumentLoader()) 342 provisionalDocumentLoader->stopLoading(); 343 loader->stopLoading(UnloadEventPolicyUnloadAndPageHide); 344 } 345 346 cancel(); 347 m_scheduledRedirection = redirection; 348 if (!loader->isComplete() && m_scheduledRedirection->type != ScheduledRedirection::redirection) 349 loader->completed(); 350 startTimer(); 351 } 352 353 void RedirectScheduler::startTimer() 354 { 355 if (!m_scheduledRedirection) 356 return; 357 358 ASSERT(m_frame->page()); 359 360 FrameLoader* loader = m_frame->loader(); 361 362 if (m_timer.isActive()) 363 return; 364 365 if (m_scheduledRedirection->type == ScheduledRedirection::redirection && !loader->allAncestorsAreComplete()) 366 return; 367 368 m_timer.startOneShot(m_scheduledRedirection->delay); 369 370 switch (m_scheduledRedirection->type) { 371 case ScheduledRedirection::locationChange: 372 case ScheduledRedirection::redirection: 373 if (m_scheduledRedirection->toldClient) 374 return; 375 m_scheduledRedirection->toldClient = true; 376 loader->clientRedirected(KURL(ParsedURLString, m_scheduledRedirection->url), 377 m_scheduledRedirection->delay, 378 currentTime() + m_timer.nextFireInterval(), 379 m_scheduledRedirection->lockBackForwardList); 380 return; 381 case ScheduledRedirection::formSubmission: 382 // FIXME: It would make sense to report form submissions as client redirects too. 383 // But we didn't do that in the past when form submission used a separate delay 384 // mechanism, so doing it will be a behavior change. 385 return; 386 case ScheduledRedirection::historyNavigation: 387 // Don't report history navigations. 388 return; 389 } 390 ASSERT_NOT_REACHED(); 391 } 392 393 void RedirectScheduler::cancel(bool newLoadInProgress) 394 { 395 m_timer.stop(); 396 397 OwnPtr<ScheduledRedirection> redirection(m_scheduledRedirection.release()); 398 if (redirection && redirection->toldClient) 399 m_frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress); 400 } 401 402 } // namespace WebCore 403 404