1// Copyright © 2023 650 Industries. 2// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. 3 4import URL from 'url-parse'; 5 6class DOMException extends Error { 7 constructor(message: string, name: string) { 8 super(message); 9 this.name = name; 10 } 11} 12 13// The differences between the definitions of `Location` and `WorkerLocation` 14// are because of the `LegacyUnforgeable` attribute only specified upon 15// `Location`'s properties. See: 16// - https://html.spec.whatwg.org/multipage/history.html#the-location-interface 17// - https://heycam.github.io/webidl/#LegacyUnforgeable 18class Location { 19 constructor(href: string | null = null) { 20 const url = new URL( 21 // @ts-expect-error 22 href 23 ); 24 // @ts-expect-error 25 url.username = ''; 26 // @ts-expect-error 27 url.password = ''; 28 Object.defineProperties(this, { 29 hash: { 30 get() { 31 return url.hash; 32 }, 33 set() { 34 throw new DOMException(`Cannot set "location.hash".`, 'NotSupportedError'); 35 }, 36 enumerable: true, 37 }, 38 host: { 39 get() { 40 return url.host; 41 }, 42 set() { 43 throw new DOMException(`Cannot set "location.host".`, 'NotSupportedError'); 44 }, 45 enumerable: true, 46 }, 47 hostname: { 48 get() { 49 return url.hostname; 50 }, 51 set() { 52 throw new DOMException(`Cannot set "location.hostname".`, 'NotSupportedError'); 53 }, 54 enumerable: true, 55 }, 56 href: { 57 get() { 58 return url.href; 59 }, 60 set() { 61 throw new DOMException(`Cannot set "location.href".`, 'NotSupportedError'); 62 }, 63 enumerable: true, 64 }, 65 origin: { 66 get() { 67 return url.origin; 68 }, 69 enumerable: true, 70 }, 71 pathname: { 72 get() { 73 return url.pathname; 74 }, 75 set() { 76 throw new DOMException(`Cannot set "location.pathname".`, 'NotSupportedError'); 77 }, 78 enumerable: true, 79 }, 80 port: { 81 get() { 82 return url.port; 83 }, 84 set() { 85 throw new DOMException(`Cannot set "location.port".`, 'NotSupportedError'); 86 }, 87 enumerable: true, 88 }, 89 protocol: { 90 get() { 91 return url.protocol; 92 }, 93 set() { 94 throw new DOMException(`Cannot set "location.protocol".`, 'NotSupportedError'); 95 }, 96 enumerable: true, 97 }, 98 search: { 99 get() { 100 // @ts-expect-error 101 return url.search; 102 }, 103 set() { 104 throw new DOMException(`Cannot set "location.search".`, 'NotSupportedError'); 105 }, 106 enumerable: true, 107 }, 108 ancestorOrigins: { 109 get() { 110 return { 111 length: 0, 112 item: () => null, 113 contains: () => false, 114 }; 115 }, 116 enumerable: true, 117 }, 118 assign: { 119 value: function assign() { 120 throw new DOMException(`Cannot call "location.assign()".`, 'NotSupportedError'); 121 }, 122 enumerable: true, 123 }, 124 reload: { 125 value: function reload() { 126 if (process.env.NODE_ENV !== 'production') { 127 // NOTE: This does change how native fast refresh works. The upstream metro-runtime will check 128 // if `location.reload` exists before falling back on an implementation that is nearly identical to 129 // this. The main difference is that on iOS there is a "reason" message sent, but at the time of writing 130 // this, that message is unused (ref: `RCTTriggerReloadCommandNotification`). 131 const DevSettings = (require('react-native') as typeof import('react-native')) 132 .DevSettings; 133 return DevSettings.reload(); 134 } else { 135 throw new DOMException(`Cannot call "location.reload()".`, 'NotSupportedError'); 136 } 137 }, 138 enumerable: true, 139 }, 140 replace: { 141 value: function replace() { 142 throw new DOMException(`Cannot call "location.replace()".`, 'NotSupportedError'); 143 }, 144 enumerable: true, 145 }, 146 toString: { 147 value: function toString() { 148 return url.href; 149 }, 150 enumerable: true, 151 }, 152 [Symbol.for('Expo.privateCustomInspect')]: { 153 value(inspect: any) { 154 const object = { 155 hash: this.hash, 156 host: this.host, 157 hostname: this.hostname, 158 href: this.href, 159 origin: this.origin, 160 pathname: this.pathname, 161 port: this.port, 162 protocol: this.protocol, 163 search: this.search, 164 }; 165 return `${this.constructor.name} ${inspect(object)}`; 166 }, 167 }, 168 }); 169 } 170} 171 172Object.defineProperties(Location.prototype, { 173 [Symbol.toString()]: { 174 value: 'Location', 175 configurable: true, 176 }, 177}); 178 179let location: Location | undefined = undefined; 180 181export function setLocationHref(href: string) { 182 location = new Location(href); 183} 184 185export function install() { 186 Object.defineProperty(global, 'Location', { 187 value: Location, 188 configurable: true, 189 writable: true, 190 }); 191 192 Object.defineProperty(window, 'location', { 193 get() { 194 return location; 195 }, 196 set() { 197 throw new DOMException(`Cannot set "location".`, 'NotSupportedError'); 198 }, 199 enumerable: true, 200 }); 201} 202