tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NavigateEvent.cpp (17087B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/NavigateEvent.h"
      8 
      9 #include "mozilla/HoldDropJSObjects.h"
     10 #include "mozilla/PresShell.h"
     11 #include "mozilla/dom/AbortController.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/dom/ElementBinding.h"
     14 #include "mozilla/dom/NavigateEventBinding.h"
     15 #include "mozilla/dom/Navigation.h"
     16 #include "mozilla/dom/NavigationHistoryEntry.h"
     17 #include "mozilla/dom/SessionHistoryEntry.h"
     18 #include "nsDocShell.h"
     19 #include "nsFocusManager.h"
     20 #include "nsGlobalWindowInner.h"
     21 
     22 extern mozilla::LazyLogModule gNavigationAPILog;
     23 
     24 #define LOG_FMTI(format, ...) \
     25  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Info, format, ##__VA_ARGS__);
     26 
     27 #define LOG_FMT(format, ...) \
     28  MOZ_LOG_FMT(gNavigationAPILog, LogLevel::Debug, format, ##__VA_ARGS__);
     29 
     30 namespace mozilla::dom {
     31 
     32 NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
     33    NavigateEvent, Event,
     34    (mDestination, mSignal, mFormData, mSourceElement, mNavigationHandlerList,
     35     mAbortController, mNavigationPrecommitHandlerList),
     36    (mInfo))
     37 
     38 NS_IMPL_ADDREF_INHERITED(NavigateEvent, Event)
     39 NS_IMPL_RELEASE_INHERITED(NavigateEvent, Event)
     40 
     41 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NavigateEvent)
     42 NS_INTERFACE_MAP_END_INHERITING(Event)
     43 
     44 JSObject* NavigateEvent::WrapObjectInternal(JSContext* aCx,
     45                                            JS::Handle<JSObject*> aGivenProto) {
     46  return NavigateEvent_Binding::Wrap(aCx, this, aGivenProto);
     47 }
     48 
     49 /* static */
     50 already_AddRefed<NavigateEvent> NavigateEvent::Constructor(
     51    const GlobalObject& aGlobal, const nsAString& aType,
     52    const NavigateEventInit& aEventInitDict) {
     53  nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
     54      do_QueryInterface(aGlobal.GetAsSupports());
     55  return Constructor(eventTarget, aType, aEventInitDict);
     56 }
     57 
     58 /* static */
     59 already_AddRefed<NavigateEvent> NavigateEvent::Constructor(
     60    EventTarget* aEventTarget, const nsAString& aType,
     61    const NavigateEventInit& aEventInitDict) {
     62  RefPtr<NavigateEvent> event = new NavigateEvent(aEventTarget);
     63  bool trusted = event->Init(aEventTarget);
     64  event->InitEvent(
     65      aType, aEventInitDict.mBubbles ? CanBubble::eYes : CanBubble::eNo,
     66      aEventInitDict.mCancelable ? Cancelable::eYes : Cancelable::eNo,
     67      aEventInitDict.mComposed ? Composed::eYes : Composed::eNo);
     68  event->InitNavigateEvent(aEventInitDict);
     69  event->SetTrusted(trusted);
     70  return event.forget();
     71 }
     72 
     73 /* static */
     74 already_AddRefed<NavigateEvent> NavigateEvent::Constructor(
     75    EventTarget* aEventTarget, const nsAString& aType,
     76    const NavigateEventInit& aEventInitDict,
     77    nsIStructuredCloneContainer* aClassicHistoryAPIState,
     78    class AbortController* aAbortController) {
     79  RefPtr<NavigateEvent> event =
     80      Constructor(aEventTarget, aType, aEventInitDict);
     81 
     82  event->mAbortController = aAbortController;
     83  MOZ_DIAGNOSTIC_ASSERT(event->mSignal == aAbortController->Signal());
     84 
     85  event->mClassicHistoryAPIState = aClassicHistoryAPIState;
     86 
     87  return event.forget();
     88 }
     89 
     90 NavigationType NavigateEvent::NavigationType() const { return mNavigationType; }
     91 
     92 void NavigateEvent::SetNavigationType(enum NavigationType aNavigationType) {
     93  mNavigationType = aNavigationType;
     94 }
     95 
     96 already_AddRefed<NavigationDestination> NavigateEvent::Destination() const {
     97  return do_AddRef(mDestination);
     98 }
     99 
    100 bool NavigateEvent::CanIntercept() const { return mCanIntercept; }
    101 
    102 bool NavigateEvent::UserInitiated() const { return mUserInitiated; }
    103 
    104 bool NavigateEvent::HashChange() const { return mHashChange; }
    105 
    106 AbortSignal* NavigateEvent::Signal() const { return mSignal; }
    107 
    108 already_AddRefed<FormData> NavigateEvent::GetFormData() const {
    109  return do_AddRef(mFormData);
    110 }
    111 
    112 void NavigateEvent::GetDownloadRequest(nsAString& aDownloadRequest) const {
    113  aDownloadRequest = mDownloadRequest;
    114 }
    115 
    116 void NavigateEvent::GetInfo(JSContext* aCx,
    117                            JS::MutableHandle<JS::Value> aInfo) const {
    118  aInfo.set(mInfo);
    119 }
    120 
    121 bool NavigateEvent::HasUAVisualTransition() const {
    122  return mHasUAVisualTransition;
    123 }
    124 
    125 Element* NavigateEvent::GetSourceElement() const { return mSourceElement; }
    126 
    127 template <typename OptionEnum>
    128 static void MaybeReportWarningToConsole(Document* aDocument,
    129                                        const nsString& aOption,
    130                                        OptionEnum aPrevious, OptionEnum aNew) {
    131  if (!aDocument) {
    132    return;
    133  }
    134 
    135  nsTArray<nsString> params = {aOption,
    136                               NS_ConvertUTF8toUTF16(GetEnumString(aNew)),
    137                               NS_ConvertUTF8toUTF16(GetEnumString(aPrevious))};
    138  nsContentUtils::ReportToConsole(
    139      nsIScriptError::warningFlag, "DOM"_ns, aDocument,
    140      nsContentUtils::eDOM_PROPERTIES,
    141      "PreviousInterceptCallOptionOverriddenWarning", params);
    142 }
    143 
    144 // https://html.spec.whatwg.org/#dom-navigateevent-intercept
    145 void NavigateEvent::Intercept(const NavigationInterceptOptions& aOptions,
    146                              ErrorResult& aRv) {
    147  LOG_FMTI("Called NavigateEvent.intercept()");
    148 
    149  // Step 1
    150  if (PerformSharedChecks(aRv); aRv.Failed()) {
    151    return;
    152  }
    153 
    154  // Step 2
    155  if (!mCanIntercept) {
    156    aRv.ThrowSecurityError("Event's canIntercept was initialized to false");
    157    return;
    158  }
    159 
    160  // Step 3
    161  if (!IsBeingDispatched()) {
    162    aRv.ThrowInvalidStateError("Event has never been dispatched");
    163    return;
    164  }
    165 
    166  // Step 4
    167  if (aOptions.mPrecommitHandler.WasPassed()) {
    168    // Step 4.1
    169    if (!Cancelable()) {
    170      aRv.ThrowInvalidStateError("Event is not cancelable");
    171      return;
    172    }
    173 
    174    // Step 4.2
    175    mNavigationPrecommitHandlerList.AppendElement(
    176        aOptions.mPrecommitHandler.InternalValue().get());
    177  }
    178 
    179  // Step 5
    180  MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::None ||
    181                        mInterceptionState == InterceptionState::Intercepted);
    182 
    183  // Step 6
    184  mInterceptionState = InterceptionState::Intercepted;
    185 
    186  // Step 7
    187  if (aOptions.mHandler.WasPassed()) {
    188    mNavigationHandlerList.AppendElement(
    189        aOptions.mHandler.InternalValue().get());
    190  }
    191 
    192  // Step 8
    193  if (aOptions.mFocusReset.WasPassed()) {
    194    // Step 8.1
    195    if (mFocusResetBehavior &&
    196        *mFocusResetBehavior != aOptions.mFocusReset.Value()) {
    197      RefPtr<Document> document = GetAssociatedDocument();
    198      MaybeReportWarningToConsole(document, u"focusReset"_ns,
    199                                  *mFocusResetBehavior,
    200                                  aOptions.mFocusReset.Value());
    201    }
    202 
    203    // Step 8.2
    204    mFocusResetBehavior = Some(aOptions.mFocusReset.Value());
    205  }
    206 
    207  // Step 9
    208  if (aOptions.mScroll.WasPassed()) {
    209    // Step 9.1
    210    if (mScrollBehavior && *mScrollBehavior != aOptions.mScroll.Value()) {
    211      RefPtr<Document> document = GetAssociatedDocument();
    212      MaybeReportWarningToConsole(document, u"scroll"_ns, *mScrollBehavior,
    213                                  aOptions.mScroll.Value());
    214    }
    215 
    216    // Step 9.2
    217    mScrollBehavior.emplace(aOptions.mScroll.Value());
    218  }
    219 }
    220 
    221 // https://html.spec.whatwg.org/#dom-navigateevent-scroll
    222 void NavigateEvent::Scroll(ErrorResult& aRv) {
    223  LOG_FMTI("Called NavigateEvent.scroll()");
    224 
    225  // Step 1
    226  if (PerformSharedChecks(aRv); aRv.Failed()) {
    227    return;
    228  }
    229 
    230  // Step 2
    231  if (mInterceptionState != InterceptionState::Committed) {
    232    aRv.ThrowInvalidStateError("NavigateEvent was not committed");
    233    return;
    234  }
    235 
    236  // Step 3
    237  ProcessScrollBehavior();
    238 }
    239 
    240 NavigateEvent::NavigateEvent(EventTarget* aOwner)
    241    : Event(aOwner, nullptr, nullptr) {
    242  mozilla::HoldJSObjects(this);
    243 }
    244 
    245 NavigateEvent::~NavigateEvent() { DropJSObjects(this); }
    246 
    247 void NavigateEvent::InitNavigateEvent(const NavigateEventInit& aEventInitDict) {
    248  mNavigationType = aEventInitDict.mNavigationType;
    249  mDestination = aEventInitDict.mDestination;
    250  mCanIntercept = aEventInitDict.mCanIntercept;
    251  mUserInitiated = aEventInitDict.mUserInitiated;
    252  mHashChange = aEventInitDict.mHashChange;
    253  mSignal = aEventInitDict.mSignal;
    254  mFormData = aEventInitDict.mFormData;
    255  mDownloadRequest = aEventInitDict.mDownloadRequest;
    256  mInfo = aEventInitDict.mInfo;
    257  mHasUAVisualTransition = aEventInitDict.mHasUAVisualTransition;
    258  mSourceElement = aEventInitDict.mSourceElement;
    259  if (RefPtr document = GetAssociatedDocument()) {
    260    mLastScrollGeneration = document->LastScrollGeneration();
    261  }
    262 }
    263 
    264 void NavigateEvent::SetCanIntercept(bool aCanIntercept) {
    265  mCanIntercept = aCanIntercept;
    266 }
    267 
    268 enum NavigateEvent::InterceptionState NavigateEvent::InterceptionState() const {
    269  return mInterceptionState;
    270 }
    271 
    272 void NavigateEvent::SetInterceptionState(
    273    enum InterceptionState aInterceptionState) {
    274  mInterceptionState = aInterceptionState;
    275 }
    276 
    277 nsIStructuredCloneContainer* NavigateEvent::ClassicHistoryAPIState() const {
    278  return mClassicHistoryAPIState;
    279 }
    280 
    281 nsTArray<RefPtr<NavigationInterceptHandler>>&
    282 NavigateEvent::NavigationHandlerList() {
    283  return mNavigationHandlerList;
    284 }
    285 
    286 AbortController* NavigateEvent::AbortController() const {
    287  return mAbortController;
    288 }
    289 
    290 bool NavigateEvent::IsBeingDispatched() const {
    291  return mEvent->mFlags.mIsBeingDispatched;
    292 }
    293 
    294 // https://html.spec.whatwg.org/#navigateevent-finish
    295 void NavigateEvent::Finish(bool aDidFulfill) {
    296  // Step 1
    297  MOZ_DIAGNOSTIC_ASSERT(mInterceptionState != InterceptionState::Finished);
    298 
    299  // Step 2
    300  if (mInterceptionState == InterceptionState::Intercepted) {
    301    // Step 2.1
    302    MOZ_DIAGNOSTIC_ASSERT(!aDidFulfill);
    303 
    304    // Step 2.2
    305    MOZ_DIAGNOSTIC_ASSERT(!mNavigationPrecommitHandlerList.IsEmpty());
    306 
    307    // Step 2.3
    308    mInterceptionState = InterceptionState::Finished;
    309 
    310    // Step 2.4
    311    return;
    312  }
    313 
    314  // Step 3
    315  if (mInterceptionState == InterceptionState::None) {
    316    return;
    317  }
    318 
    319  // Step 4
    320  PotentiallyResetFocus();
    321 
    322  // Step 5
    323  if (aDidFulfill) {
    324    PotentiallyProcessScrollBehavior();
    325  }
    326 
    327  // Step 6
    328  mInterceptionState = InterceptionState::Finished;
    329 }
    330 
    331 // https://html.spec.whatwg.org/#navigateevent-perform-shared-checks
    332 void NavigateEvent::PerformSharedChecks(ErrorResult& aRv) {
    333  // Step 1
    334  if (RefPtr document = GetAssociatedDocument();
    335      !document || !document->IsFullyActive()) {
    336    aRv.ThrowInvalidStateError("Document isn't fully active");
    337    return;
    338  }
    339 
    340  // Step 2
    341  if (!IsTrusted()) {
    342    aRv.ThrowSecurityError("Event is untrusted");
    343    return;
    344  }
    345 
    346  // Step 3
    347  if (DefaultPrevented()) {
    348    aRv.ThrowInvalidStateError("Event was canceled");
    349  }
    350 }
    351 
    352 // https://html.spec.whatwg.org/#potentially-reset-the-focus
    353 void NavigateEvent::PotentiallyResetFocus() {
    354  // Step 1
    355  MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed ||
    356                        mInterceptionState == InterceptionState::Scrolled);
    357 
    358  // Step 2
    359  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject());
    360 
    361  // If we don't have a window here, there's not much we can do. This could
    362  // potentially happen in a chrome context, and in the end it's just better to
    363  // be sure and null check.
    364  if (NS_WARN_IF(!window)) {
    365    return;
    366  }
    367 
    368  Navigation* navigation = window->Navigation();
    369 
    370  // Step 3
    371  bool focusChanged = navigation->FocusedChangedDuringOngoingNavigation();
    372 
    373  // Step 4
    374  navigation->SetFocusedChangedDuringOngoingNavigation(false);
    375 
    376  // Step 5
    377  if (focusChanged) {
    378    return;
    379  }
    380 
    381  // Step 6
    382  if (mFocusResetBehavior &&
    383      *mFocusResetBehavior == NavigationFocusReset::Manual) {
    384    return;
    385  }
    386 
    387  // Step 7
    388  Document* document = window->GetExtantDoc();
    389 
    390  // If we don't have a document here, there's not much we can do.
    391  if (NS_WARN_IF(!document)) {
    392    return;
    393  }
    394 
    395  // Step 8
    396  RefPtr<Element> focusTarget = document->GetDocumentElement();
    397  if (focusTarget) {
    398    focusTarget =
    399        focusTarget->GetAutofocusDelegate(mozilla::IsFocusableFlags(0));
    400  }
    401 
    402  // Step 9
    403  if (!focusTarget) {
    404    focusTarget = document->GetBody();
    405  }
    406 
    407  // Step 10
    408  if (!focusTarget) {
    409    focusTarget = document->GetDocumentElement();
    410  }
    411 
    412  // Step 11, step 12
    413  FocusOptions options;
    414  options.mPreventScroll = true;
    415  focusTarget = nsFocusManager::GetTheFocusableArea(
    416      focusTarget, nsFocusManager::ProgrammaticFocusFlags(options));
    417 
    418  if (focusTarget) {
    419    LOG_FMT("Reset focus to {}", *focusTarget->AsNode());
    420    focusTarget->Focus(options, CallerType::NonSystem, IgnoredErrorResult());
    421  } else if (RefPtr<nsIFocusManager> focusManager =
    422                 nsFocusManager::GetFocusManager()) {
    423    if (nsPIDOMWindowOuter* window = document->GetWindow()) {
    424      // Now focus the document itself if focus is on an element within it.
    425      nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
    426      focusManager->GetFocusedWindow(getter_AddRefs(focusedWindow));
    427      if (SameCOMIdentity(window, focusedWindow)) {
    428        LOG_FMT("Reset focus to document viewport");
    429        focusManager->ClearFocus(focusedWindow);
    430      }
    431    }
    432  }
    433 }
    434 
    435 // https://html.spec.whatwg.org/#potentially-process-scroll-behavior
    436 void NavigateEvent::PotentiallyProcessScrollBehavior() {
    437  // Step 1
    438  MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed ||
    439                        mInterceptionState == InterceptionState::Scrolled);
    440 
    441  // Step 2
    442  if (mInterceptionState == InterceptionState::Scrolled) {
    443    return;
    444  }
    445 
    446  // Step 3
    447  if (mScrollBehavior && *mScrollBehavior == NavigationScrollBehavior::Manual) {
    448    return;
    449  }
    450 
    451  // Process 4
    452  ProcessScrollBehavior();
    453 }
    454 
    455 // Here we want to scroll to the beginning of the document, as described in
    456 // https://drafts.csswg.org/cssom-view/#scroll-to-the-beginning-of-the-document
    457 MOZ_CAN_RUN_SCRIPT
    458 static void ScrollToBeginningOfDocument(Document& aDocument) {
    459  RefPtr<PresShell> presShell = aDocument.GetPresShell();
    460  if (!presShell) {
    461    return;
    462  }
    463 
    464  RefPtr<Element> rootElement = aDocument.GetRootElement();
    465  ScrollAxis vertical(WhereToScroll::Start, WhenToScroll::Always);
    466  presShell->ScrollContentIntoView(rootElement, vertical, ScrollAxis(),
    467                                   ScrollFlags::TriggeredByScript);
    468 }
    469 
    470 // https://html.spec.whatwg.org/#restore-scroll-position-data
    471 static void RestoreScrollPositionData(Document* aDocument,
    472                                      const uint32_t& aLastScrollGeneration,
    473                                      SessionHistoryInfo* aHistoryEntry) {
    474  // 1. Let document be entry's document.
    475  // 2. If document's has been scrolled by the user is true, then the user agent
    476  // should return.
    477  if (!aDocument || aDocument->HasBeenScrolledSince(aLastScrollGeneration)) {
    478    return;
    479  }
    480 
    481  RefPtr<nsDocShell> docShell = nsDocShell::Cast(aDocument->GetDocShell());
    482  if (!docShell) {
    483    return;
    484  }
    485 
    486  // 3. The user agent should attempt to use entry's scroll position data to
    487  // restore the scroll positions of entry's document's restorable scrollable
    488  // regions. The user agent may continue to attempt to do so periodically,
    489  // until document's has been scrolled by the user becomes true.
    490  docShell->RestoreScrollPositionFromTargetSessionHistoryInfo(aHistoryEntry);
    491 }
    492 
    493 // https://html.spec.whatwg.org/#process-scroll-behavior
    494 void NavigateEvent::ProcessScrollBehavior() {
    495  // Step 1
    496  MOZ_DIAGNOSTIC_ASSERT(mInterceptionState == InterceptionState::Committed);
    497 
    498  // Step 2
    499  mInterceptionState = InterceptionState::Scrolled;
    500 
    501  // Step 3
    502  if (mNavigationType == NavigationType::Traverse ||
    503      mNavigationType == NavigationType::Reload) {
    504    RefPtr<Document> document = GetAssociatedDocument();
    505    // SHIP changes the active entry in
    506    // `nsDocShell::HandleSameDocumentNavigation`, which breaks with Navigation
    507    // API spec steps as it's too late, and at this point, the actual "active
    508    // session history entry" will become the target session history entry
    509    // provided here, which is why we're using this instead of
    510    // nsDocShell::mActiveEntry
    511    RestoreScrollPositionData(
    512        document, mLastScrollGeneration,
    513        mDestination->GetEntry()
    514            ? mDestination->GetEntry()->SessionHistoryInfo()
    515            : nullptr);
    516    return;
    517  }
    518 
    519  // Step 4.1
    520  RefPtr<Document> document = GetAssociatedDocument();
    521  // If there is no document there's not much to do.
    522  if (!document) {
    523    return;
    524  }
    525 
    526  // Step 4.2
    527  nsAutoCString ref;
    528  if (nsIURI* uri = document->GetDocumentURI();
    529      NS_SUCCEEDED(uri->GetRef(ref)) &&
    530      !nsContentUtils::GetTargetElement(document, NS_ConvertUTF8toUTF16(ref))) {
    531    ScrollToBeginningOfDocument(*document);
    532    return;
    533  }
    534 
    535  // Step 4.3
    536  // Here we need to update Document::mScrollToRef, since that is what
    537  // Document::ScrollToRef will be scrolling to.
    538  document->SetScrollToRef(document->GetDocumentURI());
    539  document->ScrollToRef();
    540 }
    541 
    542 Document* NavigateEvent::GetAssociatedDocument() const {
    543  if (nsCOMPtr<nsPIDOMWindowInner> globalWindow =
    544          do_QueryInterface(GetParentObject())) {
    545    return globalWindow->GetExtantDoc();
    546  }
    547  return nullptr;
    548 }
    549 
    550 }  // namespace mozilla::dom
    551 
    552 #undef LOG_FMTI
    553 #undef LOG_FMT