Skip to content

Commit

Permalink
Handle module resolution when there is no active script
Browse files Browse the repository at this point in the history
Closes #3295. This also fixes a related inaccurate assert in EnqueueJob,
putting in guards for a null active script there as well and adding
examples explaining the various scenarios.
  • Loading branch information
domenic authored Nov 22, 2018
1 parent 7663812 commit 3e48877
Showing 1 changed file with 129 additions and 36 deletions.
165 changes: 129 additions & 36 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -86477,6 +86477,10 @@ interface <dfn>ApplicationCache</dfn> : <span>EventTarget</span> {
<li><p>Return <var>record</var>.[[HostDefined]].</p></li>
</ol>

<p class="note">The <span>active script</span> concept is so far only used by the
<code>import()</code> feature, to determine the <span data-x="concept-script-base-url">base
URL</span> to use for resolving relative module specifiers.</p>

<hr>

<p>An <dfn data-export="">environment</dfn> is an object that identifies the settings of a
Expand Down Expand Up @@ -88278,14 +88282,54 @@ document.querySelector("button").addEventListener("click", bound);

<li><p>Let <var>active script</var> be the <span>active script</span>.</p></li>

<li><p>Assert: <var>active script</var> is not null, as jobs are only enqueued by the JavaScript
specification while a script is active.</p></li>
<li><p>Let <var>script execution context</var> be null.</p></li>

<li><p>Let <var>script execution context</var> be a new <span>JavaScript execution
context</span>, with its Function field set to null, its Realm field set to <var>active
script</var>'s <span>settings object</span>'s <span data-x="environment settings object's
Realm">Realm</span>, and its ScriptOrModule set to <var>active script</var>'s <span
data-x="concept-script-record">record</span>.</p></li>
<li>
<p>If <var>active script</var> is not null, set <var>script execution context</var> to a new
<span>JavaScript execution context</span>, with its Function field set to null, its Realm field
set to <var>active script</var>'s <span>settings object</span>'s <span data-x="environment
settings object's Realm">Realm</span>, and its ScriptOrModule set to <var>active script</var>'s
<span data-x="concept-script-record">record</span>.</p>

<p class="note">As seen below, this is used in order to propagate the current <span>active
script</span> forward to the time when the job is executed.</p>

<div class="example">
<p>A case where <var>active script</var> is non-null, and saving it in this way is useful, is
the following:</p>

<pre><code class="js" data-x="">Promise.resolve('import(`./example.mjs`)').then(eval);</code></pre>

<p>Without this step (and the steps below that use it), there would be no <span>active
script</span> when the <code>import()</code> expression is evaluated, since <code>eval()</code>
is a built-in function that does not originate from any particular <span
data-x="concept-script">script</span>.</p>

<p>With this step in place, the active script is propagated from the above code into the job,
allowing <code>import()</code> to use the original script's <span
data-x="concept-script-base-url">base URL</span> appropriately.</p>
</div>

<div class="example">
<p><var>active script</var> can be null if the user clicks on the following button, before any
script ever accesses the button's <code data-x="">onclick</code> property:</p>

<pre><code class="html" data-x="">&lt;button onclick="Promise.resolve('import(`./example.mjs`)').then(eval)">Click me&lt;/button></code></pre>

<p>In this case, the JavaScript function for the <span data-x="event handlers">event
handler</span> will be created by the <span data-x="getting the current value of the event
handler">get the current value of the event handler</span> algorithm as a direct result of user
action, with no script on the stack (i.e., no <span>active script</span>). Thus, when the
promise machinery calls <span>EnqueueJob</span>, there will be no <span>active script</span> to
pass along.</p>

<p>As a consequence, this means that when the <code>import()</code> expression is evaluated,
there will still be no <span>active script</span>. Fortunately that is handled by our
implementations of <span>HostResolveImportedModule</span> and
<span>HostImportModuleDynamically</span>, by falling back to using the <span>current settings
object</span>'s <span>API base URL</span>.</p>
</div>
</li>

<li>
<p><span>Queue a microtask</span>, on <var>job settings</var>'s <span>responsible event
Expand All @@ -88310,20 +88354,20 @@ document.querySelector("button").addEventListener("click", bound);
</li>

<li>
<p><span data-x="stack push">Push</span> <var>script execution context</var> onto the
<span>JavaScript execution context stack</span>.</p>
<p>If <var>script execution context</var> is not null, then <span data-x="stack
push">push</span> <var>script execution context</var> onto the <span>JavaScript execution
context stack</span>.</p>

<p class="note">This affects the <span>active script</span> while the job runs, in cases like
<code data-x="">Promise.resolve("...").then(eval)</code> where there would otherwise be no
active script since <code>eval()</code> is a built-in function that does not originate from
any particular <span data-x="concept-script">script</span>.</p>
<p class="note">As explained above, this affects the <span>active script</span> while the job
runs.</p>
</li>

<li><p>Let <var>result</var> be the result of performing the abstract operation specified by
<var>job</var>, using the elements of <var>arguments</var> as its arguments.</p></li>

<li><p><span data-x="stack pop">Pop</span> <var>script execution context</var> from the
<span>JavaScript execution context stack</span>.</p></li>
<li><p>If <var>script execution context</var> is not null, then <span data-x="stack
pop">pop</span> <var>script execution context</var> from the <span>JavaScript execution context
stack</span>.</p></li>

<li><p><span>Clean up after running a callback</span> with <var>incumbent
settings</var>.</p></li>
Expand Down Expand Up @@ -88470,20 +88514,48 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
implementation: <ref spec=JAVASCRIPT> <ref spec=JSIMPORT></p>

<ol>
<li><p>Let <var>referencing script</var> be
<var>referencingScriptOrModule</var>.[[HostDefined]].</p></li>
<li><p>Let <var>settings object</var> be the <span>current settings object</span>.</p></li>

<li><p>Let <var>moduleMap</var> be <var>referencing script</var>'s <span>settings object</span>'s
<span data-x="concept-settings-object-module-map">module map</span>.</p></li>
<li><p>Let <var>base URL</var> be <var>settings object</var>'s <span>API base
URL</span>.</p></li>

<li>
<p>If <var>referencingScriptOrModule</var> is not null, then:</p>

<ol>
<li><p>Let <var>referencing script</var> be
<var>referencingScriptOrModule</var>.[[HostDefined]].</p></li>

<li><p>Set <var>settings object</var> to <var>referencing script</var>'s <span>settings
object</span>.</p></li>

<li><p>Set <var>base URL</var> to <var>referencing script</var>'s <span
data-x="concept-script-base-url">base URL</span>.</p></li>
</ol>

<div class="example">
<p><var>referencingScriptOrModule</var> is not usually null. One case where it <em>can</em> be
null is if the user clicks the control in the following example:</p>

<pre><code class="html" data-x="">&lt;button onclick="import('./foo.mjs')">Click me&lt;/button></code></pre>

<p>In this case, at the time the <code>import()</code> expression runs,
<span>GetActiveScriptOrModule</span> will return null, which will be passed to this abstract
operation when <span data-x="js-HostResolveImportedModule">HostResolveImportedModule</span> is
called by <span>FinishDynamicImport</span>.</p>
</div>
</li>

<li><p>Let <var>moduleMap</var> be <var>settings object</var>'s <span
data-x="concept-settings-object-module-map">module map</span>.</p></li>

<li><p>Let <var>url</var> be the result of <span data-x="resolve a module specifier">resolving a
module specifier</span> given <var>referencing script</var>'s <span
data-x="concept-script-base-url">base URL</span> and <var>specifier</var>.</p></li>
module specifier</span> given <var>base URL</var> and <var>specifier</var>.</p></li>

<li><p>Assert: <var>url</var> is never failure, because <span data-x="resolve a module
specifier">resolving a module specifier</span> must have been <a
href="#validate-requested-module-specifiers">previously successful</a> with these
same two arguments.</p></li>
specifier">resolving a module specifier</span> must have been previously successful with these
same two arguments (either <a href="#validate-requested-module-specifiers">while creating the
corresponding module script</a>, or in <span>HostImportModuleDynamically</span>).</p></li>

<li><p>Let <var>resolved module script</var> be <var>moduleMap</var>[<var>url</var>]. (This entry
must <span data-x="map exists">exist</span> for us to have gotten to this point.)</p></li>
Expand All @@ -88506,12 +88578,38 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
User agents must use the following implementation: <ref spec=JSIMPORT></p>

<ol>
<li><p>Let <var>referencing script</var> be
<var>referencingScriptOrModule</var>.[[HostDefined]].</p></li>
<li><p>Let <var>settings object</var> be the <span>current settings object</span>.</p></li>

<li><p>Let <var>base URL</var> be <var>settings object</var>'s <span>API base
URL</span>.</p></li>

<li><p>Let <var>fetch options</var> be the <span>default classic script fetch
options</span>.</p></li>

<li>
<p>If <var>referencingScriptOrModule</var> is not null, then:</p>

<ol>
<li><p>Let <var>referencing script</var> be
<var>referencingScriptOrModule</var>.[[HostDefined]].</p>

<li><p>Set <var>settings object</var> to <var>referencing script</var>'s <span>settings
object</span>.</p></li>

<li><p>Set <var>base URL</var> to <var>referencing script</var>'s <span
data-x="concept-script-base-url">base URL</span>.</p></li>

<li><p>Set <var>fetch options</var> to the <span>descendant script fetch options</span> for
<var>referencing script</var>'s <span data-x="concept-script-script-fetch-options">fetch
options</span>.</p></li>
</ol>

<p class="note">As explained above for <span>HostResolveImportedModule</span>, in the common
case, <var>referencingScriptOrModule</var> is non-null.</p>
</li>

<li><p>Let <var>url</var> be the result of <span data-x="resolve a module specifier">resolving a
module specifier</span> given <var>referencing script</var>'s <span
data-x="concept-script-base-url">base URL</span> and <var>specifier</var>.</p></li>
module specifier</span> given <var>base URL</var> and <var>specifier</var>.</p></li>

<li>
<p>If <var>url</var> is failure, then:</p>
Expand All @@ -88527,14 +88625,9 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
</ol>
</li>

<li><p>Let <var>options</var> be the <span>descendant script fetch options</span> for
<var>referencing script</var>'s <span data-x="concept-script-script-fetch-options">fetch
options</span>.</p></li>

<li><p><span>Fetch a module script graph</span> given <var>url</var>, <var>referencing
script</var>'s <span>settings object</span>, "<code data-x="">script</code>", and
<var>options</var>. Wait until the algorithm asynchronously completes with
<var>result</var>.</p></li>
<li><p><span>Fetch a module script graph</span> given <var>url</var>, <var>settings object</var>,
"<code data-x="">script</code>", and <var>fetch options</var>. Wait until the algorithm
asynchronously completes with <var>result</var>.</p></li>

<li>
<p>If <var>result</var> is null, then:</p>
Expand Down

0 comments on commit 3e48877

Please sign in to comment.