context.js (4609B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import { 6 getThreadContext, 7 getSelectedFrame, 8 getCurrentThread, 9 hasSource, 10 hasSourceActor, 11 getCurrentlyFetchedTopFrame, 12 hasFrame, 13 } from "../selectors/index"; 14 15 // Context encapsulates the main parameters of the current redux state, which 16 // impact most other information tracked by the debugger. 17 // 18 // The main use of Context is to control when asynchronous operations are 19 // allowed to make changes to the program state. Such operations might be 20 // invalidated as the state changes from the time the operation was originally 21 // initiated. For example, operations on pause state might still continue even 22 // after the thread unpauses. 23 // 24 // The methods below can be used to compare an old context with the current one 25 // and see if the operation is now invalid and should be abandoned. Actions can 26 // also include a 'cx' Context property, which will be checked by the context 27 // middleware. If the action fails validateContextAction() then it will not be 28 // dispatched. 29 // 30 // Context can additionally be used as a shortcut to access the main properties 31 // of the pause state. 32 33 // A normal Context is invalidated if the target navigates. 34 35 // A ThreadContext is invalidated if the target navigates, or if the current 36 // thread changes, pauses, or resumes. 37 38 export class ContextError extends Error { 39 constructor(msg) { 40 // Use a prefix string to help `PromiseTestUtils.allowMatchingRejectionsGlobally` 41 // ignore all these exceptions as this is based on error strings. 42 super(`DebuggerContextError: ${msg}`); 43 } 44 } 45 46 export function validateNavigateContext(state, cx) { 47 const newcx = getThreadContext(state); 48 49 if (newcx.navigateCounter != cx.navigateCounter) { 50 throw new ContextError("Page has navigated"); 51 } 52 } 53 54 export function validateThreadContext(state, cx) { 55 const newcx = getThreadContext(state); 56 57 if (cx.thread != newcx.thread) { 58 throw new ContextError("Current thread has changed"); 59 } 60 61 if (cx.pauseCounter != newcx.pauseCounter) { 62 throw new ContextError("Current thread has paused or resumed"); 63 } 64 } 65 66 export function validateContext(state, cx) { 67 validateNavigateContext(state, cx); 68 69 if ("thread" in cx) { 70 validateThreadContext(state, cx); 71 } 72 } 73 74 export function validateSelectedFrame(state, selectedFrame) { 75 const newThread = getCurrentThread(state); 76 if (selectedFrame.thread != newThread) { 77 throw new ContextError("Selected thread has changed"); 78 } 79 80 const newSelectedFrame = getSelectedFrame(state); 81 // Compare frame's IDs as frame objects are cloned during mapping 82 if (selectedFrame.id != newSelectedFrame?.id) { 83 throw new ContextError("Selected frame changed"); 84 } 85 } 86 87 export function validateBreakpoint(state, breakpoint) { 88 // XHR breakpoint don't use any location and are always valid 89 if (!breakpoint.location) { 90 return; 91 } 92 93 if (!hasSource(state, breakpoint.location.source.id)) { 94 throw new ContextError( 95 `Breakpoint's location is obsolete (source '${breakpoint.location.source.id}' no longer exists)` 96 ); 97 } 98 if (!hasSource(state, breakpoint.generatedLocation.source.id)) { 99 throw new ContextError( 100 `Breakpoint's generated location is obsolete (source '${breakpoint.generatedLocation.source.id}' no longer exists)` 101 ); 102 } 103 } 104 105 export function validateSource(state, source) { 106 if (!hasSource(state, source.id)) { 107 throw new ContextError( 108 `Obsolete source (source '${source.id}' no longer exists)` 109 ); 110 } 111 } 112 113 export function validateSourceActor(state, sourceActor) { 114 if (!hasSourceActor(state, sourceActor.id)) { 115 throw new ContextError( 116 `Obsolete source actor (source '${sourceActor.id}' no longer exists)` 117 ); 118 } 119 } 120 121 export function validateThreadFrames(state, thread, frames) { 122 const newThread = getCurrentThread(state); 123 if (thread != newThread) { 124 throw new ContextError("Selected thread has changed"); 125 } 126 const newTopFrame = getCurrentlyFetchedTopFrame(state, newThread); 127 if (newTopFrame?.id != frames[0].id) { 128 throw new ContextError("Thread moved to another location"); 129 } 130 } 131 132 export function validateFrame(state, frame) { 133 if (!hasFrame(state, frame)) { 134 throw new ContextError( 135 `Obsolete frame (frame '${frame.id}' no longer exists)` 136 ); 137 } 138 } 139 140 export function isValidThreadContext(state, cx) { 141 const newcx = getThreadContext(state); 142 return cx.thread == newcx.thread && cx.pauseCounter == newcx.pauseCounter; 143 }