├── .npmrc ├── .gitattributes ├── package.json ├── .gitignore ├── LICENSE ├── README.md ├── spec.emu └── index.html /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "dynamic-import-host-adjustment", 4 | "description": "A repository template for ECMAScript proposals.", 5 | "scripts": { 6 | "build": "ecmarkup spec.emu index.html", 7 | "precommit": "npm run build" 8 | }, 9 | "homepage": "https://github.com/tc39/dynamic-import-host-adjustment#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/tc39/dynamic-import-host-adjustment.git" 13 | }, 14 | "license": "MIT", 15 | "devDependencies": { 16 | "ecmarkup": "^3.11.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | 44 | # Emacs droppings 45 | *~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Import Host Adjustment 2 | 3 | | | | 4 | | ------------- | ------------- | 5 | | **Stage** | [2](https://tc39.es/process-document/) | 6 | | **Spec** | [source](https://github.com/tc39/dynamic-import-host-adjustment/blob/master/spec.emu), [output](https://tc39.es/dynamic-import-host-adjustment/) | 7 | | **Tests** | [TODO](#testing) | 8 | | **Champion** | @mikesamuel | 9 | | **Reviewers** | @bakkot, @erights, @bmeck | 10 | 11 | [Trusted Types][] guards sensitive APIs; it double checks that values 12 | have been trusted by policy code before performing operations that 13 | cannot be undone. 14 | 15 | The dynamic import operator, `import(...)`, loads code and initializes modules. 16 | Loading code from an untrustworthy source is an operation that cannot be undone. 17 | 18 | This adjusts the host callout which enables dynamic loading. With it: 19 | 20 | 1. The host receives the original specifier (before it is stringified) so can use runtime type 21 | information to decide whether to allow code loading to proceed. 22 | 1. The host callout can control stringification and convey the result to FinishDynamicImport 23 | to avoid repeated stringification, and to integrate with [default policies][default policy]. 24 | 25 | ## Testing 26 | 27 | Tests, to be written, will be implemented as 28 | [web-platform-tests](https://github.com/web-platform-tests/wpt) and 29 | will focus on the following properties: 30 | 31 | 1. Polymorphic objects stringified once. Something like 32 | ```js 33 | importScripts("/resources/testharness.js"); 34 | 35 | test( 36 | () => { 37 | let stringifyCount = 0; 38 | import({ 39 | toString() { 40 | let specifier = `data:text/javascript,export default ${ stringifyCount }`; 41 | ++stringifyCount; 42 | return specifier; 43 | } 44 | }) 45 | .then( 46 | (defaultExport) => { 47 | assert_equals(stringifyCount, 1); 48 | assert_equals(defaultExport, 0); 49 | }, 50 | (err) => { 51 | assert_equals(err, null); 52 | }) 53 | .finally(done); 54 | }, 55 | 'DynamicImportStringifiesSpecifierOnce'); 56 | ``` 57 | 1. The above, with coverage for both *null* and non-*null* referencing modules. 58 | 1. Tests specific to trusted-types host implementation. 59 | 60 | See also [webappsec-csp #243](https://github.com/w3c/webappsec-csp/issues/243) 61 | on `import('data:...')` as CSP bypass. 62 | 63 | [Trusted Types]: https://wicg.github.io/trusted-types/dist/spec/ 64 | [default policy]: https://wicg.github.io/trusted-types/dist/spec/#default-policy-hdr 65 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
  7 | title: Dynamic Import Host Adjustment
  8 | stage: 0
  9 | contributors: Mike Samuel
 10 | 
11 | 12 |

This proposal allows the host to inspect and adjust the argument, 13 | _x_, in `import(x)` so that Trusted Types can use runtime-type 14 | information to decide whether to allow the import to proceed.

15 | 16 |

For more detail see the 17 | explainer.

18 | 19 |

This proposal lays out two options:

20 | 21 | 25 | 26 |

Option 1: Define a new host callout

27 | 28 | 29 | 30 |

First, this proposal adjusts `import(...)` to stringify its argument 31 | via a host callout.

32 |
33 | 34 |

Import Calls

35 | 36 | 37 |

Runtime Semantics: Evaluation

38 | 39 | ImportCall : `import` `(` AssignmentExpression `)` 40 | 41 | 1. Let _referencingScriptOrModule_ be ! GetActiveScriptOrModule(). 42 | 1. Let _argRef_ be the result of evaluating |AssignmentExpression|. 43 | 1. Let _specifier_ be ? GetValue(_argRef_). 44 | 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). 45 | 1. Let _specifierString_ be ToString HostDynamicValueToSpecifier(_referencingScriptOrModule_, _specifier_). 46 | 1. IfAbruptRejectPromise(_specifierString_, _promiseCapability_). 47 | 1. Assert Type(_specifierString_) is String. 48 | 1. Perform ! HostImportModuleDynamically(_referencingScriptOrModule_, _specifierString_, _promiseCapability_). 49 | 1. Return _promiseCapability_.[[Promise]]. 50 | 51 |
52 | 53 | 54 |

