Changeset 190272 in webkit


Ignore:
Timestamp:
Sep 27, 2015, 3:24:18 PM (9 years ago)
Author:
Yusuke Suzuki
Message:

[ES6] Implement ES6 Module loader hook stubs in WebCore
https://bugs.webkit.org/show_bug.cgi?id=149574

Reviewed by Ryosuke Niwa.

In this patch, we implement the loader hooks in the WebCore side.
Since the fetcher and actual evaluation code are separated from
this patch, they become empty.

Here, WebCore implements 3 loader hooks.

  1. resolve hook

Takes the module name and resolve it to the unique module key.
In WebCore, we use URL as a module key.
And for the inlined module tag (e.g. <script type="module">import ...</script>),
we use the ES6 symbol as a module key.
In WebCore, we take the module name like "./hello.js" and resolve it by using
the URL of the importer module.
This functionality is implemented in this patch.

  1. fetch hook

Fetches the resource specified by the module key. In WebCore, the module key is
URL. We use CachedResource loading system to load the resource of the modules.
The actual code of the fetch hook will be implemented in the subsequent patch.

  1. evaluate hook

This is additional hook to instrument the module's execution for the inspector.
The actual code of the evaluate hook will be implemented in the subsequent patch.

In addition to that, we added required JSC forward headers for the module loader
implementation.

  • CMakeLists.txt:
  • ForwardingHeaders/runtime/JSInternalPromise.h: Added.
  • ForwardingHeaders/runtime/JSInternalPromiseDeferred.h: Added.
  • ForwardingHeaders/runtime/JSModuleRecord.h: Added.
  • ForwardingHeaders/runtime/Symbol.h: Added.
  • WebCore.vcxproj/WebCore.vcxproj:
  • WebCore.vcxproj/WebCore.vcxproj.filters:
  • WebCore.xcodeproj/project.pbxproj:
  • bindings/js/JSBindingsAllInOne.cpp:
  • bindings/js/JSDOMWindowBase.cpp:

(WebCore::JSDOMWindowBase::moduleLoaderResolve):
(WebCore::JSDOMWindowBase::moduleLoaderFetch):
(WebCore::JSDOMWindowBase::moduleLoaderEvaluate):

  • bindings/js/JSDOMWindowBase.h:
  • bindings/js/JSModuleLoader.cpp: Added.

(WebCore::JSModuleLoader::JSModuleLoader):
(WebCore::JSModuleLoader::resolve):
(WebCore::JSModuleLoader::fetch):
(WebCore::JSModuleLoader::evaluate):

  • bindings/js/JSModuleLoader.h: Added.

(WebCore::JSModuleLoader::document):

  • dom/Document.cpp:

(WebCore::Document::Document):
(WebCore::Document::~Document):

  • dom/Document.h:

(WebCore::Document::moduleLoader):

