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