Second, this proposal defines the new host callout.

55 |
56 | 57 | 58 |

Runtime Semantics: HostDynamicValueToSpecifier ( _referencingScriptOrModule_, _specifier_ )

59 |

HostDynamicValueToSpecifier is an implementation-defined 60 | abstract operation that converts a runtime value representing a 61 | module specifier, _specifier_, to a string that corresponds to a 62 | |ModuleSpecifier| String. The host also receives the Script 63 | Record or Module Record, _referencingScriptOrModule_. 64 | _referencingScriptOrModule_ may be *null*, if the 65 | resolution is being performed in the context of 66 | an `import()` 67 | expression, and there is no active script or module at that 68 | time.

69 | 70 |

The default implementation performs the following steps:

71 | 72 | 1. Return ? ToString(_specifier_). 73 | 74 | 75 |

The implementation of HostDynamicValueToSpecifier must 76 | conform to the following requirements:

77 | 83 |
84 |
85 | 86 |
87 | 88 |

Option 2: Reuse HostImportModuleDynamically

89 | 90 | 91 | 92 | 93 |

Like the first option, this proposal adjusts `import(...)` to not stringify 94 | its argument too early. 95 | This means that HostImportModuleDynamically could pass given a 96 | TrustedModuleSpecifier but not given a raw string.

97 |
98 | 99 |

Import Calls

100 | 101 | 102 |

Runtime Semantics: Evaluation

103 | 104 | ImportCall : `import` `(` AssignmentExpression `)` 105 | 106 | 1. Let _referencingScriptOrModule_ be ! GetActiveScriptOrModule(). 107 | 1. Let _argRef_ be the result of evaluating |AssignmentExpression|. 108 | 1. Let _specifier_ be ? GetValue(_argRef_). 109 | 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). 110 | 1. Let _specifierString_ be ToString(_specifier_). 111 | 1. IfAbruptRejectPromise(_specifierString_, _promiseCapability_). 112 | 1. Perform ! HostImportModuleDynamically(_referencingScriptOrModule_, _specifierString__specifier_, _promiseCapability_). 113 | 1. Return _promiseCapability_.[[Promise]]. 114 | 115 |
116 |
117 | 118 |
119 | 120 | 121 | 122 | Second, this option adjusts HostImportModuleDynamically to deal with non-string 123 | _specifier_s. It also threads the eventual string for of the specifier through to 124 | FinishDynamicImport. This allows us to maintain the apparent order of operations. 125 | 126 | 127 |

Runtime Semantics: HostImportModuleDynamically ( _referencingScriptOrModule_, _specifier_, _promiseCapability_ )

128 | 129 |

HostImportModuleDynamically is an implementation-defined abstract 130 | operation that performs any necessary setup work in order to make 131 | available the module corresponding to the |ModuleSpecifier| String, 132 | _specifier_, occurring within the context of the script or module 133 | represented by the Script Record or Module Record 134 | _referencingScriptOrModule_. (_referencingScriptOrModule_ may also 135 | be *null*, if there is no active script or module when 136 | the `import()` 137 | expression occurs.) It then performs FinishDynamicImport to finish 138 | the dynamic import process.

139 | 140 |

The implementation of HostImportModuleDynamically must conform to 141 | the following requirements:

142 | 143 | 257 | 258 |

The actual process performed is implementation-defined, but 259 | typically consists of performing whatever I/O operations are 260 | necessary to allow HostResolveImportedModule to synchronously 261 | retrieve the appropriate Module Record, and then calling its 262 | Evaluate concrete method. This might require performing similar 263 | normalization as HostResolveImportedModule does.

