Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixtures/html/simple-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.info('call from script-module');
17 changes: 12 additions & 5 deletions fixtures/html/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,15 @@
background-color: rgba(224, 172, 150, 0.594);
padding: 2rem;
}

#float-area>p.first {
background-color: rgba(134, 55, 20, 0.352);
}
</style>
<script src="./simple.js"></script>
<script>
console.log('Hello, world!');
const html = document.firstChild;
console.log(html);
console.info('Simple HTML:', window['foobar']);
</script>
<script src="./simple.js"></script>
</head>

<body>
Expand All @@ -132,7 +131,9 @@
</div>
<article>
<p class="primary first">
A range of organizations join the World Wide Web Consortium as Members to work with us to drive the direction of core web technologies and exchange ideas with industry and research leaders. We rotate randomly a few of our Member organizations' logos underneath.
A range of organizations join the World Wide Web Consortium as Members to work with us to drive the direction of
core web technologies and exchange ideas with industry and research leaders. We rotate randomly a few of our
Member organizations' logos underneath.
</p>
<p>
1 October 2024 was W3C's 30th anniversary. We celebrated at our annual TPAC our three decades, advances in the
Expand All @@ -152,6 +153,12 @@
<div>Footer</div>
<img class="float-image" src="https://www.gstatic.com/webp/gallery/1.webp" alt="A cute kitten"
style="margin-top: 200px;">

<script>
const header = document.querySelector('#switch');
header.textContent += (' (foobar=' + window['foobar'] + ')');
</script>
<script src="./simple-module.js" type="module"></script>
</body>

</html>
4 changes: 4 additions & 0 deletions fixtures/html/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ run(async () => {
// + 'foobar'
// + '</div>';
});

window['foobar'] = Math.random();
console.info('set global foobar =', window['foobar']);
console.info('script end');
50 changes: 50 additions & 0 deletions src/client/dom/browsing_context.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include <idgen.hpp>
#include "./browsing_context.hpp"
#include "../html/html_script_element.hpp"

namespace dom
{
uint32_t BrowsingContext::registerScriptForExecution(std::shared_ptr<HTMLScriptElement> script)
{
static TrIdGenerator idgen(0x1);
uint32_t script_id = idgen.get();
script_execution_queue.emplace_back(script_id, std::weak_ptr<HTMLScriptElement>(script));

// Try to execute if this is the first script in the queue
tryExecuteNextScript();
return script_id;
}

void BrowsingContext::tryExecuteNextScript()
{
while (!script_execution_queue.empty())
{
auto &handle = script_execution_queue.front();
auto script = handle.scriptElement.lock();

// If script was destroyed, remove from queue and continue
if (!script)
{
script_execution_queue.pop_front();
continue;
}

// Execute the script and it will return false if not ready yet
if (!script->executeScriptFromQueue())
return; // Script will call notifyScriptExecutionComplete when done

// Script not ready yet, stop trying
break;
}
}

void BrowsingContext::notifyScriptExecutionComplete(uint32_t script_id)
{
if (!script_execution_queue.empty() &&
script_execution_queue.front().scriptId == script_id)
{
script_execution_queue.pop_front();
tryExecuteNextScript();
}
}
}
39 changes: 39 additions & 0 deletions src/client/dom/browsing_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <functional>
#include <string>
#include <memory>
#include <deque>
#include <unordered_map>
#include <v8.h>

#include "./dom_parser.hpp"
Expand All @@ -17,6 +19,23 @@ namespace dom
URL,
Source,
};

// Forward declaration to avoid circular dependency
class HTMLScriptElement;

// Script execution handle to decouple from HTMLScriptElement
struct ScriptExecutionHandle
{
uint32_t scriptId;
std::weak_ptr<HTMLScriptElement> scriptElement;

ScriptExecutionHandle(uint32_t id, std::weak_ptr<HTMLScriptElement> element)
: scriptId(id)
, scriptElement(element)
{
}
};

