summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorirobert@chromium.org <irobert@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-19 00:56:12 +0000
committerirobert@chromium.org <irobert@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-19 00:56:12 +0000
commiteb92f630d55178ce2ec03cc2f9ab6899bef8f465 (patch)
tree3bf34e8e40c2be442d3226d2ed399cac46b1f2b1 /content
parent707c7bb5c5f6450a53b9825fccded29266ce29d0 (diff)
downloadchromium_src-eb92f630d55178ce2ec03cc2f9ab6899bef8f465.zip
chromium_src-eb92f630d55178ce2ec03cc2f9ab6899bef8f465.tar.gz
chromium_src-eb92f630d55178ce2ec03cc2f9ab6899bef8f465.tar.bz2
Add loadCommit and loadStop Event.
loadCommit notifies that a load has committed with event properties "url" and "isTopLevel". loadStop notifies that all loads in the tag (including all subframes) have completed, such that any progress indicator can complete, with no event properties. BUG=154125, 153537 Review URL: https://chromiumcodereview.appspot.com/11035067 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@162877 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/browser_plugin/browser_plugin_guest.cc8
-rw-r--r--content/browser/browser_plugin/browser_plugin_guest.h2
-rw-r--r--content/browser/browser_plugin/browser_plugin_host_browsertest.cc40
-rw-r--r--content/common/browser_plugin_messages.h16
-rw-r--r--content/renderer/browser_plugin/browser_plugin.cc60
-rw-r--r--content/renderer/browser_plugin/browser_plugin.h8
-rw-r--r--content/renderer/browser_plugin/browser_plugin_browsertest.cc16
-rw-r--r--content/renderer/browser_plugin/browser_plugin_manager_impl.cc25
-rw-r--r--content/renderer/browser_plugin/browser_plugin_manager_impl.h8
-rw-r--r--content/test/data/browser_plugin_embedder.html12
10 files changed, 146 insertions, 49 deletions
diff --git a/content/browser/browser_plugin/browser_plugin_guest.cc b/content/browser/browser_plugin/browser_plugin_guest.cc
index 0c51bac..7c1105b 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.cc
+++ b/content/browser/browser_plugin/browser_plugin_guest.cc
@@ -426,7 +426,7 @@ void BrowserPluginGuest::DidCommitProvisionalLoadForFrame(
PageTransition transition_type,
RenderViewHost* render_view_host) {
// Inform its embedder of the updated URL.
- BrowserPluginMsg_DidNavigate_Params params;
+ BrowserPluginMsg_LoadCommit_Params params;
params.url = url;
params.is_top_level = is_main_frame;
params.process_id = render_view_host->GetProcess()->GetID();
@@ -435,10 +435,14 @@ void BrowserPluginGuest::DidCommitProvisionalLoadForFrame(
params.entry_count =
web_contents()->GetController().GetEntryCount();
SendMessageToEmbedder(
- new BrowserPluginMsg_DidNavigate(instance_id(), params));
+ new BrowserPluginMsg_LoadCommit(instance_id(), params));
RecordAction(UserMetricsAction("BrowserPlugin.Guest.DidNavigate"));
}
+void BrowserPluginGuest::DidStopLoading(RenderViewHost* render_view_host) {
+ SendMessageToEmbedder(new BrowserPluginMsg_LoadStop(instance_id()));
+}
+
void BrowserPluginGuest::RenderViewGone(base::TerminationStatus status) {
if (pending_input_event_reply_.get()) {
IPC::Message* reply_message = pending_input_event_reply_.release();
diff --git a/content/browser/browser_plugin/browser_plugin_guest.h b/content/browser/browser_plugin/browser_plugin_guest.h
index cd1ccec..714dc42 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.h
+++ b/content/browser/browser_plugin/browser_plugin_guest.h
@@ -114,6 +114,8 @@ class CONTENT_EXPORT BrowserPluginGuest : public NotificationObserver,
const GURL& url,
PageTransition transition_type,
RenderViewHost* render_view_host) OVERRIDE;
+ virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE;
+
virtual void RenderViewGone(base::TerminationStatus status) OVERRIDE;
// WebContentsDelegate implementation.
diff --git a/content/browser/browser_plugin/browser_plugin_host_browsertest.cc b/content/browser/browser_plugin/browser_plugin_host_browsertest.cc
index c40bfde..a0366f1 100644
--- a/content/browser/browser_plugin/browser_plugin_host_browsertest.cc
+++ b/content/browser/browser_plugin/browser_plugin_host_browsertest.cc
@@ -922,4 +922,44 @@ IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, DISABLED_PostMessageToIFrame) {
}
}
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, LoadStop) {
+ const char* kEmbedderURL = "files/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, "about:blank", true, "");
+
+ const string16 expected_title = ASCIIToUTF16("loadStop");
+ content::TitleWatcher title_watcher(
+ test_embedder()->web_contents(), expected_title);
+ // Renavigate the guest to |kHTMLForGuest|.
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ ExecuteSyncJSFunction(rvh, ASCIIToUTF16(
+ StringPrintf("SetSrc('%s');", kHTMLForGuest)));
+
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserPluginHostTest, LoadCommit) {
+ const char* kEmbedderURL = "files/browser_plugin_embedder.html";
+ StartBrowserPluginTest(kEmbedderURL, "about:blank", true, "");
+
+ const string16 expected_title = ASCIIToUTF16(
+ StringPrintf("loadCommit:%s", kHTMLForGuest));
+ content::TitleWatcher title_watcher(
+ test_embedder()->web_contents(), expected_title);
+ // Renavigate the guest to |kHTMLForGuest|.
+ RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
+ test_embedder()->web_contents()->GetRenderViewHost());
+ ExecuteSyncJSFunction(rvh, ASCIIToUTF16(
+ StringPrintf("SetSrc('%s');", kHTMLForGuest)));
+
+ string16 actual_title = title_watcher.WaitAndGetTitle();
+ EXPECT_EQ(expected_title, actual_title);
+ scoped_ptr<base::Value> is_top_level(rvh->ExecuteJavascriptAndGetValue(
+ string16(), ASCIIToUTF16("commitIsTopLevel")));
+ bool top_level_bool = false;
+ EXPECT_TRUE(is_top_level->GetAsBoolean(&top_level_bool));
+ EXPECT_EQ(true, top_level_bool);
+}
+
} // namespace content
diff --git a/content/common/browser_plugin_messages.h b/content/common/browser_plugin_messages.h
index fe5fd51..4c3026d 100644
--- a/content/common/browser_plugin_messages.h
+++ b/content/common/browser_plugin_messages.h
@@ -180,7 +180,7 @@ IPC_MESSAGE_CONTROL4(BrowserPluginMsg_LoadRedirect,
GURL /* new_url */,
bool /* is_top_level */)
-IPC_STRUCT_BEGIN(BrowserPluginMsg_DidNavigate_Params)
+IPC_STRUCT_BEGIN(BrowserPluginMsg_LoadCommit_Params)
// The current URL of the guest.
IPC_STRUCT_MEMBER(GURL, url)
// Indicates whether the navigation was on the top-level frame.
@@ -194,12 +194,16 @@ IPC_STRUCT_BEGIN(BrowserPluginMsg_DidNavigate_Params)
IPC_STRUCT_MEMBER(int, entry_count)
IPC_STRUCT_END()
-// When the guest navigates, the browser process informs the embedder through
-// the BrowserPluginMsg_DidNavigate message along with information about the
-// guest's current navigation state.
-IPC_MESSAGE_CONTROL2(BrowserPluginMsg_DidNavigate,
+// When the guest commits a navigation, the browser process informs
+// the embedder through the BrowserPluginMsg_DidCommit message.
+IPC_MESSAGE_CONTROL2(BrowserPluginMsg_LoadCommit,
int /* instance_id */,
- BrowserPluginMsg_DidNavigate_Params)
+ BrowserPluginMsg_LoadCommit_Params)
+
+// When the guest page has completed loading (including subframes), the browser
+// process informs the embedder through the BrowserPluginMsg_LoadStop message.
+IPC_MESSAGE_CONTROL1(BrowserPluginMsg_LoadStop,
+ int /* instance_id */)
// When the guest crashes, the browser process informs the embedder through this
// message.
diff --git a/content/renderer/browser_plugin/browser_plugin.cc b/content/renderer/browser_plugin/browser_plugin.cc
index ad90ac0..5bf93ea 100644
--- a/content/renderer/browser_plugin/browser_plugin.cc
+++ b/content/renderer/browser_plugin/browser_plugin.cc
@@ -45,9 +45,10 @@ namespace {
const char kCrashEventName[] = "crash";
const char kIsTopLevel[] = "isTopLevel";
const char kLoadAbortEventName[] = "loadAbort";
+const char kLoadCommitEventName[] = "loadCommit";
const char kLoadRedirectEventName[] = "loadRedirect";
const char kLoadStartEventName[] = "loadStart";
-const char kNavigationEventName[] = "navigation";
+const char kLoadStopEventName[] = "loadStop";
const char kNewURL[] = "newUrl";
const char kOldURL[] = "oldUrl";
const char kPartitionAttribute[] = "partition";
@@ -358,8 +359,37 @@ void BrowserPlugin::GuestCrashed() {
}
}
-void BrowserPlugin::DidNavigate(
- const BrowserPluginMsg_DidNavigate_Params& params) {
+void BrowserPlugin::LoadStart(const GURL& url, bool is_top_level) {
+ if (!HasListeners(kLoadStartEventName))
+ return;
+
+ WebKit::WebElement plugin = container()->element();
+ v8::HandleScope handle_scope;
+ v8::Context::Scope context_scope(
+ plugin.document().frame()->mainWorldScriptContext());
+
+ // Construct the loadStart event object.
+ v8::Local<v8::Object> event = v8::Object::New();
+ event->Set(v8::String::New(kURL, sizeof(kURL) - 1),
+ v8::String::New(url.spec().data(), url.spec().size()));
+ event->Set(v8::String::New(kIsTopLevel, sizeof(kIsTopLevel) - 1),
+ v8::Boolean::New(is_top_level));
+ v8::Local<v8::Value> val = event;
+
+ // TODO(fsamuel): Copying the event listeners is insufficent because
+ // new persistent handles are not created when the copy constructor is
+ // called. See http://crbug.com/155044.
+ EventListeners listeners(event_listener_map_[kLoadStartEventName]);
+ EventListeners::iterator it = listeners.begin();
+ for (; it != listeners.end(); ++it) {
+ WebKit::WebFrame* frame = plugin.document().frame();
+ if (frame)
+ frame->callFunctionEvenIfScriptDisabled(*it, v8::Object::New(), 1, &val);
+ }
+}
+
+void BrowserPlugin::LoadCommit(
+ const BrowserPluginMsg_LoadCommit_Params& params) {
// If the guest has just committed a new navigation then it is no longer
// crashed.
guest_crashed_ = false;
@@ -368,7 +398,7 @@ void BrowserPlugin::DidNavigate(
current_nav_entry_index_ = params.current_entry_index;
nav_entry_count_ = params.entry_count;
- if (!HasListeners(kNavigationEventName))
+ if (!HasListeners(kLoadCommitEventName))
return;
WebKit::WebElement plugin = container()->element();
@@ -376,7 +406,7 @@ void BrowserPlugin::DidNavigate(
v8::Context::Scope context_scope(
plugin.document().frame()->mainWorldScriptContext());
- // Construct the navigation event object.
+ // Construct the loadCommit event object.
v8::Local<v8::Object> event = v8::Object::New();
event->Set(v8::String::New(kURL, sizeof(kURL) - 1),
v8::String::New(src_.data(), src_.size()));
@@ -387,19 +417,17 @@ void BrowserPlugin::DidNavigate(
// TODO(fsamuel): Copying the event listeners is insufficent because
// new persistent handles are not created when the copy constructor is
// called. See http://crbug.com/155044.
- EventListeners listeners(event_listener_map_[kNavigationEventName]);
+ EventListeners listeners(event_listener_map_[kLoadCommitEventName]);
EventListeners::iterator it = listeners.begin();
for (; it != listeners.end(); ++it) {
WebKit::WebFrame* frame = plugin.document().frame();
- if (frame) {
- frame->callFunctionEvenIfScriptDisabled(
- *it, v8::Object::New(), 1, &val);
- }
+ if (frame)
+ frame->callFunctionEvenIfScriptDisabled(*it, v8::Object::New(), 1, &val);
}
}
-void BrowserPlugin::LoadStart(const GURL& url, bool is_top_level) {
- if (!HasListeners(kLoadStartEventName))
+void BrowserPlugin::LoadStop() {
+ if (!HasListeners(kLoadStopEventName))
return;
WebKit::WebElement plugin = container()->element();
@@ -407,18 +435,14 @@ void BrowserPlugin::LoadStart(const GURL& url, bool is_top_level) {
v8::Context::Scope context_scope(
plugin.document().frame()->mainWorldScriptContext());
- // Construct the loadStart event object.
+ // Construct the loadStop event object.
v8::Local<v8::Object> event = v8::Object::New();
- event->Set(v8::String::New(kURL, sizeof(kURL) - 1),
- v8::String::New(url.spec().data(), url.spec().size()));
- event->Set(v8::String::New(kIsTopLevel, sizeof(kIsTopLevel) - 1),
- v8::Boolean::New(is_top_level));
v8::Local<v8::Value> val = event;
// TODO(fsamuel): Copying the event listeners is insufficent because
// new persistent handles are not created when the copy constructor is
// called. See http://crbug.com/155044.
- EventListeners listeners(event_listener_map_[kLoadStartEventName]);
+ EventListeners listeners(event_listener_map_[kLoadStopEventName]);
EventListeners::iterator it = listeners.begin();
for (; it != listeners.end(); ++it) {
WebKit::WebFrame* frame = plugin.document().frame();
diff --git a/content/renderer/browser_plugin/browser_plugin.h b/content/renderer/browser_plugin/browser_plugin.h
index 5bdbf11..3f363a5 100644
--- a/content/renderer/browser_plugin/browser_plugin.h
+++ b/content/renderer/browser_plugin/browser_plugin.h
@@ -19,7 +19,7 @@
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragStatus.h"
struct BrowserPluginHostMsg_ResizeGuest_Params;
-struct BrowserPluginMsg_DidNavigate_Params;
+struct BrowserPluginMsg_LoadCommit_Params;
struct BrowserPluginMsg_UpdateRect_Params;
namespace content {
@@ -61,10 +61,12 @@ class CONTENT_EXPORT BrowserPlugin :
const BrowserPluginMsg_UpdateRect_Params& params);
// Inform the BrowserPlugin that its guest has crashed.
void GuestCrashed();
- // Informs the BrowserPlugin that the guest has navigated to a new URL.
- void DidNavigate(const BrowserPluginMsg_DidNavigate_Params& params);
+ // Inform the BrowserPlugin that the guest has navigated to a new URL.
+ void LoadCommit(const BrowserPluginMsg_LoadCommit_Params& params);
// Inform the BrowserPlugin that the guest has started loading a new page.
void LoadStart(const GURL& url, bool is_top_level);
+ // Inform the BrowserPlugin that the guest has finished loading a new page.
+ void LoadStop();
// Inform the BrowserPlugin that the guest has aborted loading a new page.
void LoadAbort(const GURL& url, bool is_top_level, const std::string& type);
// Inform the BrowserPlugin that the guest has redirected a navigation.
diff --git a/content/renderer/browser_plugin/browser_plugin_browsertest.cc b/content/renderer/browser_plugin/browser_plugin_browsertest.cc
index 6ab316a..9a05386 100644
--- a/content/renderer/browser_plugin/browser_plugin_browsertest.cc
+++ b/content/renderer/browser_plugin/browser_plugin_browsertest.cc
@@ -308,14 +308,14 @@ TEST_F(BrowserPluginTest, RemovePlugin) {
TEST_F(BrowserPluginTest, CustomEvents) {
const char* kAddEventListener =
"var url;"
- "function nav(e) {"
- " url = e.url;"
+ "function nav(u) {"
+ " url = u.url;"
"}"
"document.getElementById('browserplugin')."
- " addEventListener('navigation', nav);";
+ " addEventListener('loadCommit', nav);";
const char* kRemoveEventListener =
"document.getElementById('browserplugin')."
- " removeEventListener('navigation', nav);";
+ " removeEventListener('loadCommit', nav);";
const char* kGetProcessID =
"document.getElementById('browserplugin').getProcessId()";
const char* kGoogleURL = "http://www.google.com/";
@@ -339,19 +339,19 @@ TEST_F(BrowserPluginTest, CustomEvents) {
ASSERT_TRUE(browser_plugin);
{
- BrowserPluginMsg_DidNavigate_Params navigate_params;
+ BrowserPluginMsg_LoadCommit_Params navigate_params;
navigate_params.url = GURL(kGoogleURL);
navigate_params.process_id = 1337;
- browser_plugin->DidNavigate(navigate_params);
+ browser_plugin->LoadCommit(navigate_params);
EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url"));
EXPECT_EQ(1337, ExecuteScriptAndReturnInt(kGetProcessID));
}
ExecuteJavaScript(kRemoveEventListener);
{
- BrowserPluginMsg_DidNavigate_Params navigate_params;
+ BrowserPluginMsg_LoadCommit_Params navigate_params;
navigate_params.url = GURL(kGoogleNewsURL);
navigate_params.process_id = 42;
- browser_plugin->DidNavigate(navigate_params);
+ browser_plugin->LoadCommit(navigate_params);
// The URL variable should not change because we've removed the event
// listener.
EXPECT_EQ(kGoogleURL, ExecuteScriptAndReturnString("url"));
diff --git a/content/renderer/browser_plugin/browser_plugin_manager_impl.cc b/content/renderer/browser_plugin/browser_plugin_manager_impl.cc
index 8b4226a..b19260a 100644
--- a/content/renderer/browser_plugin/browser_plugin_manager_impl.cc
+++ b/content/renderer/browser_plugin/browser_plugin_manager_impl.cc
@@ -37,7 +37,6 @@ bool BrowserPluginManagerImpl::OnControlMessageReceived(
IPC_BEGIN_MESSAGE_MAP(BrowserPluginManagerImpl, message)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_UpdateRect, OnUpdateRect)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_GuestCrashed, OnGuestCrashed)
- IPC_MESSAGE_HANDLER(BrowserPluginMsg_DidNavigate, OnDidNavigate)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_AdvanceFocus, OnAdvanceFocus)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_GuestContentWindowReady,
OnGuestContentWindowReady)
@@ -46,6 +45,8 @@ bool BrowserPluginManagerImpl::OnControlMessageReceived(
IPC_MESSAGE_HANDLER(BrowserPluginMsg_LoadStart, OnLoadStart)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_LoadAbort, OnLoadAbort)
IPC_MESSAGE_HANDLER(BrowserPluginMsg_LoadRedirect, OnLoadRedirect)
+ IPC_MESSAGE_HANDLER(BrowserPluginMsg_LoadCommit, OnLoadCommit)
+ IPC_MESSAGE_HANDLER(BrowserPluginMsg_LoadStop, OnLoadStop)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
@@ -66,14 +67,6 @@ void BrowserPluginManagerImpl::OnGuestCrashed(int instance_id) {
plugin->GuestCrashed();
}
-void BrowserPluginManagerImpl::OnDidNavigate(
- int instance_id,
- const BrowserPluginMsg_DidNavigate_Params& params) {
- BrowserPlugin* plugin = GetBrowserPlugin(instance_id);
- if (plugin)
- plugin->DidNavigate(params);
-}
-
void BrowserPluginManagerImpl::OnAdvanceFocus(int instance_id, bool reverse) {
BrowserPlugin* plugin = GetBrowserPlugin(instance_id);
if (plugin)
@@ -102,6 +95,20 @@ void BrowserPluginManagerImpl::OnLoadStart(int instance_id,
plugin->LoadStart(url, is_top_level);
}
+void BrowserPluginManagerImpl::OnLoadCommit(
+ int instance_id,
+ const BrowserPluginMsg_LoadCommit_Params& params) {
+ BrowserPlugin* plugin = GetBrowserPlugin(instance_id);
+ if (plugin)
+ plugin->LoadCommit(params);
+}
+
+void BrowserPluginManagerImpl::OnLoadStop(int instance_id) {
+ BrowserPlugin* plugin = GetBrowserPlugin(instance_id);
+ if (plugin)
+ plugin->LoadStop();
+}
+
void BrowserPluginManagerImpl::OnLoadAbort(int instance_id,
const GURL& url,
bool is_top_level,
diff --git a/content/renderer/browser_plugin/browser_plugin_manager_impl.h b/content/renderer/browser_plugin/browser_plugin_manager_impl.h
index 28e1486..20d70cc 100644
--- a/content/renderer/browser_plugin/browser_plugin_manager_impl.h
+++ b/content/renderer/browser_plugin/browser_plugin_manager_impl.h
@@ -8,7 +8,7 @@
#include "content/renderer/browser_plugin/browser_plugin_manager.h"
#include "googleurl/src/gurl.h"
-struct BrowserPluginMsg_DidNavigate_Params;
+struct BrowserPluginMsg_LoadCommit_Params;
struct BrowserPluginMsg_UpdateRect_Params;
namespace content {
@@ -34,14 +34,16 @@ class BrowserPluginManagerImpl : public BrowserPluginManager {
int message_id,
const BrowserPluginMsg_UpdateRect_Params& params);
void OnGuestCrashed(int instance_id);
- void OnDidNavigate(int instance_id,
- const BrowserPluginMsg_DidNavigate_Params& params);
+
void OnAdvanceFocus(int instance_id, bool reverse);
void OnGuestContentWindowReady(int instance_id, int guest_routing_id);
void OnShouldAcceptTouchEvents(int instance_id, bool accept);
void OnLoadStart(int instance_id,
const GURL& url,
bool is_top_level);
+ void OnLoadCommit(int instance_id,
+ const BrowserPluginMsg_LoadCommit_Params& params);
+ void OnLoadStop(int instance_id);
void OnLoadAbort(int instance_id,
const GURL& url,
bool is_top_level,
diff --git a/content/test/data/browser_plugin_embedder.html b/content/test/data/browser_plugin_embedder.html
index 5702027..0f3d6a0 100644
--- a/content/test/data/browser_plugin_embedder.html
+++ b/content/test/data/browser_plugin_embedder.html
@@ -5,6 +5,16 @@ function loadAbort(evt) {
function loadStart(evt) {
document.title = evt.url;
}
+function loadStop(evt) {
+ document.title = "loadStop";
+}
+
+var commitIsTopLevel;
+function loadCommit(evt) {
+ document.title = "loadCommit:" + evt.url;
+ commitIsTopLevel = evt.isTopLevel;
+}
+
var redirectOldUrl;
var redirectNewUrl;
function loadRedirect(event) {
@@ -89,4 +99,6 @@ function receiveMessage(event) {
plugin.addEventListener('loadAbort', loadAbort);
plugin.addEventListener('loadRedirect', loadRedirect);
window.addEventListener('message', receiveMessage, false);
+ plugin.addEventListener('loadStop', loadStop);
+ plugin.addEventListener('loadCommit', loadCommit);
</script>