264 |
265 | 266 |
267 | 268 | 269 | 270 |

Finally, we adjust FinishDynamicImport to use the module specifier 271 | string computed by the host callout.

272 |
273 | 274 |

Runtime Semantics: FinishDynamicImport ( _referencingScriptOrModule_, _specifier_, _promiseCapability_, _completion_ )

275 | 276 |

FinishDynamicImport completes the process of a dynamic import 277 | originally started by an 278 | `import()` call, 279 | resolving or rejecting the promise returned by that call as 280 | appropriate according to _completion_. It is performed by host 281 | environments as part of HostImportModuleDynamically.

282 | 283 | 284 | 1. If _completion_ is an abrupt completion, then perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _completion_.[[Value]] »). 285 | 1. Else, 286 | 1. Assert: _completion_ is a normal completion and _completion_.[[Value]] is *undefined*. 287 | 1. Let _specifierString_ be ! _completion_. 288 | 1. Assert: Type(_specifierString_) is String. 289 | 1. Let _moduleRecord_ be ! HostResolveImportedModule(_referencingScriptOrModule_, _specifier__specifierString_). 290 | 1. Assert: Evaluate has already been invoked on _moduleRecord_ and successfully completed. 291 | 1. Let _namespace_ be GetModuleNamespace(_moduleRecord_). 292 | 1. If _namespace_ is an abrupt completion, perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _namespace_.[[Value]] »). 293 | 1. Else, perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _namespace_.[[Value]] »). 294 | 295 |
296 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dynamic Import Host Adjustment

Stage 0 Draft / August 23, 2019

Dynamic Import Host Adjustment

1809 | 1810 |

This proposal allows the host to inspect and adjust the argument, 1811 | x, in import(x) so that Trusted Types can use runtime-type 1812 | information to decide whether to allow the import to proceed.

1813 | 1814 |

For more detail see the 1815 | 1816 | explainer.

1817 | 1818 |

This proposal lays out two options:

1819 | 1820 | 1824 | 1825 |

Option 1: Define a new host callout

1826 | 1827 | 1828 | Editor's Note
1829 |

First, this proposal adjusts import(...) to stringify its argument 1830 | via a host callout.

1831 |
1832 | 1833 |

1Import Calls

1834 | 1835 | 1836 |

1.1Runtime Semantics: Evaluation

1837 | 1838 | 1839 | ImportCall:import(AssignmentExpression) 1840 | 1841 |
  1. Let referencingScriptOrModule be ! GetActiveScriptOrModule().
  2. Let argRef be the result of evaluating AssignmentExpression.
  3. Let specifier be ? GetValue(argRef).
  4. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  5. Let specifierString be ToString HostDynamicValueToSpecifier(referencingScriptOrModule, specifier).
  6. IfAbruptRejectPromise(specifierString, promiseCapability).
  7. Assert Type(specifierString) is String.
  8. Perform ! HostImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability).
  9. Return promiseCapability.[[Promise]]. 1842 |
1843 |
1844 | 1845 | Editor's Note
1846 |

Second, this proposal defines the new host callout.

1847 |
1848 | 1849 | 1850 |

1.2Runtime Semantics: HostDynamicValueToSpecifier ( referencingScriptOrModule, specifier )

1851 |

HostDynamicValueToSpecifier is an implementation-defined 1852 | abstract operation that converts a runtime value representing a 1853 | module specifier, specifier, to a string that corresponds to a 1854 | ModuleSpecifier String. The host also receives the Script 1855 | Record or Module Record, referencingScriptOrModule. 1856 | referencingScriptOrModule may be null, if the 1857 | resolution is being performed in the context of 1858 | an import() 1859 | expression, and there is no active script or module at that 1860 | time.

1861 | 1862 |

The default implementation performs the following steps:

1863 |
  1. Return ? ToString(specifier). 1864 |
1865 | 1866 |

The implementation of HostDynamicValueToSpecifier must 1867 | conform to the following requirements:

1868 |
    1869 |
  • When HostDynamicValueToSpecifier completes normally, its result must 1870 | be a string.
  • 1871 |
  • HostDynamicValueToSpecifier may not stringify specifier more 1872 | than once.
  • 1873 |
