Skip to content

Commit 1363b0a

Browse files
robhogankelset
authored andcommitted
Use Content-Location header in bundle response as JS source URL (#37501)
Summary: Pull Request resolved: #37501 This is the iOS side of the fix for #36794. That issue aside for the moment, the high-level idea here is to conceptually separate the bundle *request URL*, which represents a request for the *latest* bundle, from the *source URL* passed to JS engines, which should represent the code actually being executed. In future, we'd like to use this to refer to a point-in-time snapshot of the bundle, so that stack traces more often refer to the code that was actually run, even if it's since been updated on disk (actually implementing this isn't planned at the moment, but it helps describe the distinction). Short term, this separation gives us a way to address the issue with JSC on iOS 16.4 by allowing Metro to provide the client with a [JSC-safe URL](react-native-community/discussions-and-proposals#646) to pass to the JS engine, even where the request URL isn't JSC-safe. We'll deliver that URL to the client on HTTP bundle requests via the [`Content-Location`](https://www.rfc-editor.org/rfc/rfc9110#name-content-location) header, which is a published standard for communicating a location for the content provided in a successful response (typically used to provide a direct URL to an asset after content negotiation, but I think it fits here too). For the long-term goal we should follow up with the same functionality on Android and out-of-tree platforms, but it's non-essential for anything other than iOS 16.4 at the moment. For the issue fix to work end-to-end we'll also need to update Metro, but the two pieces are decoupled and non-breaking so it doesn't matter which lands first. Changelog: [iOS][Changed] Prefer `Content-Location` header in bundle response as JS source URL Reviewed By: huntie Differential Revision: D45950661 fbshipit-source-id: 170fcd63a098f81bdcba55ebde0cf3569dceb88d
1 parent 90d0f93 commit 1363b0a

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

React/Base/RCTJavaScriptLoader.mm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,17 @@ static void attemptAsynchronousLoadOfBundleAtURL(
312312
return;
313313
}
314314

315-
RCTSource *source = RCTSourceCreate(scriptURL, data, data.length);
315+
// Prefer `Content-Location` as the canonical source URL, if given, or fall back to scriptURL.
316+
NSURL *sourceURL = scriptURL;
317+
NSString *contentLocationHeader = headers[@"Content-Location"];
318+
if (contentLocationHeader) {
319+
NSURL *contentLocationURL = [NSURL URLWithString:contentLocationHeader relativeToURL:scriptURL];
320+
if (contentLocationURL) {
321+
sourceURL = contentLocationURL;
322+
}
323+
}
324+
325+
RCTSource *source = RCTSourceCreate(sourceURL, data, data.length);
316326
parseHeaders(headers, source);
317327
onComplete(nil, source);
318328
}

React/CxxBridge/RCTCxxBridge.mm

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ - (void)start
473473
// Load the source asynchronously, then store it for later execution.
474474
dispatch_group_enter(prepareBridge);
475475
__block NSData *sourceCode;
476+
__block NSURL *sourceURL = self.bundleURL;
476477

477478
#if (RCT_DEV | RCT_ENABLE_LOADING_VIEW) && __has_include(<React/RCTDevLoadingViewProtocol.h>)
478479
{
@@ -488,6 +489,9 @@ - (void)start
488489
}
489490

490491
sourceCode = source.data;
492+
if (source.url) {
493+
sourceURL = source.url;
494+
}
491495
dispatch_group_leave(prepareBridge);
492496
}
493497
onProgress:^(RCTLoadingProgress *progressData) {
@@ -502,7 +506,7 @@ - (void)start
502506
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
503507
RCTCxxBridge *strongSelf = weakSelf;
504508
if (sourceCode && strongSelf.loading) {
505-
[strongSelf executeSourceCode:sourceCode sync:NO];
509+
[strongSelf executeSourceCode:sourceCode withSourceURL:sourceURL sync:NO];
506510
}
507511
});
508512
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
@@ -1048,7 +1052,7 @@ - (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module withModuleData
10481052
[_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData];
10491053
}
10501054

1051-
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
1055+
- (void)executeSourceCode:(NSData *)sourceCode withSourceURL:(NSURL *)url sync:(BOOL)sync
10521056
{
10531057
// This will get called from whatever thread was actually executing JS.
10541058
dispatch_block_t completion = ^{
@@ -1073,12 +1077,13 @@ - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
10731077
};
10741078

10751079
if (sync) {
1076-
[self executeApplicationScriptSync:sourceCode url:self.bundleURL];
1080+
[self executeApplicationScriptSync:sourceCode url:url];
10771081
completion();
10781082
} else {
1079-
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
1083+
[self enqueueApplicationScript:sourceCode url:url onComplete:completion];
10801084
}
10811085

1086+
// Use the original request URL here - HMRClient uses this to derive the /hot URL and entry point.
10821087
[self.devSettings setupHMRClientWithBundleURL:self.bundleURL];
10831088
}
10841089

0 commit comments

Comments
 (0)