Location:
trunk/Source/WebCore
Files:
6 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/CMakeLists.txt

    r190255 r190272  
    11781178    bindings/js/JSMessageEventCustom.cpp
    11791179    bindings/js/JSMessagePortCustom.cpp
     1180    bindings/js/JSModuleLoader.cpp
    11801181    bindings/js/JSMutationCallback.cpp
    11811182    bindings/js/JSMutationObserverCustom.cpp
  • trunk/Source/WebCore/ChangeLog

    r190271 r190272  
     12015-09-27  Yusuke Suzuki  <utatane.tea@gmail.com>
     2
     3        [ES6] Implement ES6 Module loader hook stubs in WebCore
     4        https://bugs.webkit.org/show_bug.cgi?id=149574
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        In this patch, we implement the loader hooks in the WebCore side.
     9        Since the fetcher and actual evaluation code are separated from
     10        this patch, they become empty.
     11
     12        Here, WebCore implements 3 loader hooks.
     13
     14        1. resolve hook
     15
     16            Takes the module name and resolve it to the unique module key.
     17            In WebCore, we use URL as a module key.
     18            And for the inlined module tag (e.g. <script type="module">import ...</script>),
     19            we use the ES6 symbol as a module key.
     20            In WebCore, we take the module name like "./hello.js" and resolve it by using
     21            the URL of the importer module.
     22            This functionality is implemented in this patch.
     23
     24        2. fetch hook
     25
     26            Fetches the resource specified by the module key. In WebCore, the module key is
     27            URL. We use CachedResource loading system to load the resource of the modules.
     28            The actual code of the fetch hook will be implemented in the subsequent patch.
     29
     30        3. evaluate hook
     31
     32            This is additional hook to instrument the module's execution for the inspector.
     33            The actual code of the evaluate hook will be implemented in the subsequent patch.
     34
     35        In addition to that, we added required JSC forward headers for the module loader
     36        implementation.
     37
     38        * CMakeLists.txt:
     39        * ForwardingHeaders/runtime/JSInternalPromise.h: Added.
     40        * ForwardingHeaders/runtime/JSInternalPromiseDeferred.h: Added.
     41        * ForwardingHeaders/runtime/JSModuleRecord.h: Added.
     42        * ForwardingHeaders/runtime/Symbol.h: Added.
     43        * WebCore.vcxproj/WebCore.vcxproj:
     44        * WebCore.vcxproj/WebCore.vcxproj.filters:
     45        * WebCore.xcodeproj/project.pbxproj:
     46        * bindings/js/JSBindingsAllInOne.cpp:
     47        * bindings/js/JSDOMWindowBase.cpp:
     48        (WebCore::JSDOMWindowBase::moduleLoaderResolve):
     49        (WebCore::JSDOMWindowBase::moduleLoaderFetch):
     50        (WebCore::JSDOMWindowBase::moduleLoaderEvaluate):
     51        * bindings/js/JSDOMWindowBase.h:
     52        * bindings/js/JSModuleLoader.cpp: Added.
     53        (WebCore::JSModuleLoader::JSModuleLoader):
     54        (WebCore::JSModuleLoader::resolve):
     55        (WebCore::JSModuleLoader::fetch):
     56        (WebCore::JSModuleLoader::evaluate):
     57        * bindings/js/JSModuleLoader.h: Added.
     58        (WebCore::JSModuleLoader::document):
     59        * dom/Document.cpp:
     60        (WebCore::Document::Document):
     61        (WebCore::Document::~Document):
     62        * dom/Document.h:
     63        (WebCore::Document::moduleLoader):
     64
    1652015-09-26  Hunseop Jeong  <hs85.jeong@samsung.com>
    266
  • trunk/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj

    r190239 r190272  
    1841518415    </ClCompile>
    1841618416    <ClCompile Include="..\bindings\js\JSMessagePortCustom.cpp">
     18417      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
     18418      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
     18419      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
     18420      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
     18421      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
     18422      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
     18423      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
     18424      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
     18425      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
     18426      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
     18427      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
     18428      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     18429    </ClCompile>
     18430    <ClCompile Include="..\bindings\js\JSModuleLoader.cpp">
    1841718431      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
    1841818432      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
     
    2247922493    <ClInclude Include="..\bindings\js\JSMainThreadExecState.h" />
    2248022494    <ClInclude Include="..\bindings\js\JSMessagePortCustom.h" />
     22495    <ClInclude Include="..\bindings\js\JSModuleLoader.h" />
    2248122496    <ClInclude Include="..\bindings\js\JSMutationCallback.h" />
    2248222497    <ClInclude Include="..\bindings\js\JSNodeCustom.h" />
     
    2272322738    <ClInclude Include="..\ForwardingHeaders\runtime\ArgList.h" />
    2272422739    <ClInclude Include="..\ForwardingHeaders\runtime\Identifier.h" />
     22740    <ClInclude Include="..\ForwardingHeaders\runtime\JSInternalPromise.h" />
     22741    <ClInclude Include="..\ForwardingHeaders\runtime\JSInternalPromiseDeferred.h" />
    2272522742    <ClInclude Include="..\ForwardingHeaders\runtime\JSLock.h" />
    2272622743    <ClInclude Include="..\ForwardingHeaders\runtime\JSObject.h" />
    2272722744    <ClInclude Include="..\ForwardingHeaders\runtime\JSString.h" />
     22745    <ClInclude Include="..\ForwardingHeaders\runtime\JSModuleRecord.h" />
    2272822746    <ClInclude Include="..\ForwardingHeaders\runtime\Lookup.h" />
    2272922747    <ClInclude Include="..\ForwardingHeaders\runtime\Operations.h" />
    2273022748    <ClInclude Include="..\ForwardingHeaders\runtime\Protect.h" />
    2273122749    <ClInclude Include="..\ForwardingHeaders\runtime\StringObject.h" />
     22750    <ClInclude Include="..\ForwardingHeaders\runtime\Symbol.h" />
    2273222751    <ClInclude Include="..\ForwardingHeaders\yarr\RegularExpression.h" />
    2273322752    <ClInclude Include="..\ForwardingHeaders\yarr\Yarr.h" />
  • trunk/Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters

    r190198 r190272  
    43814381      <Filter>bindings\js</Filter>
    43824382    </ClCompile>
     4383    <ClCompile Include="..\bindings\js\JSModuleLoader.cpp">
     4384      <Filter>bindings\js</Filter>
     4385    </ClCompile>
    43834386    <ClCompile Include="..\bindings\js\JSMutationCallback.cpp">
    43844387      <Filter>bindings\js</Filter>
     
    1147511478      <Filter>bindings\js</Filter>
    1147611479    </ClInclude>
     11480    <ClInclude Include="..\bindings\js\JSModuleLoader.h">
     11481      <Filter>bindings\js</Filter>
     11482    </ClInclude>
    1147711483    <ClInclude Include="..\bindings\js\JSMutationCallback.h">
    1147811484      <Filter>bindings\js</Filter>
     
    1165511661      <Filter>ForwardingHeaders\runtime</Filter>
    1165611662    </ClInclude>
     11663    <ClInclude Include="..\ForwardingHeaders\runtime\JSInternalPromise.h">
     11664      <Filter>ForwardingHeaders\runtime</Filter>
     11665    </ClInclude>
     11666    <ClInclude Include="..\ForwardingHeaders\runtime\JSInternalPromiseDeferred.h">
     11667      <Filter>ForwardingHeaders\runtime</Filter>
     11668    </ClInclude>
    1165711669    <ClInclude Include="..\ForwardingHeaders\runtime\JSLock.h">
    1165811670      <Filter>ForwardingHeaders\runtime</Filter>
     
    1166111673      <Filter>ForwardingHeaders\runtime</Filter>
    1166211674    </ClInclude>
     11675    <ClInclude Include="..\ForwardingHeaders\runtime\JSModuleRecord.h">
     11676      <Filter>ForwardingHeaders\runtime</Filter>
     11677    </ClInclude>
    1166311678    <ClInclude Include="..\ForwardingHeaders\runtime\JSString.h">
    1166411679      <Filter>ForwardingHeaders\runtime</Filter>
     
    1167711692    </ClInclude>
    1167811693    <ClInclude Include="..\ForwardingHeaders\runtime\StringObject.h">
     11694      <Filter>ForwardingHeaders\runtime</Filter>
     11695    </ClInclude>
     11696    <ClInclude Include="..\ForwardingHeaders\runtime\Symbol.h">
    1167911697      <Filter>ForwardingHeaders\runtime</Filter>
    1168011698    </ClInclude>
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r190239 r190272  
    63256325                E1FF8F6C180DB5BE00132674 /* CryptoAlgorithmRegistry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1FF8F6A180DB5BE00132674 /* CryptoAlgorithmRegistry.cpp */; };
    63266326                E1FF8F6D180DB5BE00132674 /* CryptoAlgorithmRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = E1FF8F6B180DB5BE00132674 /* CryptoAlgorithmRegistry.h */; };
     6327                E38838981BAD145F00D62EE3 /* JSModuleLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E38838941BAD145F00D62EE3 /* JSModuleLoader.cpp */; settings = {ASSET_TAGS = (); }; };
     6328                E38838991BAD145F00D62EE3 /* JSModuleLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = E38838951BAD145F00D62EE3 /* JSModuleLoader.h */; settings = {ASSET_TAGS = (); }; };
    63276329                E401C27517CE53EC00C41A35 /* ElementIteratorAssertions.h in Headers */ = {isa = PBXBuildFile; fileRef = E401C27417CE53EC00C41A35 /* ElementIteratorAssertions.h */; settings = {ATTRIBUTES = (Private, ); }; };
    63286330                E424A39E1330DF0100CF6DC9 /* LegacyTileGridTile.h in Headers */ = {isa = PBXBuildFile; fileRef = E424A39D1330DF0100CF6DC9 /* LegacyTileGridTile.h */; };
     
    1408614088                E1FF8F6A180DB5BE00132674 /* CryptoAlgorithmRegistry.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CryptoAlgorithmRegistry.cpp; sourceTree = "<group>"; };
    1408714089                E1FF8F6B180DB5BE00132674 /* CryptoAlgorithmRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoAlgorithmRegistry.h; sourceTree = "<group>"; };
     14090                E38838941BAD145F00D62EE3 /* JSModuleLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSModuleLoader.cpp; sourceTree = "<group>"; };
     14091                E38838951BAD145F00D62EE3 /* JSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSModuleLoader.h; sourceTree = "<group>"; };
    1408814092                E401C27417CE53EC00C41A35 /* ElementIteratorAssertions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementIteratorAssertions.h; sourceTree = "<group>"; };
    1408914093                E406F3FB1198307D009D59D6 /* ColorData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColorData.cpp; sourceTree = "<group>"; };
     
    2153821542                                E1C36D320EB0A094007410BC /* JSWorkerGlobalScopeBase.cpp */,
    2153921543                                E1C36D330EB0A094007410BC /* JSWorkerGlobalScopeBase.h */,
     21544                                E38838941BAD145F00D62EE3 /* JSModuleLoader.cpp */,
     21545                                E38838951BAD145F00D62EE3 /* JSModuleLoader.h */,
    2154021546                                4198BDEE1A81123600B22FB5 /* ReadableJSStream.cpp */,
    2154121547                                4198BDEF1A81123600B22FB5 /* ReadableJSStream.h */,
     
    2620826214                                52E2CAFC19FF0207001EEB4F /* MediaProducer.h in Headers */,
    2620926215                                4E19592A0A39DACC00220FE5 /* MediaQuery.h in Headers */,
     26216                                E38838991BAD145F00D62EE3 /* JSModuleLoader.h in Headers */,
    2621026217                                4E19592C0A39DACC00220FE5 /* MediaQueryEvaluator.h in Headers */,
    2621126218                                4E19592E0A39DACC00220FE5 /* MediaQueryExp.h in Headers */,
     
    2857428581                                498391510F1E76B400C23782 /* DOMWebKitCSSMatrix.mm in Sources */,
    2857528582                                8AD0A59714C88358000D83C5 /* DOMWebKitCSSRegionRule.mm in Sources */,
     28583                                E38838981BAD145F00D62EE3 /* JSModuleLoader.cpp in Sources */,
    2857628584                                31611E620E1C4E1400F6A579 /* DOMWebKitCSSTransformValue.mm in Sources */,
    2857728585                                3F2B33EC165AF15600E3987C /* DOMWebKitCSSViewportRule.mm in Sources */,
  • trunk/Source/WebCore/bindings/js/JSBindingsAllInOne.cpp

    r190198 r190272  
    107107#include "JSMessageEventCustom.cpp"
    108108#include "JSMessagePortCustom.cpp"
     109#include "JSModuleLoader.cpp"
    109110#include "JSMutationCallback.cpp"
    110111#include "JSMutationObserverCustom.cpp"
  • trunk/Source/WebCore/bindings/js/JSDOMWindowBase.cpp

    r190129 r190272  
    3030#include "JSDOMGlobalObjectTask.h"
    3131#include "JSDOMWindowCustom.h"
     32#include "JSModuleLoader.h"
    3233#include "JSNode.h"
    3334#include "Logging.h"
     
    3738#include "Settings.h"
    3839#include "WebCoreJSClientData.h"
     40#include <runtime/JSInternalPromiseDeferred.h>
    3941#include <runtime/Microtask.h>
    4042#include <wtf/MainThread.h>
     
    5759const ClassInfo JSDOMWindowBase::s_info = { "Window", &JSDOMGlobalObject::s_info, 0, CREATE_METHOD_TABLE(JSDOMWindowBase) };
    5860
    59 const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = { &shouldAllowAccessFrom, &supportsProfiling, &supportsRichSourceInfo, &shouldInterruptScript, &javaScriptRuntimeFlags, &queueTaskToEventLoop, &shouldInterruptScriptBeforeTimeout, nullptr, nullptr, nullptr, nullptr, nullptr };
     61const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = { &shouldAllowAccessFrom, &supportsProfiling, &supportsRichSourceInfo, &shouldInterruptScript, &javaScriptRuntimeFlags, &queueTaskToEventLoop, &shouldInterruptScriptBeforeTimeout, &moduleLoaderResolve, &moduleLoaderFetch, nullptr, nullptr, &moduleLoaderEvaluate };
    6062
    6163JSDOMWindowBase::JSDOMWindowBase(VM& vm, Structure* structure, PassRefPtr<DOMWindow> window, JSDOMWindowShell* shell)
     
    277279}
    278280
     281
     282JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSValue moduleName, JSC::JSValue importerModuleKey)
     283{
     284    JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
     285    if (RefPtr<Document> document = thisObject->impl().document())
     286        return document->moduleLoader()->resolve(globalObject, exec, moduleName, importerModuleKey);
     287    JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
     288    return deferred->reject(exec, jsUndefined());
     289}
     290
     291JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderFetch(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSValue moduleKey)
     292{
     293    JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
     294    if (RefPtr<Document> document = thisObject->impl().document())
     295        return document->moduleLoader()->fetch(globalObject, exec, moduleKey);
     296    JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
     297    return deferred->reject(exec, jsUndefined());
     298}
     299
     300JSC::JSValue JSDOMWindowBase::moduleLoaderEvaluate(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSValue moduleKey, JSC::JSValue moduleRecord)
     301{
     302    JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
     303    if (RefPtr<Document> document = thisObject->impl().document())
     304        return document->moduleLoader()->evaluate(globalObject, exec, moduleKey, moduleRecord);
     305    return JSC::jsUndefined();
     306}
     307
    279308} // namespace WebCore
  • trunk/Source/WebCore/bindings/js/JSDOMWindowBase.h

    r180653 r190272  
    7979
    8080    private:
     81        static JSC::JSInternalPromise* moduleLoaderResolve(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSValue, JSC::JSValue);
     82        static JSC::JSInternalPromise* moduleLoaderFetch(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSValue);
     83        static JSC::JSValue moduleLoaderEvaluate(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSValue, JSC::JSValue);
     84
    8185        RefPtr<DOMWindow> m_impl;
    8286        JSDOMWindowShell* m_shell;
  • trunk/Source/WebCore/dom/Document.cpp

    r190210 r190272  
    9797#include "InspectorInstrumentation.h"
    9898#include "JSLazyEventListener.h"
     99#include "JSModuleLoader.h"
    99100#include "Language.h"
    100101#include "LoaderStrategy.h"
     
    465466    , m_overMinimumLayoutThreshold(false)
    466467    , m_scriptRunner(std::make_unique<ScriptRunner>(*this))
     468    , m_moduleLoader(std::make_unique<JSModuleLoader>(*this))
    467469    , m_xmlVersion(ASCIILiteral("1.0"))
    468470    , m_xmlStandalone(StandaloneUnspecified)
     
    606608
    607609    m_scriptRunner = nullptr;
     610    m_moduleLoader = nullptr;
    608611
    609612    removeAllEventListeners();
  • trunk/Source/WebCore/dom/Document.h

    r190169 r190272  
    123123class LayoutRect;
    124124class LiveNodeList;
     125class JSModuleLoader;
    125126class JSNode;
    126127class Locale;
     
    935936   
    936937    ScriptRunner* scriptRunner() { return m_scriptRunner.get(); }
     938    JSModuleLoader* moduleLoader() { return m_moduleLoader.get(); }
    937939
    938940    HTMLScriptElement* currentScript() const { return !m_currentScriptStack.isEmpty() ? m_currentScriptStack.last().get() : 0; }
     
    15191521   
    15201522    std::unique_ptr<ScriptRunner> m_scriptRunner;
     1523    std::unique_ptr<JSModuleLoader> m_moduleLoader;
    15211524
    15221525    Vector<RefPtr<HTMLScriptElement>> m_currentScriptStack;
Note: See TracChangeset for help on using the changeset viewer.