1874 |
1875 |
1876 | 1877 |
1878 | 1879 |

Option 2: Reuse HostImportModuleDynamically

1880 | 1881 | 1882 | 1883 | Editor's Note
1884 |

Like the first option, this proposal adjusts import(...) to not stringify 1885 | its argument too early. 1886 | This means that HostImportModuleDynamically could pass given a 1887 | TrustedModuleSpecifier but not given a raw string.

1888 |
1889 | 1890 |

2Import Calls

1891 | 1892 | 1893 |

2.1Runtime Semantics: Evaluation

1894 | 1895 | 1896 | ImportCall:import(AssignmentExpression) 1897 | 1898 |
  1. Let referencingScriptOrModule be ! GetActiveScriptOrModule().
  2. Let argRef be the result of evaluating AssignmentExpression.
  3. Let specifier be ? GetValue(argRef).
  4. Let promiseCapability be ! NewPromiseCapability(%Promise%).
  5. Let specifierString be ToString(specifier).
  6. IfAbruptRejectPromise(specifierString, promiseCapability).
  7. Perform ! HostImportModuleDynamically(referencingScriptOrModule, specifierStringspecifier, promiseCapability).
  8. Return promiseCapability.[[Promise]]. 1899 |
1900 |
1901 |
1902 | 1903 |
1904 | 1905 | 1906 | Editor's Note
1907 | Second, this option adjusts HostImportModuleDynamically to deal with non-string 1908 | specifiers. It also threads the eventual string for of the specifier through to 1909 | FinishDynamicImport. This allows us to maintain the apparent order of operations. 1910 | 1911 |
1912 | 1913 |

3Runtime Semantics: HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability )

1914 | 1915 |

HostImportModuleDynamically is an implementation-defined abstract 1916 | operation that performs any necessary setup work in order to make 1917 | available the module corresponding to the ModuleSpecifier String, 1918 | specifier, occurring within the context of the script or module 1919 | represented by the Script Record or Module Record 1920 | referencingScriptOrModule. (referencingScriptOrModule may also 1921 | be null, if there is no active script or module when 1922 | the import() 1923 | expression occurs.) It then performs FinishDynamicImport to finish 1924 | the dynamic import process.

1925 | 1926 |

The implementation of HostImportModuleDynamically must conform to 1927 | the following requirements:

1928 | 1929 | 2049 | 2050 |

The actual process performed is implementation-defined, but 2051 | typically consists of performing whatever I/O operations are 2052 | necessary to allow HostResolveImportedModule to synchronously 2053 | retrieve the appropriate Module Record, and then calling its 2054 | Evaluate concrete method. This might require performing similar 2055 | normalization as HostResolveImportedModule does.

2056 |
2057 | 2058 |
2059 | 2060 | 2061 | Editor's Note
2062 |

Finally, we adjust FinishDynamicImport to use the module specifier 2063 | string computed by the host callout.

2064 |
2065 | 2066 |

4Runtime Semantics: FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, completion )

2067 | 2068 |

FinishDynamicImport completes the process of a dynamic import 2069 | originally started by an 2070 | 2071 | import() call, 2072 | resolving or rejecting the promise returned by that call as 2073 | appropriate according to completion. It is performed by host 2074 | environments as part of HostImportModuleDynamically.

2075 | 2076 |
  1. If completion is an abrupt completion, then perform ! Call(promiseCapability.[[Reject]], undefined, « completion.[[Value]] »).
  2. Else,
    1. Assert: completion is a normal completion and completion.[[Value]] is undefined.
    2. Let specifierString be ! completion.
    3. Assert: Type(specifierString) is String.
    4. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifierspecifierString).
    5. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
    6. Let namespace be GetModuleNamespace(moduleRecord).
    7. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
    8. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »). 2077 |
2078 |
2079 |

ACopyright & Software License

2080 | 2081 |

Copyright Notice

2082 |

© 2019 Mike Samuel

2083 | 2084 |

Software License

2085 |

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

2086 | 2087 |

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

2088 | 2089 |
    2090 |
  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. 2091 |
  3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  4. 2092 |
  5. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
  6. 2093 |
2094 | 2095 |

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

2096 | 2097 |
2098 |
--------------------------------------------------------------------------------