class BrowsingContext : public RuntimeContext
{
public:
Expand Down Expand Up @@ -96,7 +115,27 @@ namespace dom
return scriptingContext->updateImportMapFromJSON(json);
}

/**
* Register a script for document-order execution.
* Returns a unique script ID for tracking.
*/
uint32_t registerScriptForExecution(std::shared_ptr<HTMLScriptElement> script);

/**
* Try to execute the next script in the queue if it's ready.
*/
void tryExecuteNextScript();

/**
* Notify that a script has completed execution.
*/
void notifyScriptExecutionComplete(uint32_t script_id);

public:
vector<shared_ptr<Document>> documents;

private:
// Script execution queue using handles to avoid circular dependency
std::deque<ScriptExecutionHandle> script_execution_queue;
};
}
57 changes: 55 additions & 2 deletions src/client/html/html_script_element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ namespace dom
{
compiledScript = browsingContext->createScript(baseURI, isClassicScript() ? SourceTextType::Classic : SourceTextType::ESM);
compiledScript->crossOrigin = crossOrigin == HTMLScriptCrossOrigin::Anonymous ? true : false;

// Register classic scripts (non-async, non-defer) with execution queue
if (isClassicScript() && !async && !defer)
{
usesExecutionQueue = true;
auto scriptElement = dynamic_pointer_cast<HTMLScriptElement>(shared_from_this());
scriptExecutionId = browsingContext->registerScriptForExecution(scriptElement);
}

loadSource();
}
// TODO(yorkie): support "speculationrules"?
Expand Down Expand Up @@ -109,7 +118,17 @@ namespace dom
skipScriptExecution = true;

if (!skipScriptExecution)
executeScript();
{
// If script uses execution queue, let the queue manage execution
if (usesExecutionQueue)
{
browsingContext->tryExecuteNextScript();
}
else
{
executeScript();
}
}
}

void HTMLScriptElement::scheduleScriptExecution()
Expand All @@ -120,7 +139,18 @@ namespace dom

// Check if the script is already compiled, then schedule the execution by default.
if (scriptCompiled)
executeScript();
{
// If script uses execution queue, let the queue manage execution
if (usesExecutionQueue)
{
auto browsingContext = ownerDocument->lock()->browsingContext;
browsingContext->tryExecuteNextScript();
}
else
{
executeScript();
}
}
}

void HTMLScriptElement::executeScript()
Expand All @@ -131,6 +161,29 @@ namespace dom
browsingContext->scriptingContext->evaluate(compiledScript);
scriptExecutedOnce = true;
scriptExecutionScheduled = false;

// Notify browsing context if this script was using the execution queue
if (usesExecutionQueue)
browsingContext->notifyScriptExecutionComplete(scriptExecutionId);

dispatchEvent(dom::DOMEventType::Load);
}

bool HTMLScriptElement::isReadyToExecute() const
{
return scriptCompiled && !scriptExecutedOnce && compiledScript != nullptr;
}

bool HTMLScriptElement::executeScriptFromQueue()
{
if (isReadyToExecute())
{
executeScript();
return true;
}
else
{
return false;
}
}
}
19 changes: 19 additions & 0 deletions src/client/html/html_script_element.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ namespace dom
void scheduleScriptExecution();
void executeScript();

public:
/**
* Check if the script is ready to execute (compiled and not yet executed).
* Used by the BrowsingContext execution queue.
*/
bool isReadyToExecute() const;

/**
* Execute the script from the execution queue.
* This bypasses the normal execution checks since the queue manages ordering.
*
* @returns Whether the script was executed.
*/
bool executeScriptFromQueue();

public:
/**
* A boolean value that controls how the script should be executed. For classic scripts, if the async property is set to true, the external
Expand Down Expand Up @@ -115,5 +130,9 @@ namespace dom
bool scriptCompiled = false;
bool scriptExecutedOnce = false;
bool scriptExecutionScheduled = false;

// Whether the script uses the execution queue
bool usesExecutionQueue = false;
size_t scriptExecutionId = 0;
};
}