I'd like you to do a code review. Please review the following patch:
----------------------------------------------------------------------
r655: wangxianzhu | 2008-01-06 15:59:56 +0800
Finished Text/HTML details view feature:
1. Added text details view;
2. Reuse browser child process;
3. Removed ScriptContextInterface::LockObject() and UnlockObject(), and changed to using scriptable reference counting;
Added OnReferenceChange notification in ScriptableInterface.
----------------------------------------------------------------------
=== hosts/simple/resources/text_details_view.xml
==================================================================
--- hosts/simple/resources/text_details_view.xml (revision 654)
+++ hosts/simple/resources/text_details_view.xml (revision 655)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<view width="300" height="250" resizable="true" onopen="onopen()">
+ <script>
+<!--
+function onopen() {
+ label.innerText = detailsViewData.GetValue("content");
+}
+-->
+ </script>
+ <label name="label" width="100%" height="100%" wordWrap="true"/>
+</view>
=== hosts/simple/resources/html_details_view.xml
==================================================================
--- hosts/simple/resources/html_details_view.xml (revision 654)
+++ hosts/simple/resources/html_details_view.xml (revision 655)
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<view width="300" height="250" resizable="true" onopen="onopen()">
+ <script>
+<!--
+function onopen() {
+ browser.contentType = detailsViewData.getValue("contentType");
+ browser.innerText = detailsViewData.getValue("content");
+
+ browser.onGetProperty = function(name) {
+ if (external[name] == undefined) return "\"undefined\"";
+ if (typeof external[name] == "function") return "\"function\"";
+ return external[name];
+ };
+ browser.onSetProperty = function(name, value) {
+ external[name] = value;
+ };
+ browser.onCallback = function(name, args) {
+ external[name].apply(null, args);
+ };
+ browser.onOpenURL = function(url) {
+ view.openURL(url);
+ };
+}
+-->
+ </script>
+ <_browser name="browser" width="100%" height="100%"/>
+</view>
=== hosts/simple/resources/details_view.xml
==================================================================
--- hosts/simple/resources/details_view.xml (revision 654)
+++ hosts/simple/resources/details_view.xml (revision 655)
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<view width="200" height="200" resizable="true" onopen="onopen()">
- <script>
-<!--
-function onopen() {
- browser.contentType = detailsViewData.getValue("contentType");
- browser.innerText = detailsViewData.getValue("content");
-
- browser.onGetProperty = function(name) {
- if (external[name] == undefined) return "\"undefined\"";
- if (typeof external[name] == "function") return "\"function\"";
- return external[name];
- };
- browser.onSetProperty = function(name, value) {
- external[name] = value;
- };
- browser.onCallback = function(name, args) {
- external[name].apply(null, args);
- };
- browser.onOpenURL = function(url) {
- view.openURL(url);
- };
-}
--->
- </script>
- <_browser name="browser" width="100%" height="100%"/>
-</view>
=== ggadget/xml_http_request.h
==================================================================
--- ggadget/xml_http_request.h (revision 654)
+++ ggadget/xml_http_request.h (revision 655)
@@ -25,9 +25,8 @@
class ScriptContextInterface;
class XMLParserInterface;
-XMLHttpRequestInterface *CreateXMLHttpRequest(
- MainLoopInterface *main_loop, ScriptContextInterface *script_context,
- XMLParserInterface *xml_parser);
+XMLHttpRequestInterface *CreateXMLHttpRequest(MainLoopInterface *main_loop,
+ XMLParserInterface *xml_parser);
} // namespace ggadget
=== ggadget/gtkmoz/browser_element.cc
==================================================================
--- ggadget/gtkmoz/browser_element.cc (revision 654)
+++ ggadget/gtkmoz/browser_element.cc (revision 655)
@@ -14,10 +14,11 @@
limitations under the License.
*/
-#include <cmath>
#include <unistd.h>
#include <fcntl.h>
+#include <signal.h>
#include <vector>
+#include <cmath>
#include <gtk/gtk.h>
#include <ggadget/logger.h>
#include <ggadget/main_loop_interface.h>
@@ -40,19 +41,25 @@
container_(NULL),
container_x_(0), container_y_(0),
socket_(NULL),
- child_pid_(0),
- down_fd_(0), up_fd_(0), ret_fd_(0),
- up_fd_watch_(0) {
+ controller_(BrowserController::get(owner->GetView()->GetMainLoop())),
+ browser_id_(controller_->AddBrowserElement(this)),
+ x_(0), y_(0), width_(0), height_(0) {
}
+
~Impl() {
- QuitChild();
+ if (GTK_IS_WIDGET(socket_))
+ gtk_widget_destroy(socket_);
+ controller_->SendCommand(kCloseBrowserCommand, browser_id_, NULL);
+ controller_->RemoveBrowserElement(browser_id_);
}
- bool EnsureSocket() {
+ void CreateSocket() {
if (socket_)
- return true;
+ return;
socket_ = gtk_socket_new();
+ g_signal_connect(socket_, "realize", G_CALLBACK(OnSocketRealize), this);
+
owner_->GetView()->GetNativeWidgetInfo(
reinterpret_cast<void **>(&container_), &container_x_, &container_y_);
if (!GTK_IS_FIXED(container_)) {
@@ -60,243 +67,374 @@
G_OBJECT_TYPE_NAME(container_));
gtk_widget_destroy(socket_);
socket_ = NULL;
- return false;
+ return;
}
- gtk_fixed_put(GTK_FIXED(container_), socket_,
- container_x_ + static_cast<gint>(round(owner_->GetPixelX())),
- container_y_ + static_cast<gint>(round(owner_->GetPixelY())));
- gtk_widget_set_size_request(
- socket_,
- static_cast<gint>(ceil(owner_->GetPixelWidth())),
- static_cast<gint>(ceil(owner_->GetPixelHeight())));
+
+ x_ = container_x_ + static_cast<gint>(round(owner_->GetPixelX()));
+ y_ = container_y_ + static_cast<gint>(round(owner_->GetPixelY()));
+ width_ = static_cast<gint>(ceil(owner_->GetPixelWidth()));
+ height_ = static_cast<gint>(ceil(owner_->GetPixelHeight()));
+ gtk_fixed_put(GTK_FIXED(container_), socket_, x_, y_);
+ gtk_widget_set_size_request(socket_, width_, height_);
gtk_widget_show(socket_);
-
- if (GTK_WIDGET_REALIZED(container_)) {
- EnsurePipeAndChild();
- return true;
- } else {
- g_signal_connect(socket_, "realize", G_CALLBACK(OnSocketRealize), this);
- return false;
- }
}
static void OnSocketRealize(GtkWidget *widget, gpointer user_data) {
Impl *impl = static_cast<Impl *>(user_data);
- impl->EnsurePipeAndChild();
- impl->SendCommand(kSetContentCommand, impl->content_type_.c_str(),
- impl->content_.c_str(), NULL);
+ std::string browser_id_str = StringPrintf("%zd", impl->browser_id_);
+ // Convert GdkNativeWindow to intmax_t to ensure the printf format
+ // to match the data type and not to loose accuracy.
+ std::string socket_id_str = StringPrintf("0x%jx",
+ static_cast<intmax_t>(gtk_socket_get_id(GTK_SOCKET(impl->socket_))));
+ impl->controller_->SendCommand(kNewBrowserCommand, impl->browser_id_,
+ socket_id_str.c_str(), NULL);
+ impl->SetChildContent();
}
- void EnsurePipeAndChild() {
- int down_pipe_fds[2], up_pipe_fds[2], ret_pipe_fds[2];
- if (pipe(down_pipe_fds) == -1) {
- LOG("Failed to create downwards pipe to browser child");
- return;
- }
- if (pipe(up_pipe_fds) == -1) {
- LOG("Failed to create upwards pipe to browser child");
- close(down_pipe_fds[0]);
- close(down_pipe_fds[1]);
- return;
- }
- if (pipe(ret_pipe_fds) == -1) {
- LOG("Failed to create return value pipe to browser child");
- close(down_pipe_fds[0]);
- close(down_pipe_fds[1]);
- close(up_pipe_fds[0]);
- close(up_pipe_fds[1]);
- return;
- }
+ void SetChildContent() {
+ controller_->SendCommand(kSetContentCommand, browser_id_,
+ content_type_.c_str(), content_.c_str(), NULL);
+ }
- child_pid_ = fork();
- if (child_pid_ == -1) {
- LOG("Failed to fork browser child");
- close(down_pipe_fds[0]);
- close(down_pipe_fds[1]);
- close(up_pipe_fds[0]);
- close(up_pipe_fds[1]);
- close(ret_pipe_fds[0]);
- close(ret_pipe_fds[1]);
- return;
+ void Layout() {
+ if (GTK_IS_FIXED(container_) && GTK_IS_SOCKET(socket_)) {
+ (DLOG("Layout: %lf %lf %lf %lf", owner_->GetPixelX(), owner_->GetPixelY(),
+ owner_->GetPixelWidth(), owner_->GetPixelHeight()));
+ gint x = container_x_ + static_cast<gint>(round(owner_->GetPixelX()));
+ gint y = container_y_ + static_cast<gint>(round(owner_->GetPixelY()));
+ gint width = static_cast<gint>(ceil(owner_->GetPixelWidth()));
+ gint height = static_cast<gint>(ceil(owner_->GetPixelHeight()));
+
+ if (x != x_ || y != y_) {
+ x_ = x;
+ y_ = y;
+ gtk_fixed_move(GTK_FIXED(container_), socket_, x, y);
+ }
+ if (width != width_ || height != height_) {
+ width_ = width;
+ height_ = height;
+ gtk_widget_set_size_request(socket_, width, height);
+ }
}
+ }
- if (child_pid_ == 0) {
- // This is the child process.
- close(down_pipe_fds[1]);
- close(up_pipe_fds[0]);
- close(ret_pipe_fds[1]);
- // Convert GdkNativeWindow to intmax_t to ensure the printf format
- // to match the data type and not to loose accuracy.
- std::string socket_id_str = StringPrintf("0x%jx",
- static_cast<intmax_t>(gtk_socket_get_id(GTK_SOCKET(socket_))));
- std::string down_fd_str = StringPrintf("%d", down_pipe_fds[0]);
- std::string up_fd_str = StringPrintf("%d", up_pipe_fds[1]);
- std::string ret_fd_str = StringPrintf("%d", ret_pipe_fds[0]);
- // TODO: Deal with the situtation that the main program is not run from
- // the directory it is in.
- execl("browser_child", "browser_child", socket_id_str.c_str(),
- down_fd_str.c_str(), up_fd_str.c_str(), ret_fd_str.c_str(), NULL);
- LOG("Failed to execute browser child");
- _exit(-1);
+ void ProcessUpMessage(const std::vector<const char *> ¶ms) {
+ std::string result;
+ const char *type = params[0];
+ if (strcmp(type, kGetPropertyFeedback) == 0) {
+ if (params.size() != 3) {
+ LOG("%s feedback needs 3 parameters, but %zd is given",
+ kGetPropertyFeedback, params.size());
+ } else {
+ result = get_property_signal_(JSONString(params[2])).value;
+ }
+ } else if (strcmp(type, kSetPropertyFeedback) == 0) {
+ if (params.size() != 4) {
+ LOG("%s feedback needs 4 parameters, but %zd is given",
+ kSetPropertyFeedback, params.size());
+ } else {
+ set_property_signal_(JSONString(params[2]), JSONString(params[3]));
+ }
+ } else if (strcmp(type, kCallbackFeedback) == 0) {
+ if (params.size() < 3) {
+ LOG("%s feedback needs at least 3 parameters, but %zd is given",
+ kCallbackFeedback, params.size());
+ } else {
+ size_t callback_params_count = params.size() - 3;
+ Variant *callback_params = new Variant[callback_params_count];
+ for (size_t i = 0; i < callback_params_count; i++)
+ callback_params[i] = Variant(JSONString(params[i + 3]));
+ JSONString result_json = callback_signal_(
+ JSONString(params[2]),
+ ScriptableArray::Create(callback_params, callback_params_count));
+ result = result_json.value;
+ }
+ } else if (strcmp(type, kOpenURLFeedback) == 0) {
+ if (params.size() != 3) {
+ LOG("%s feedback needs 3 parameters, but %zd is given",
+ kOpenURLFeedback, params.size());
+ }
+ open_url_signal_(params[2]);
} else {
- close(down_pipe_fds[0]);
- close(up_pipe_fds[1]);
- close(ret_pipe_fds[0]);
- down_fd_ = down_pipe_fds[1];
- up_fd_ = up_pipe_fds[0];
- ret_fd_ = ret_pipe_fds[1];
- int up_fd_flags = fcntl(up_fd_, F_GETFL);
- up_fd_flags |= O_NONBLOCK;
- fcntl(up_fd_, F_SETFL, up_fd_flags);
- up_fd_watch_ = main_loop_->AddIOReadWatch(
- up_fd_, new WatchCallbackSlot(NewSlot(this, &Impl::OnUpReady)));
+ LOG("Unknown feedback: %s", type);
}
+ DLOG("ProcessUpMessage: %s(%s,%s,%s,%s) result: %s", type,
+ params.size() > 1 ? params[1] : "",
+ params.size() > 2 ? params[2] : "",
+ params.size() > 3 ? params[3] : "",
+ params.size() > 4 ? params[4] : "",
+ result.c_str());
+ result += '\n';
+ controller_->Write(controller_->ret_fd_, result.c_str(), result.size());
}
- void Layout() {
- gtk_fixed_move(
- GTK_FIXED(container_), socket_,
- container_x_ + static_cast<gint>(round(owner_->GetPixelX())),
- container_y_ + static_cast<gint>(round(owner_->GetPixelY())));
- gtk_widget_set_size_request(
- socket_,
- static_cast<gint>(ceil(owner_->GetPixelWidth())),
- static_cast<gint>(ceil(owner_->GetPixelHeight())));
+ void SetContent(const JSONString &content) {
+ content_ = content.value;
+ if (!GTK_IS_SOCKET(socket_)) {
+ // After the child exited, the socket_ will become an invalid GtkSocket.
+ CreateSocket();
+ } else {
+ SetChildContent();
+ }
}
- bool OnUpReady(int fd) {
- // ASSERT(fd == up_fd_);
- char buffer[4096];
- ssize_t read_bytes;
- while ((read_bytes = read(up_fd_, buffer, sizeof(buffer))) > 0) {
- up_buffer_.append(buffer, read_bytes);
- if (read_bytes < static_cast<ssize_t>(sizeof(buffer)))
- break;
+ class BrowserController {
+ public:
+ BrowserController(MainLoopInterface *main_loop)
+ : main_loop_(main_loop),
+ child_pid_(0),
+ down_fd_(0), up_fd_(0), ret_fd_(0),
+ up_fd_watch_(0),
+ ping_timer_watch_(main_loop->AddTimeoutWatch(
+ kPingInterval * 3 / 2,
+ new WatchCallbackSlot(
+ NewSlot(this, &BrowserController::PingTimerCallback)))),
+ ping_flag_(false),
+ removing_watch_(false) {
+ StartChild();
}
- ProcessUpMessages();
- return true;
- }
- void ProcessUpMessages() {
- size_t curr_pos = 0;
- size_t eom_pos;
- while ((eom_pos = up_buffer_.find(kEndOfMessageFull, curr_pos)) !=
- up_buffer_.npos) {
- std::vector<const char *> params;
- while (curr_pos < eom_pos) {
- size_t end_of_line_pos = up_buffer_.find('\n', curr_pos);
- ASSERT(end_of_line_pos != up_buffer_.npos);
- up_buffer_[end_of_line_pos] = '\0';
- params.push_back(up_buffer_.c_str() + curr_pos);
- curr_pos = end_of_line_pos + 1;
+ ~BrowserController() {
+ StopChild(false);
+ instance_ = NULL;
+ // This object may live longer than the main loop, so don't remove timer
+ // watch here.
+ }
+
+ bool PingTimerCallback(int watch) {
+ if (!ping_flag_)
+ RestartChild();
+ ping_flag_ = false;
+ return true;
+ }
+
+ static BrowserController *get(MainLoopInterface *main_loop) {
+ ASSERT(!instance_ || instance_->main_loop_ == main_loop);
+ if (!instance_)
+ instance_ = new BrowserController(main_loop);
+ return instance_;
+ }
+
+ void StartChild() {
+ removing_watch_ = false;
+
+ int down_pipe_fds[2], up_pipe_fds[2], ret_pipe_fds[2];
+ if (pipe(down_pipe_fds) == -1) {
+ LOG("Failed to create downwards pipe to browser child");
+ return;
}
- ASSERT(curr_pos = eom_pos + 1);
- curr_pos += sizeof(kEndOfMessageFull) - 2;
- ProcessUpMessage(params);
+ if (pipe(up_pipe_fds) == -1) {
+ LOG("Failed to create upwards pipe to browser child");
+ close(down_pipe_fds[0]);
+ close(down_pipe_fds[1]);
+ return;
+ }
+ if (pipe(ret_pipe_fds) == -1) {
+ LOG("Failed to create return value pipe to browser child");
+ close(down_pipe_fds[0]);
+ close(down_pipe_fds[1]);
+ close(up_pipe_fds[0]);
+ close(up_pipe_fds[1]);
+ return;
+ }
+
+ child_pid_ = fork();
+ if (child_pid_ == -1) {
+ LOG("Failed to fork browser child");
+ close(down_pipe_fds[0]);
+ close(down_pipe_fds[1]);
+ close(up_pipe_fds[0]);
+ close(up_pipe_fds[1]);
+ close(ret_pipe_fds[0]);
+ close(ret_pipe_fds[1]);
+ return;
+ }
+
+ if (child_pid_ == 0) {
+ // This is the child process.
+ close(down_pipe_fds[1]);
+ close(up_pipe_fds[0]);
+ close(ret_pipe_fds[1]);
+ std::string down_fd_str = StringPrintf("%d", down_pipe_fds[0]);
+ std::string up_fd_str = StringPrintf("%d", up_pipe_fds[1]);
+ std::string ret_fd_str = StringPrintf("%d", ret_pipe_fds[0]);
+ // TODO: Deal with the situtation that the main program is not run from
+ // the directory it is in.
+ execl("browser_child", "browser_child",
+ down_fd_str.c_str(), up_fd_str.c_str(), ret_fd_str.c_str(), NULL);
+ LOG("Failed to execute browser child");
+ _exit(-1);
+ } else {
+ close(down_pipe_fds[0]);
+ close(up_pipe_fds[1]);
+ close(ret_pipe_fds[0]);
+ down_fd_ = down_pipe_fds[1];
+ up_fd_ = up_pipe_fds[0];
+ ret_fd_ = ret_pipe_fds[1];
+ int up_fd_flags = fcntl(up_fd_, F_GETFL);
+ up_fd_flags |= O_NONBLOCK;
+ fcntl(up_fd_, F_SETFL, up_fd_flags);
+ up_fd_watch_ = main_loop_->AddIOReadWatch(up_fd_,
+ new UpFdWatchCallback(this));
+ }
}
- up_buffer_.erase(0, curr_pos);
- }
- void ProcessUpMessage(const std::vector<const char *> ¶ms) {
- std::string result;
- if (params.size() > 0) {
- const char *type = params[0];
- if (strcmp(type, kGetPropertyFeedback) == 0) {
- if (params.size() != 2) {
- LOG("%s feedback needs 2 parameters, but %zd is given",
- kGetPropertyFeedback, params.size());
- } else {
- result = get_property_signal_(JSONString(params[1])).value;
+ void StopChild(bool on_error) {
+ if (!removing_watch_) {
+ removing_watch_ = true;
+ main_loop_->RemoveWatch(up_fd_watch_);
+ removing_watch_ = false;
+ }
+ up_fd_watch_ = 0;
+ if (child_pid_) {
+ // Don't send QUIT command on error to prevent error loops.
+ if (!on_error) {
+ std::string quit_command(kQuitCommand);
+ quit_command += kEndOfMessageFull;
+ Write(down_fd_, quit_command.c_str(), quit_command.size());
}
- } else if (strcmp(type, kSetPropertyFeedback) == 0) {
- if (params.size() != 3) {
- LOG("%s feedback needs 3 parameters, but %zd is given",
- kSetPropertyFeedback, params.size());
- } else {
- set_property_signal_(JSONString(params[1]), JSONString(params[2]));
- }
- } else if (strcmp(type, kCallbackFeedback) == 0) {
- if (params.size() < 2) {
- LOG("%s feedback needs at least 2 parameters, but %zd is given",
- kCallbackFeedback, params.size());
- } else {
- size_t callback_params_count = params.size() - 2;
- Variant *callback_params = new Variant[callback_params_count];
- for (size_t i = 0; i < callback_params_count; i++)
- callback_params[i] = Variant(JSONString(params[i + 2]));
- JSONString result_json = callback_signal_(
- JSONString(params[1]),
- ScriptableArray::Create(callback_params, callback_params_count));
- result = result_json.value;
- }
- } else if (strcmp(type, kOpenURLFeedback) == 0) {
- if (params.size() < 2) {
- LOG("%s feedback needs 2 parameters, but %zd is given",
- kOpenURLFeedback, params.size());
- }
- open_url_signal_(JSONString(params[1]));
- } else {
- LOG("Unknown feedback: %s", type);
+ close(down_fd_);
+ down_fd_ = 0;
+ close(up_fd_);
+ up_fd_ = 0;
+ close(ret_fd_);
+ ret_fd_ = 0;
+ child_pid_ = 0;
}
- DLOG("ProcessUpMessage: %s(%s,%s) result: %s", type,
- params.size() > 1 ? params[1] : "",
- params.size() > 2 ? params[2] : "",
- result.c_str());
+ browser_elements_.clear();
}
- result += '\n';
- write(ret_fd_, result.c_str(), result.size());
- }
- void QuitChild() {
- SendCommand(kQuitCommand, NULL);
- if (socket_) {
- gtk_widget_destroy(socket_);
- socket_ = NULL;
+ void RestartChild() {
+ StopChild(true);
+ StartChild();
}
- if (down_fd_) {
- close(down_fd_);
- down_fd_ = 0;
+
+ size_t AddBrowserElement(Impl *impl) {
+ // Find an empty slot.
+ BrowserElements::iterator it = std::find(browser_elements_.begin(),
+ browser_elements_.end(),
+ static_cast<Impl *>(NULL));
+ size_t result = it - browser_elements_.begin();
+ if (it == browser_elements_.end())
+ browser_elements_.push_back(impl);
+ else
+ *it = impl;
+ return result;
}
- if (up_fd_) {
- main_loop_->RemoveWatch(up_fd_watch_);
- close(up_fd_);
- up_fd_ = 0;
- up_fd_watch_ = 0;
+
+ void RemoveBrowserElement(size_t id) {
+ browser_elements_[id] = NULL;
}
- if (ret_fd_) {
- close(ret_fd_);
- ret_fd_ = 0;
+
+ class UpFdWatchCallback : public WatchCallbackInterface {
+ public:
+ UpFdWatchCallback(BrowserController *controller)
+ : controller_(controller) {
+ }
+ virtual bool Call(MainLoopInterface *main_loop, int watch_id) {
+ controller_->OnUpReady();
+ return true;
+ }
+ virtual void OnRemove(MainLoopInterface *main_loop, int watch_id) {
+ if (!controller_->removing_watch_) {
+ controller_->removing_watch_ = true;
+ // Removed by the mainloop when mainloop itself is to be destroyed.
+ delete controller_;
+ }
+ delete this;
+ }
+ private:
+ BrowserController *controller_;
+ };
+
+ void OnUpReady() {
+ char buffer[4096];
+ ssize_t read_bytes;
+ while ((read_bytes = read(up_fd_, buffer, sizeof(buffer))) > 0) {
+ up_buffer_.append(buffer, read_bytes);
+ if (read_bytes < static_cast<ssize_t>(sizeof(buffer)))
+ break;
+ }
+ if (read_bytes < 0)
+ RestartChild();
+ ProcessUpMessages();
}
- child_pid_ = 0;
- up_buffer_.clear();
- }
- void SetContent(const JSONString &content) {
- content_ = content.value;
- if (EnsureSocket()) {
- SendCommand(kSetContentCommand, content_type_.c_str(),
- content.value.c_str(), NULL);
+ void ProcessUpMessages() {
+ size_t curr_pos = 0;
+ size_t eom_pos;
+ while ((eom_pos = up_buffer_.find(kEndOfMessageFull, curr_pos)) !=
+ up_buffer_.npos) {
+ std::vector<const char *> params;
+ while (curr_pos < eom_pos) {
+ size_t end_of_line_pos = up_buffer_.find('\n', curr_pos);
+ ASSERT(end_of_line_pos != up_buffer_.npos);
+ up_buffer_[end_of_line_pos] = '\0';
+ params.push_back(up_buffer_.c_str() + curr_pos);
+ curr_pos = end_of_line_pos + 1;
+ }
+ ASSERT(curr_pos = eom_pos + 1);
+ curr_pos += sizeof(kEndOfMessageFull) - 2;
+
+ if (params.size() == 1 && strcmp(params[0], kPingFeedback) == 0) {
+ Write(ret_fd_, kPingAckFull, sizeof(kPingAckFull) - 1);
+ ping_flag_ = true;
+ } else if (params.size() < 2) {
+ LOG("No enough feedback parameters");
+ } else {
+ size_t id = static_cast<size_t>(strtol(params[1], NULL, 0));
+ if (id < browser_elements_.size() && browser_elements_[id] != NULL) {
+ browser_elements_[id]->ProcessUpMessage(params);
+ } else {
+ LOG("Invalid browser id: %s", params[1]);
+ }
+ }
+ }
+ up_buffer_.erase(0, curr_pos);
}
- // The most common reason that EnsureSocket() returns false is that the
- // container widget has not been realized. The remaining things will be
- // done when the container widget get realized.
- }
- void SendCommand(const char *type, ...) {
- if (down_fd_ > 0) {
- std::string buffer(type);
- va_list ap;
- va_start(ap, type);
- const char *param;
- while ((param = va_arg(ap, const char *)) != NULL) {
+ void SendCommand(const char *type, size_t browser_id, ...) {
+ if (down_fd_ > 0) {
+ std::string buffer(type);
buffer += '\n';
- buffer += param;
+ buffer += StringPrintf("%zd", browser_id);
+
+ va_list ap;
+ va_start(ap, browser_id);
+ const char *param;
+ while ((param = va_arg(ap, const char *)) != NULL) {
+ buffer += '\n';
+ buffer += param;
+ }
+ buffer += kEndOfMessageFull;
+ Write(down_fd_, buffer.c_str(), buffer.size());
}
- buffer += kEndOfMessageFull;
- write(down_fd_, buffer.c_str(), buffer.size());
}
- }
+ static void OnSigPipe(int sig) {
+ instance_->RestartChild();
+ }
+
+ void Write(int fd, const char *data, size_t size) {
+ sighandler_t old_handler = signal(SIGPIPE, OnSigPipe);
+ if (write(fd, data, size) < 0)
+ RestartChild();
+ signal(SIGPIPE, old_handler);
+ }
+
+ static BrowserController *instance_;
+ MainLoopInterface *main_loop_;
+ int child_pid_;
+ int down_fd_, up_fd_, ret_fd_;
+ int up_fd_watch_;
+ int ping_timer_watch_;
+ bool ping_flag_;
+ std::string up_buffer_;
+ typedef std::vector<Impl *> BrowserElements;
+ BrowserElements browser_elements_;
+ bool removing_watch_;
+ };
+
BrowserElement *owner_;
MainLoopInterface *main_loop_;
std::string content_type_;
@@ -304,16 +442,18 @@
GtkWidget *container_;
int container_x_, container_y_;
GtkWidget *socket_;
- pid_t child_pid_;
- int down_fd_, up_fd_, ret_fd_;
- int up_fd_watch_;
- std::string up_buffer_;
+ BrowserController *controller_;
+ size_t browser_id_;
+ gint x_, y_, width_, height_;
Signal1<JSONString, JSONString> get_property_signal_;
Signal2<void, JSONString, JSONString> set_property_signal_;
Signal2<JSONString, JSONString, ScriptableArray *> callback_signal_;
- Signal1<void, JSONString> open_url_signal_;
+ Signal1<void, const std::string &> open_url_signal_;
};
+BrowserElement::Impl::BrowserController *
+ BrowserElement::Impl::BrowserController::instance_ = NULL;
+
BrowserElement::BrowserElement(BasicElement *parent, View *view,
const char *name)
: BasicElement(parent, view, "browser", name, true),
=== ggadget/gtkmoz/browser_child.cc
==================================================================
--- ggadget/gtkmoz/browser_child.cc (revision 654)
+++ ggadget/gtkmoz/browser_child.cc (revision 655)
@@ -16,9 +16,11 @@
#include <unistd.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
+#include <vector>
#include <gtk/gtk.h>
#include <gtkmozembed.h>
@@ -28,12 +30,14 @@
#include <nsCOMPtr.h>
#include <dom/nsIScriptNameSpaceManager.h>
#include <nsCRT.h>
+#include <nsICategoryManager.h>
#include <nsIComponentRegistrar.h>
#include <nsIGenericFactory.h>
-#include <nsICategoryManager.h>
+#include <nsIInterfaceRequestor.h>
#include <nsServiceManagerUtils.h>
#include <nsStringAPI.h>
#include <nsXPCOMCID.h>
+#include <xpconnect/nsIXPConnect.h>
#include <xpconnect/nsIXPCScriptable.h>
#include <ggadget/smjs/json.h>
@@ -44,17 +48,25 @@
using ggadget::smjs::JSONDecode;
using ggadget::gtkmoz::kEndOfMessage;
using ggadget::gtkmoz::kEndOfMessageFull;
+using ggadget::gtkmoz::kNewBrowserCommand;
using ggadget::gtkmoz::kSetContentCommand;
+using ggadget::gtkmoz::kCloseBrowserCommand;
using ggadget::gtkmoz::kQuitCommand;
using ggadget::gtkmoz::kGetPropertyFeedback;
using ggadget::gtkmoz::kSetPropertyFeedback;
using ggadget::gtkmoz::kCallbackFeedback;
using ggadget::gtkmoz::kOpenURLFeedback;
+using ggadget::gtkmoz::kPingFeedback;
+using ggadget::gtkmoz::kPingAck;
+using ggadget::gtkmoz::kPingInterval;
// Default down and ret fds are standard input and up fd is standard output.
// The default values are useful when browser child is tested independently.
static int g_down_fd = 0, g_up_fd = 1, g_ret_fd = 0;
-static GtkMozEmbed *g_embed = NULL;
+static std::vector<GtkMozEmbed *> g_embeds;
+static GtkMozEmbed *g_embed_for_new_window = NULL;
+static GtkMozEmbed *g_last_new_window_embed = NULL;
+static GtkWidget *g_popup_for_new_window = NULL;
#define EXTOBJ_CLASSNAME "ExternalObject"
#define EXTOBJ_PROPERTY_NAME "external"
@@ -64,6 +76,51 @@
{ 0xb8, 0x1e, 0x85, 0x15, 0xe7, 0x9f, 0x00, 0x30 } \
}
+// We can't include "nsIScriptGlobalObject.h" because it requires
+// MOZILLA_INTERAL_API defined.
+static const nsIID kIScriptGlobalObjectIID = {
+ 0xd326a211, 0xdc31, 0x45c6,
+ { 0x98, 0x97, 0x22, 0x11, 0xea, 0xbc, 0xd0, 0x1c }
+};
+
+static int FindBrowserId(JSContext *cx) {
+ JSObject *js_global = JS_GetGlobalObject(cx);
+ if (!js_global) {
+ fprintf(stderr, "browser_child: No global object\n");
+ return -1;
+ }
+
+ JSClass *cls = JS_GET_CLASS(cx, js_global);
+ if (!cls || ((~cls->flags) & (JSCLASS_HAS_PRIVATE |
+ JSCLASS_PRIVATE_IS_NSISUPPORTS))) {
+ fprintf(stderr, "browser_child: Global object is not a nsISupports");
+ return -1;
+ }
+ nsIXPConnectWrappedNative *global_wrapper =
+ reinterpret_cast<nsIXPConnectWrappedNative *>(JS_GetPrivate(cx, js_global));
+ nsISupports *global = global_wrapper->Native();
+
+ nsresult rv;
+ for (std::vector<GtkMozEmbed *>::const_iterator it = g_embeds.begin();
+ it != g_embeds.end(); ++it) {
+ if (*it) {
+ nsCOMPtr<nsIWebBrowser> browser;
+ gtk_moz_embed_get_nsIWebBrowser(*it, getter_AddRefs(browser));
+ nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(browser, &rv));
+ NS_ENSURE_SUCCESS(rv, -1);
+ nsISupports *global1;
+ rv = req->GetInterface(kIScriptGlobalObjectIID,
+ reinterpret_cast<void **>(&global1));
+ NS_ENSURE_SUCCESS(rv, -1);
+ global1->Release();
+ if (global1 == global)
+ return it - g_embeds.begin();
+ }
+ }
+ fprintf(stderr, "browser_child: Can't find GtkMozEmbed from JS context");
+ return -1;
+}
+
static std::string SendFeedbackBuffer(const std::string &buffer) {
write(g_up_fd, buffer.c_str(), buffer.size());
@@ -74,12 +131,12 @@
return reply;
}
-// Send a feedback with parameters to the controller through the up channel,
-// and return the reply (got in the return value channel).
-static std::string SendFeedback(const char *type, ...) {
+static std::string SendFeedbackV(const char *type, int browser_id, va_list ap) {
std::string buffer(type);
- va_list ap;
- va_start(ap, type);
+ char browser_id_buf[20];
+ snprintf(browser_id_buf, sizeof(browser_id_buf), "\n%d", browser_id);
+ buffer += browser_id_buf;
+
const char *param;
while ((param = va_arg(ap, const char *)) != NULL) {
buffer += '\n';
@@ -89,10 +146,40 @@
return SendFeedbackBuffer(buffer);
}
+static std::string SendFeedbackWithBrowserId(const char *type, int browser_id,
+ ...) {
+ va_list ap;
+ va_start(ap, browser_id);
+ std::string result = SendFeedbackV(type, browser_id, ap);
+ va_end(ap);
+ return result;
+}
+
+// Send a feedback with parameters to the controller through the up channel,
+// and return the reply (got in the return value channel).
+static std::string SendFeedback(const char *type, JSContext *cx, ...) {
+ int browser_id = FindBrowserId(cx);
+ if (browser_id == -1)
+ return "";
+
+ va_list ap;
+ va_start(ap, cx);
+ std::string result = SendFeedbackV(type, browser_id, ap);
+ va_end(ap);
+ return result;
+}
+
JSBool InvokeFunction(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval) {
+ int browser_id = FindBrowserId(cx);
+ if (browser_id == -1)
+ return JS_FALSE;
+
std::string buffer(kCallbackFeedback);
- buffer += '\n';
+ char browser_id_buf[20];
+ snprintf(browser_id_buf, sizeof(browser_id_buf), "\n%d\n", browser_id);
+ buffer += browser_id_buf;
+
// According to JS stack structure, argv[-2] is the current function object.
jsval func_val = argv[-2];
buffer += JS_GetFunctionName(JS_ValueToFunction(cx, func_val));
@@ -132,7 +219,8 @@
jsval *vp, PRBool *ret_val) {
std::string json;
NS_ENSURE_TRUE(JSONEncode(cx, id, &json), NS_ERROR_FAILURE);
- std::string result = SendFeedback(kGetPropertyFeedback, json.c_str(), NULL);
+ std::string result = SendFeedback(kGetPropertyFeedback, cx,
+ json.c_str(), NULL);
if (result == "\"\\\"function\\\"\"") {
JSFunction *function = JS_NewFunction(cx, InvokeFunction, 0, 0,
obj, json.c_str());
@@ -155,8 +243,8 @@
std::string name_json, value_json;
NS_ENSURE_TRUE(JSONEncode(cx, id, &name_json), NS_ERROR_FAILURE);
NS_ENSURE_TRUE(JSONEncode(cx, *vp, &value_json), NS_ERROR_FAILURE);
- SendFeedback(kSetPropertyFeedback, name_json.c_str(), value_json.c_str(),
- NULL);
+ SendFeedback(kSetPropertyFeedback, cx,
+ name_json.c_str(), value_json.c_str(), NULL);
*ret_val = PR_TRUE;
return NS_OK;
}
@@ -246,6 +334,8 @@
};
nsresult InitExternalObject() {
+ g_external_object.AddRef();
+
nsCOMPtr<nsIComponentRegistrar> registrar;
nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
NS_ENSURE_SUCCESS(rv, rv);
@@ -308,33 +398,157 @@
return true;
}
+static gint OnOpenURL(GtkMozEmbed *embed, const char *url, gpointer data) {
+ if (embed == g_embed_for_new_window) {
+ gtk_widget_destroy(g_popup_for_new_window);
+ g_popup_for_new_window = NULL;
+ g_embed_for_new_window = NULL;
+ embed = g_last_new_window_embed;
+ }
+
+ std::vector<GtkMozEmbed *>::const_iterator it =
+ std::find(g_embeds.begin(), g_embeds.end(), embed);
+ if (it != g_embeds.end()) {
+ SendFeedbackWithBrowserId(kOpenURLFeedback, it - g_embeds.begin(),
+ url, NULL);
+ }
+ // The controller should have opened the URL, so don't let the embedded
+ // browser open it.
+ return TRUE;
+}
+
+static void OnNewWindow(GtkMozEmbed *embed, GtkMozEmbed **retval,
+ gint chrome_mask, gpointer data) {
+ if (embed == g_embed_for_new_window) {
+ *retval = NULL;
+ } else {
+ if (!GTK_IS_WIDGET(g_embed_for_new_window)) {
+ g_embed_for_new_window = GTK_MOZ_EMBED(gtk_moz_embed_new());
+ g_signal_connect(g_embed_for_new_window, "new_window",
+ G_CALLBACK(OnNewWindow), NULL);
+ g_signal_connect(g_embed_for_new_window, "open_uri",
+ G_CALLBACK(OnOpenURL), NULL);
+ g_popup_for_new_window = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_container_add(GTK_CONTAINER(g_popup_for_new_window),
+ GTK_WIDGET(g_embed_for_new_window));
+ gtk_widget_set_size_request(GTK_WIDGET(g_embed_for_new_window), 0, 0);
+ gtk_window_resize(GTK_WINDOW(g_popup_for_new_window), 0, 0);
+ gtk_widget_show_all(g_popup_for_new_window);
+ }
+ *retval = g_embed_for_new_window;
+ g_last_new_window_embed = embed;
+ }
+}
+
+static void OnBrowserDestroy(GtkObject *object, gpointer user_data) {
+ size_t id = reinterpret_cast<size_t>(user_data);
+ if (id < g_embeds.size())
+ g_embeds[id] = NULL;
+}
+
+static void RemoveBrowser(size_t id) {
+ if (id >= g_embeds.size()) {
+ fprintf(stderr, "browser_child: Invalid browser id %zd to remove\n", id);
+ return;
+ }
+ GtkMozEmbed *embed = g_embeds[id];
+ if (GTK_IS_WIDGET(embed)) {
+ GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(embed));
+ if (GTK_IS_WIDGET(parent))
+ gtk_widget_destroy(parent);
+ else
+ gtk_widget_destroy(GTK_WIDGET(embed));
+ }
+ g_embeds[id] = NULL;
+}
+
+static void NewBrowser(int param_count, const char **params, size_t id) {
+ if (param_count != 3) {
+ fprintf(stderr, "browser_child: Incorrect param count for %s: "
+ "3 expected, %d given", kSetContentCommand, param_count);
+ return;
+ }
+
+ // The new id can be less than or equals to the current size.
+ if (id > g_embeds.size()) {
+ fprintf(stderr, "browser_child: New browser id is too big: %zd\n", id);
+ return;
+ }
+ if (id == g_embeds.size()) {
+ g_embeds.push_back(NULL);
+ } else if (g_embeds[id] != NULL) {
+ fprintf(stderr, "browser_child: Warning: new browser id slot is "
+ "not empty: %zd\n", id);
+ RemoveBrowser(id);
+ }
+
+ GdkNativeWindow socket_id = static_cast<GdkNativeWindow>(strtol(params[2],
+ NULL, 0));
+ GtkWidget *window = socket_id ? gtk_plug_new(socket_id) :
+ gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(window, "destroy", G_CALLBACK(OnBrowserDestroy),
+ reinterpret_cast<gpointer>(id));
+ GtkMozEmbed *embed = GTK_MOZ_EMBED(gtk_moz_embed_new());
+ g_embeds[id] = embed;
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(embed));
+ g_signal_connect(embed, "new_window", G_CALLBACK(OnNewWindow), NULL);
+ g_signal_connect(embed, "open_uri", G_CALLBACK(OnOpenURL), NULL);
+ gtk_widget_show_all(window);
+}
+
+static void SetContent(int param_count, const char **params, size_t id) {
+ if (param_count != 4) {
+ fprintf(stderr, "browser_child: Incorrect param count for %s: "
+ "4 expected, %d given", kSetContentCommand, param_count);
+ return;
+ }
+ if (id >= g_embeds.size()) {
+ fprintf(stderr, "browser_child: Invalid browser id %zd to remove\n", id);
+ return;
+ }
+
+ GtkMozEmbed *embed = g_embeds[id];
+ // params[2]: mime type; params[3]: JSON encoded content string.
+ nsString content;
+ if (!DecodeJSONString(params[3], &content)) {
+ fprintf(stderr, "browser_child: Invalid JSON string: %s\n", params[3]);
+ return;
+ }
+ NS_ConvertUTF16toUTF8 utf8(content);
+ gtk_moz_embed_render_data(embed, utf8.get(), utf8.Length(),
+ // Base URI and MIME type.
+ "file:///dev/null", params[2]);
+}
+
static void ProcessDownMessage(int param_count, const char **params) {
NS_ASSERTION(param_count > 0, "");
- if (strcmp(params[0], kSetContentCommand) == 0) {
- if (param_count == 3) {
- // params[1]: mime type; params[2]: JSON encoded content string.
- nsString content;
- if (!DecodeJSONString(params[2], &content)) {
- fprintf(stderr, "browser_child: Invalid JSON string: %s\n", params[2]);
- } else {
- NS_ConvertUTF16toUTF8 utf8(content);
- gtk_moz_embed_render_data(g_embed, utf8.get(), utf8.Length(),
- // Base URI and MIME type.
- "file:///dev/null", params[1]);
- }
- } else {
- fprintf(stderr, "browser_child: Incorrect param count for %s: "
- "3 expected, %d given", kSetContentCommand, param_count);
- }
- } else if (strcmp(params[0], kQuitCommand) == 0) {
+ if (strcmp(params[0], kQuitCommand) == 0) {
gtk_main_quit();
- } else {
- fprintf(stderr, "browser_child: Invalid command: %s\n", params[0]);
+ return;
}
+ if (param_count < 2) {
+ fprintf(stderr, "browser_child: No enough command parameter\n");
+ return;
+ }
+
+ size_t id = static_cast<size_t>(strtol(params[1], NULL, 0));
+ if (strcmp(params[0], kNewBrowserCommand) == 0) {
+ NewBrowser(param_count, params, id);
+ return;
+ }
+ if (strcmp(params[0], kSetContentCommand) == 0) {
+ SetContent(param_count, params, id);
+ return;
+ }
+ if (strcmp(params[0], kCloseBrowserCommand) == 0) {
+ RemoveBrowser(id);
+ return;
+ }
+ fprintf(stderr, "browser_child: Invalid command: %s\n", params[0]);
}
static void ProcessDownMessages() {
- const int kMaxParams = 3;
+ const int kMaxParams = 4;
size_t curr_pos = 0;
size_t eom_pos;
while ((eom_pos = g_down_buffer.find(kEndOfMessageFull, curr_pos)) !=
@@ -356,7 +570,7 @@
curr_pos = end_of_line_pos + 1;
}
NS_ASSERTION(curr_pos = eom_pos + 1, "");
- curr_pos += sizeof(kEndOfMessageFull) - 1;
+ curr_pos += sizeof(kEndOfMessageFull) - 2;
if (param_count > 0)
ProcessDownMessage(param_count, params);
}
@@ -379,31 +593,32 @@
return TRUE;
}
-static void OnNewWindow(GtkMozEmbed *embed, GtkMozEmbed **retval,
- gint chrome_mask, gpointer data) {
- // TODO:
- *retval = NULL;
+static void OnSigPipe(int sig) {
+ fprintf(stderr, "browser_child: SIGPIPE occured, exiting...\n");
+ gtk_main_quit();
}
-static gint OnOpenURL(GtkMozEmbed *embed, const char *url, gpointer data) {
- SendFeedback(kOpenURLFeedback, url, NULL);
- // The controller should have opened the URL, so don't let the embedded
- // browser open it.
- return FALSE;
+static gboolean CheckController(gpointer data) {
+ std::string buffer(kPingFeedback);
+ buffer += kEndOfMessageFull;
+ std::string result = SendFeedbackBuffer(buffer);
+ if (result != kPingAck) {
+ fprintf(stderr, "browser_child: Ping failed, exiting...\n");
+ gtk_main_quit();
+ return FALSE;
+ }
+ return TRUE;
}
int main(int argc, char **argv) {
gtk_init(&argc, &argv);
-
- GdkNativeWindow socket_id = 0;
+ signal(SIGPIPE, OnSigPipe);
if (argc >= 2)
- socket_id = strtol(argv[1], NULL, 0);
+ g_down_fd = g_ret_fd = strtol(argv[1], NULL, 0);
if (argc >= 3)
- g_down_fd = g_ret_fd = strtol(argv[2], NULL, 0);
+ g_up_fd = strtol(argv[2], NULL, 0);
if (argc >= 4)
- g_up_fd = strtol(argv[3], NULL, 0);
- if (argc >= 5)
- g_ret_fd = strtol(argv[4], NULL, 0);
+ g_ret_fd = strtol(argv[3], NULL, 0);
// Set the down FD to non-blocking mode to make the gtk main loop happy.
int down_fd_flags = fcntl(g_down_fd, F_GETFL);
@@ -414,18 +629,15 @@
int down_fd_watch = g_io_add_watch(channel, G_IO_IN, OnDownFDReady, NULL);
g_io_channel_unref(channel);
- GtkWidget *window = socket_id ? gtk_plug_new(socket_id) :
- gtk_window_new(GTK_WINDOW_TOPLEVEL);
- g_signal_connect(window, "destroy", gtk_main_quit, NULL);
+ gtk_moz_embed_push_startup();
+ InitExternalObject();
+ if (g_ret_fd != g_down_fd) {
+ // Only start ping timer in actual environment to ease testing.
+ g_timeout_add(kPingInterval, CheckController, NULL);
+ }
- g_embed = GTK_MOZ_EMBED(gtk_moz_embed_new());
- InitExternalObject();
- gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(g_embed));
- g_signal_connect(g_embed, "new_window", G_CALLBACK(OnNewWindow), NULL);
- g_signal_connect(g_embed, "open_uri", G_CALLBACK(OnOpenURL), NULL);
- // gtk_moz_embed_load_url(g_embed, "http://www.google.com");
- gtk_widget_show_all(window);
gtk_main();
g_source_remove(down_fd_watch);
+ gtk_moz_embed_pop_startup();
return 0;
}
=== ggadget/gtkmoz/browser_child.h
==================================================================
--- ggadget/gtkmoz/browser_child.h (revision 654)
+++ ggadget/gtkmoz/browser_child.h (revision 655)
@@ -27,22 +27,48 @@
*/
static const char kEndOfMessage[] = "\"\"\"EOM\"\"\"";
/** End of message tag including the proceeding and succeeding line breaks. */
-static const char kEndOfMessageFull[] = "\n\"\"\"EOM\"\"\"\n";
+const char kEndOfMessageFull[] = "\n\"\"\"EOM\"\"\"\n";
/**
+ * The controller tells the child to open a new browser.
+ *
+ * Message format:
+ * <code>
+ * NEW\n
+ * Browser ID\n
+ * Socket ID\n
+ * """EOM"""\n
+ * </code>
+ */
+const char kNewBrowserCommand[] = "NEW";
+
+/**
* The controller sets the content to display by the browser child.
*
* Message format:
* <code>
* CONTENT\n
+ * Browser ID\n
* Mime type (not JSON encoded)\n
* Contents as a string encoded in JSON\n
* """EOM"""\n
* </code>
*/
-static const char kSetContentCommand[] = "CONTENT";
+const char kSetContentCommand[] = "CONTENT";
/**
+ * The controller wants to close a browser.
+ *
+ * Message format:
+ * <code>
+ * CLOSE\n
+ * Browser ID\n
+ * """EOM"""\n
+ * </code>
+ */
+const char kCloseBrowserCommand[] = "CLOSE";
+
+/**
* The controller wants the child browser to quit.
*
* Message Format:
@@ -51,7 +77,7 @@
* """EOM"""\n
* </code>
*/
-static const char kQuitCommand[] = "QUIT";
+const char kQuitCommand[] = "QUIT";
/**
* The browser child tells the controller that the script wants to get the
@@ -60,6 +86,7 @@
* Messsage format:
* <code>
* GET\n
+ * Browser ID\n
* Property key encoded in JSON\n
* """EOM"""\n
* </code>
@@ -71,7 +98,7 @@
* is a function, or "\"undefined\"" if the value is undefined.\n
* </code>
*/
-static const char kGetPropertyFeedback[] = "GET";
+const char kGetPropertyFeedback[] = "GET";
/**
* The browser child tells the controller that the script has set the value of
@@ -80,6 +107,7 @@
* Messsage format:
* <code>
* SET\n
+ * Browser ID\n
* Property key encoded in JSON\n
* Property value encoded in JSON\n
* """EOM"""\n
@@ -87,7 +115,7 @@
*
* The controller must immediately reply a message containing only a '\n'.
*/
-static const char kSetPropertyFeedback[] = "SET";
+const char kSetPropertyFeedback[] = "SET";
/**
* The browser child tells the controller that the script has invoked a method
@@ -96,6 +124,7 @@
* Messsage format:
* <code>
* CALL\n
+ * Browser ID\n
* Method name encoded in JSON\n
* The first parameter encoded in JSON\n
* ...
@@ -110,7 +139,7 @@
* is a function, or "\"undefined\"" if the value is undefined.\n
* </code>
*/
-static const char kCallbackFeedback[] = "CALL";
+const char kCallbackFeedback[] = "CALL";
/**
* The browser child tells the controller that the browser is about to open
@@ -119,14 +148,32 @@
* Messsage format:
* <code>
* OPEN\n
- * URL encoded in JSON\n
+ * Browser ID\n
+ * URL (not JSON encoded)\n
* """EOM"""\n
* </code>
*
* The controller must immediately reply a message containing only a '\n'.
*/
-static const char kOpenURLFeedback[] = "OPEN";
+const char kOpenURLFeedback[] = "OPEN";
+/**
+ * The browser child periodically pings the controller to check if the
+ * controller died.
+ *
+ * Message format:
+ * <code>
+ * PING\n
+ * """EOM"""\n
+ * </code>
+ *
+ * The controller must immediately reply a message containing "ACK\n".
+ */
+const char kPingFeedback[] = "PING";
+const char kPingAck[] = "ACK";
+const char kPingAckFull[] = "ACK\n";
+const int kPingInterval = 30000; // 30 seconds.
+
} // namespace gtkmoz
} // namespace ggadget
=== ggadget/gtkmoz/CMakeLists.txt
==================================================================
--- ggadget/gtkmoz/CMakeLists.txt (revision 654)
+++ ggadget/gtkmoz/CMakeLists.txt (revision 655)
@@ -28,6 +28,9 @@
PKGCONFIG_EX(firefox-gtkmozembed 1.5 GTKMOZEMBED_LIBRARIES)
PKGCONFIG_EX(firefox-js 1.5 JS_LIBRARIES)
+# Bug of firefox-js package. Define the following to make JS_GET_CLASS work.
+ADD_DEFINITIONS(-DJS_THREADSAFE)
+
SET(CHILD_SRCS
browser_child.cc
../smjs/json.cc
=== ggadget/tests/xml_http_request_test.cc
==================================================================
--- ggadget/tests/xml_http_request_test.cc (revision 654)
+++ ggadget/tests/xml_http_request_test.cc (revision 655)
@@ -43,7 +43,7 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
ASSERT_EQ(XMLHttpRequestInterface::UNSENT, request->GetReadyState());
// Invalid request method.
ASSERT_EQ(XMLHttpRequestInterface::SYNTAX_ERR,
@@ -104,7 +104,8 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
+ request->Attach();
Callback callback(request);
@@ -130,7 +131,7 @@
request->GetResponseBody(&str, &size));
ASSERT_STREQ("ABCDEFG\n", str);
ASSERT_EQ(8u, size);
- delete request;
+ request->Detach();
delete xml_parser;
}
@@ -138,7 +139,8 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
+ request->Attach();
Callback callback(request);
system("echo GFEDCBA123 >/tmp/xml_http_request_test_data");
@@ -163,7 +165,7 @@
request->GetResponseBody(&str, &size));
ASSERT_STREQ("GFEDCBA123\n", str);
ASSERT_EQ(11u, size);
- delete request;
+ request->Detach();
delete xml_parser;
}
@@ -272,7 +274,8 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
+ request->Attach();
pthread_t thread;
bool async = false;
@@ -329,7 +332,7 @@
pthread_join(thread, NULL);
ASSERT_TRUE(server_thread_succeeded);
- delete request;
+ request->Detach();
delete xml_parser;
}
@@ -337,7 +340,8 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
+ request->Attach();
pthread_t thread;
bool async = true;
@@ -434,7 +438,7 @@
pthread_join(thread, NULL);
ASSERT_TRUE(server_thread_succeeded);
- delete request;
+ request->Detach();
delete xml_parser;
}
@@ -442,7 +446,8 @@
NativeMainLoop main_loop;
XMLParserInterface *xml_parser = CreateXMLParser();
XMLHttpRequestInterface *request =
- ggadget::CreateXMLHttpRequest(&main_loop, NULL, xml_parser);
+ ggadget::CreateXMLHttpRequest(&main_loop, xml_parser);
+ request->Attach();
Callback callback(request);
@@ -464,7 +469,7 @@
ASSERT_TRUE(dom);
ASSERT_STREQ("\xE6\xB1\x89\xE5\xAD\x97",
dom->GetDocumentElement()->GetTextContent().c_str());
- delete request;
+ request->Detach();
delete xml_parser;
}
=== ggadget/tests/scriptables.cc
==================================================================
--- ggadget/tests/scriptables.cc (revision 654)
+++ ggadget/tests/scriptables.cc (revision 655)
@@ -53,7 +53,7 @@
RegisterProperty("JSON",
NewSlot(this, &TestScriptable1::GetJSON),
NewSlot(this, &TestScriptable1::SetJSON));
- // This signal is only for test, no relation to ConnectToOndeleteSignal.
+ // This signal is only for test, no relation to ConnectOnReferenceChange.
RegisterSignal("my_ondelete", &my_ondelete_signal_);
RegisterSimpleProperty("EnumSimple", &enum_property_);
RegisterStringEnumProperty("EnumString",
=== ggadget/tests/scriptables.h
==================================================================
--- ggadget/tests/scriptables.h (revision 654)
+++ ggadget/tests/scriptables.h (revision 655)
@@ -74,7 +74,7 @@
g_buffer = json.value;
}
- // This signal is only for test, no relation to ConnectToOndeleteSignal.
+ // This signal is only for test, no relation to ConnectOnReferenceChange.
// Place this signal declaration here for testing.
Signal0<void> my_ondelete_signal_;
=== ggadget/tests/scriptable_helper_test.cc
==================================================================
--- ggadget/tests/scriptable_helper_test.cc (revision 654)
+++ ggadget/tests/scriptable_helper_test.cc (revision 655)
@@ -93,8 +93,8 @@
EXPECT_STREQ("Destruct\n", g_buffer.c_str());
}
-void TestOnDelete() {
- AppendBuffer("TestOnDelete\n");
+void TestOnRefChange(int, int) {
+ AppendBuffer("TestRefChange\n");
}
void TestOnDeleteAsEventSink() {
@@ -104,10 +104,10 @@
TEST(scriptable_helper, TestOnDelete) {
TestScriptable1 *scriptable = new TestScriptable1();
ASSERT_STREQ("", g_buffer.c_str());
- ASSERT_TRUE(scriptable->ConnectToOnDeleteSignal(NewSlot(TestOnDelete)));
+ ASSERT_TRUE(scriptable->ConnectOnReferenceChange(NewSlot(TestOnRefChange)));
scriptable->SetProperty(-7, Variant(NewSlot(TestOnDeleteAsEventSink)));
delete scriptable;
- EXPECT_STREQ("TestOnDeleteAsEventSink\nDestruct\nTestOnDelete\n",
+ EXPECT_STREQ("TestOnDeleteAsEventSink\nDestruct\nTestRefChange\n",
g_buffer.c_str());
}
=== ggadget/gadget_consts.h
==================================================================
--- ggadget/gadget_consts.h (revision 654)
+++ ggadget/gadget_consts.h (revision 655)
@@ -124,7 +124,8 @@
const char kCheckBoxCheckedOverImage[] = "resource://checkbox_checked_over.png";
const char kCommonJS[] = "resource://common.js";
-const char kDetailsView[] = "resource://details_view.xml";
+const char kTextDetailsView[] = "resource://text_details_view.xml";
+const char kHTMLDetailsView[] = "resource://html_details_view.xml";
} // namespace ggadget
=== ggadget/smjs/js_script_context.cc
==================================================================
--- ggadget/smjs/js_script_context.cc (revision 654)
+++ ggadget/smjs/js_script_context.cc (revision 655)
@@ -305,6 +305,7 @@
bool JSScriptContext::RegisterClass(const char *name, Slot *constructor) {
ASSERT(constructor);
ASSERT(constructor->GetReturnType() == Variant::TYPE_SCRIPTABLE);
+ ASSERT_M(JS_GetGlobalObject(context_), ("Global object should be set first"));
JSClassWithNativeCtor *cls = new JSClassWithNativeCtor(name, constructor);
if (!JS_InitClass(context_, JS_GetGlobalObject(context_), NULL,
@@ -319,34 +320,6 @@
return true;
}
-void JSScriptContext::LockObject(ScriptableInterface *object,
- const char *name) {
- ASSERT(object);
- NativeJSWrapperMap::const_iterator it = native_js_wrapper_map_.find(object);
- if (it == native_js_wrapper_map_.end()) {
- DLOG("Can't lock %p(CLASS_ID=%jx) not attached to JavaScript",
- object, object->GetClassId());
- } else {
- DLOG("Lock: policy=%d jsobj=%p wrapper=%p scriptable=%p",
- it->second->ownership_policy(), it->second->js_object(),
- it->second, it->second->scriptable());
- JS_AddNamedRoot(context_, &(it->second->js_object()), name);
- }
-}
-
-void JSScriptContext::UnlockObject(ScriptableInterface *object) {
- ASSERT(object);
- NativeJSWrapperMap::const_iterator it = native_js_wrapper_map_.find(object);
- if (it == native_js_wrapper_map_.end()) {
- DLOG("Can't unlock %p not attached to JavaScript", object);
- } else {
- DLOG("Unlock: policy=%d jsobj=%p wrapper=%p scriptable=%p",
- it->second->ownership_policy(), it->second->js_object(),
- it->second, it->second->scriptable());
- JS_RemoveRoot(context_, &(it->second->js_object()));
- }
-}
-
bool JSScriptContext::AssignFromContext(ScriptableInterface *dest_object,
const char *dest_object_expr,
const char *dest_property,
@@ -355,65 +328,87 @@
const char *src_expr) {
ASSERT(src_context);
ASSERT(dest_property);
- ASSERT(src_expr);
- JSScriptContext *src_js_context = down_cast<JSScriptContext *>(src_context);
+ AutoLocalRootScope scope(context_);
- JSObject *dest_js_object;
- if (dest_object) {
- NativeJSWrapperMap::const_iterator it =
- native_js_wrapper_map_.find(dest_object);
- if (it == native_js_wrapper_map_.end())
- return false;
- dest_js_object = it->second->js_object();
- } else {
- dest_js_object = JS_GetGlobalObject(context_);
+ jsval dest_val;
+ if (!EvaluateToJSVal(dest_object, dest_object_expr, &dest_val) ||
+ !JSVAL_IS_OBJECT(dest_val) || JSVAL_IS_NULL(dest_val)) {
+ DLOG("Expression %s doesn't evaluate to a non-null object",
+ dest_object_expr);
+ return false;
}
+ JSObject *dest_js_object = JSVAL_TO_OBJECT(dest_val);
- if (dest_object_expr && *dest_object_expr) {
- UTF16String utf16_dest_object_expr;
- ConvertStringUTF8ToUTF16(dest_object_expr, strlen(dest_object_expr),
- &utf16_dest_object_expr);
- jsval rval;
- if (!JS_EvaluateUCScript(context_, dest_js_object,
- utf16_dest_object_expr.c_str(),
- utf16_dest_object_expr.size(),
- "", 1, &rval)) {
- DLOG("Failed to evaluate dest_object_expr %s against JSObject %p",
- dest_object_expr, dest_js_object);
- return false;
- }
- if (!JSVAL_IS_OBJECT(rval) || JSVAL_IS_NULL(rval)) {
- DLOG("Expression %s doesn't evaluate to a non-null object",
- dest_object_expr);
- return false;
- }
+ jsval src_val;
+ JSScriptContext *src_js_context = down_cast<JSScriptContext *>(src_context);
+ AutoLocalRootScope scope1(src_js_context->context_);
+ if (!src_js_context->EvaluateToJSVal(src_object, src_expr, &src_val))
+ return false;
- dest_js_object = JSVAL_TO_OBJECT(rval);
- }
+ return JS_SetProperty(context_, dest_js_object, dest_property, &src_val);
+}
- JSObject *src_js_object;
- if (src_object) {
- NativeJSWrapperMap::const_iterator it =
- src_js_context->native_js_wrapper_map_.find(src_object);
- if (it == src_js_context->native_js_wrapper_map_.end())
- return false;
- src_js_object = it->second->js_object();
- } else {
- src_js_object = JS_GetGlobalObject(src_js_context->context_);
+bool JSScriptContext::AssignFromNative(ScriptableInterface *object,
+ const char *object_expr,
+ const char *property,
+ const Variant &value) {
+ ASSERT(property);
+ AutoLocalRootScope scope(context_);
+
+ jsval dest_val;
+ if (!EvaluateToJSVal(object, object_expr, &dest_val) ||
+ !JSVAL_IS_OBJECT(dest_val) || JSVAL_IS_NULL(dest_val)) {
+ DLOG("Expression %s doesn't evaluate to a non-null object", object_expr);
+ return false;
}
+ JSObject *js_object = JSVAL_TO_OBJECT(dest_val);
- UTF16String utf16_src_expr;
- ConvertStringUTF8ToUTF16(src_expr, strlen(src_expr), &utf16_src_expr);
jsval src_val;
- if (!JS_EvaluateUCScript(src_js_context->context_, src_js_object,
- utf16_src_expr.c_str(), utf16_src_expr.size(),
- "", 1, &src_val)) {
- DLOG("Failed to evaluate src_expr %s against JSObject %p",
- src_expr, src_js_object);
+ if (!ConvertNativeToJS(context_, value, &src_val))
return false;
+ return JS_SetProperty(context_, js_object, property, &src_val);
+}
+
+Variant JSScriptContext::Evaluate(ScriptableInterface *object,
+ const char *expr) {
+ Variant result;
+ jsval js_val;
+ if (EvaluateToJSVal(object, expr, &js_val)) {
+ ConvertJSToNativeVariant(context_, js_val, &result);
+ // Just left result in void state on any error.
}
+ return result;
+}
- return JS_SetProperty(context_, dest_js_object, dest_property, &src_val);
+JSBool JSScriptContext::EvaluateToJSVal(ScriptableInterface *object,
+ const char *expr, jsval *result) {
+ *result = JSVAL_VOID;
+ JSObject *js_object;
+ if (object) {
+ NativeJSWrapperMap::const_iterator it = native_js_wrapper_map_.find(object);
+ if (it == native_js_wrapper_map_.end()) {
+ DLOG("Object %p hasn't a wrapper in JS", object);
+ return JS_FALSE;
+ }
+ js_object = it->second->js_object();
+ } else {
+ js_object = JS_GetGlobalObject(context_);
+ }
+
+ if (expr && *expr) {
+ UTF16String utf16_expr;
+ ConvertStringUTF8ToUTF16(expr, strlen(expr), &utf16_expr);
+ if (!JS_EvaluateUCScript(context_, js_object,
+ utf16_expr.c_str(), utf16_expr.size(),
+ expr, 1, result)) {
+ DLOG("Failed to evaluate dest_object_expr %s against JSObject %p",
+ expr, js_object);
+ return JS_FALSE;
+ }
+ } else {
+ *result = OBJECT_TO_JSVAL(js_object);
+ }
+ return JS_TRUE;
}
} // namespace smjs
=== ggadget/smjs/js_script_context.h
==================================================================
--- ggadget/smjs/js_script_context.h (revision 654)
+++ ggadget/smjs/js_script_context.h (revision 655)
@@ -112,10 +112,6 @@
virtual bool SetGlobalObject(ScriptableInterface *global_object);
/** @see ScriptContextInterface::RegisterClass() */
virtual bool RegisterClass(const char *name, Slot *constructor);
- /** @see ScriptContextInterface::LockObject() */
- virtual void LockObject(ScriptableInterface *object, const char *name);
- /** @see ScriptContextInterface::UnlockObject() */
- virtual void UnlockObject(ScriptableInterface *object);
/** @see ScriptContextInterface::AssignFromContext() */
virtual bool AssignFromContext(ScriptableInterface *dest_object,
const char *dest_object_expr,
@@ -123,6 +119,13 @@
ScriptContextInterface *src_context,
ScriptableInterface *src_object,
const char *src_expr);
+ /** @see ScriptContextInterface::AssignFromContext() */
+ virtual bool AssignFromNative(ScriptableInterface *object,
+ const char *object_expr,
+ const char *property,
+ const Variant &value);
+ /** @see ScriptContextInterface::Evaluate() */
+ virtual Variant Evaluate(ScriptableInterface *object, const char *expr);
private:
DISALLOW_EVIL_CONSTRUCTORS(JSScriptContext);
@@ -150,6 +153,9 @@
static JSBool ConstructObject(JSContext *cx, JSObject *obj,
uintN argc, jsval *argv, jsval *rval);
+ JSBool EvaluateToJSVal(ScriptableInterface *object, const char *expr,
+ jsval *result);
+
class JSClassWithNativeCtor {
public:
JSClassWithNativeCtor(const char *name, Slot *constructor);
=== ggadget/smjs/converter.cc
==================================================================
--- ggadget/smjs/converter.cc (revision 654)
+++ ggadget/smjs/converter.cc (revision 655)
@@ -283,8 +283,8 @@
return JS_TRUE;
}
-JSBool ConvertJSToNativeVariant(JSContext *cx, NativeJSWrapper *owner,
- jsval js_val, Variant *native_val) {
+JSBool ConvertJSToNativeVariant(JSContext *cx, jsval js_val,
+ Variant *native_val) {
if (JSVAL_IS_VOID(js_val) || JSVAL_IS_NULL(js_val))
return ConvertJSToNativeVoid(cx, js_val, native_val);
if (JSVAL_IS_BOOLEAN(js_val))
@@ -329,7 +329,7 @@
case Variant::TYPE_DATE:
return ConvertJSToNativeDate(cx, js_val, native_val);
case Variant::TYPE_VARIANT:
- return ConvertJSToNativeVariant(cx, owner, js_val, native_val);
+ return ConvertJSToNativeVariant(cx, js_val, native_val);
default:
return JS_FALSE;
}
@@ -418,7 +418,7 @@
Variant(arg_types[i]), argv[i],
&(*params)[i]);
} else {
- result = ConvertJSToNativeVariant(cx, owner, argv[i], &(*params)[i]);
+ result = ConvertJSToNativeVariant(cx, argv[i], &(*params)[i]);
}
if (!result) {
for (uintN j = 0; j < i; j++)
=== ggadget/smjs/converter.h
==================================================================
--- ggadget/smjs/converter.h (revision 654)
+++ ggadget/smjs/converter.h (revision 655)
@@ -44,14 +44,12 @@
/**
* Converts a @c jsval to a @c Variant depending source @c jsval type.
* @param cx JavaScript context.
- * @param owner the related JavaScript object wrapper in which the owner of
- * the converted value is wrapped.
* @param js_val source @c jsval value.
* @param[out] native_val result @c Variant value.
* @return @c JS_TRUE if succeeds.
*/
-JSBool ConvertJSToNativeVariant(JSContext *cx, NativeJSWrapper *owner,
- jsval js_val, Variant *native_val);
+JSBool ConvertJSToNativeVariant(JSContext *cx, jsval js_val,
+ Variant *native_val);
/**
* Frees a native value that was created by @c ConvertJSToNative(),
=== ggadget/smjs/js_native_wrapper.cc
==================================================================
--- ggadget/smjs/js_native_wrapper.cc (revision 654)
+++ ggadget/smjs/js_native_wrapper.cc (revision 655)
@@ -36,7 +36,8 @@
JSNativeWrapper::JSNativeWrapper(JSContext *js_context, JSObject *js_object)
: ref_count_(0),
js_context_(js_context),
- js_object_(js_object) {
+ js_object_(js_object),
+ name_(PrintJSValue(js_context, OBJECT_TO_JSVAL(js_object))) {
SetDynamicPropertyHandler(NewSlot(this, &JSNativeWrapper::GetProperty),
NewSlot(this, &JSNativeWrapper::SetProperty));
SetArrayHandler(NewSlot(this, &JSNativeWrapper::GetElement),
@@ -65,7 +66,7 @@
if (GetRefCount() > 0) {
// There must be a new native reference, let JavaScript know it by adding
// the object to root.
- JS_AddRoot(js_context_, &js_object_);
+ JS_AddNamedRoot(js_context_, &js_object_, name_.c_str());
}
return ScriptableHelperOwnershipShared::Attach();
}
@@ -133,7 +134,7 @@
Variant result;
jsval rval;
if (JS_GetProperty(js_context_, js_object_, name, &rval) &&
- !ConvertJSToNativeVariant(js_context_, NULL, rval, &result)) {
+ !ConvertJSToNativeVariant(js_context_, rval, &result)) {
JS_ReportError(js_context_,
"Failed to convert JS property %s value(%s) to native.",
name, PrintJSValue(js_context_, rval).c_str());
@@ -156,7 +157,7 @@
Variant result;
jsval rval;
if (JS_GetElement(js_context_, js_object_, index, &rval) &&
- !ConvertJSToNativeVariant(js_context_, NULL, rval, &result)) {
+ !ConvertJSToNativeVariant(js_context_, rval, &result)) {
JS_ReportError(js_context_,
"Failed to convert JS property %d value(%s) to native.",
index, PrintJSValue(js_context_, rval).c_str());
=== ggadget/smjs/native_js_wrapper.cc
==================================================================
--- ggadget/smjs/native_js_wrapper.cc (revision 654)
+++ ggadget/smjs/native_js_wrapper.cc (revision 655)
@@ -18,6 +18,7 @@
#include <ggadget/scriptable_interface.h>
#include <ggadget/signals.h>
#include <ggadget/slot.h>
+#include <ggadget/string_utils.h>
#include "native_js_wrapper.h"
#include "converter.h"
#include "js_function_slot.h"
@@ -53,7 +54,7 @@
js_context_(js_context),
js_object_(js_object),
scriptable_(NULL),
- ondelete_connection_(NULL),
+ on_reference_change_connection_(NULL),
ownership_policy_(ScriptableInterface::NATIVE_OWNED) {
ASSERT(js_object);
@@ -67,9 +68,8 @@
NativeJSWrapper::~NativeJSWrapper() {
if (!detached_) {
#ifdef DEBUG_JS_WRAPPER_MEMORY
- DLOG("Delete: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%p"
- "(CLASS_ID=%jx)", js_context_, ownership_policy_, js_object_, this,
- scriptable_, scriptable_->GetClassId());
+ DLOG("Delete: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%s",
+ js_context_, ownership_policy_, js_object_, this, name_.c_str());
#endif
DetachJS();
}
@@ -78,21 +78,23 @@
void NativeJSWrapper::Wrap(ScriptableInterface *scriptable) {
ASSERT(scriptable && !scriptable_);
scriptable_ = scriptable;
- // Connect the "ondelete" callback.
- ondelete_connection_ = scriptable->ConnectToOnDeleteSignal(
- NewSlot(this, &NativeJSWrapper::OnDelete));
+ name_ = StringPrintf("%p(CLASS_ID=%jx)",
+ scriptable, scriptable->GetClassId());
+
+ on_reference_change_connection_ = scriptable->ConnectOnReferenceChange(
+ NewSlot(this, &NativeJSWrapper::OnReferenceChange));
ownership_policy_ = scriptable->Attach();
// If the object is native owned, the script side should not delete the
// object unless the native side tells it to do.
if (ownership_policy_ == ScriptableInterface::NATIVE_OWNED ||
- ownership_policy_ == ScriptableInterface::NATIVE_PERMANENT)
- JS_AddRoot(js_context_, &js_object_);
-
+ ownership_policy_ == ScriptableInterface::NATIVE_PERMANENT) {
+ JS_AddNamedRoot(js_context_, &js_object_, name_.c_str());
+ }
+
#ifdef DEBUG_JS_WRAPPER_MEMORY
- DLOG("Wrap: cx=%p policy=%d jsobj=%p wrapper=%p scr=%p(CLASS_ID=%jx)",
- js_context_, ownership_policy_, js_object_, this,
- scriptable_, scriptable_->GetClassId());
+ DLOG("Wrap: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%s",
+ js_context_, ownership_policy_, js_object_, this, name_.c_str());
#ifdef DEBUG_FORCE_GC
// This GC forces many hidden memory allocation errors to expose.
DLOG("ForceGC");
@@ -249,8 +251,8 @@
NativeJSWrapper *wrapper = GetWrapperFromJS(cx, obj);
if (wrapper) {
#ifdef DEBUG_JS_WRAPPER_MEMORY
- DLOG("Finalize: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%p",
- cx, wrapper->ownership_policy_, obj, wrapper, wrapper->scriptable_);
+ DLOG("Finalize: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%s",
+ cx, wrapper->ownership_policy_, obj, wrapper, wrapper->name_.c_str());
#endif
if (!wrapper->detached_) {
@@ -277,40 +279,53 @@
void NativeJSWrapper::DetachJS() {
#ifdef DEBUG_JS_WRAPPER_MEMORY
- DLOG("DetachJS: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%p",
- js_context_, ownership_policy_, js_object_, this, scriptable_);
+ DLOG("DetachJS: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%s",
+ js_context_, ownership_policy_, js_object_, this, name_.c_str());
#endif
- ondelete_connection_->Disconnect();
+ on_reference_change_connection_->Disconnect();
scriptable_->Detach();
detached_ = true;
- if (ownership_policy_ == ScriptableInterface::NATIVE_OWNED ||
- ownership_policy_ == ScriptableInterface::NATIVE_PERMANENT)
- JS_RemoveRoot(js_context_, &js_object_);
+ JS_RemoveRoot(js_context_, &js_object_);
}
-void NativeJSWrapper::OnDelete() {
+void NativeJSWrapper::OnReferenceChange(int ref_count, int change) {
#ifdef DEBUG_JS_WRAPPER_MEMORY
- DLOG("OnDelete: cx=%p policy=%d jsobj=%p wrapper=%p scriptable=%p",
- js_context_, ownership_policy_, js_object_, this, scriptable_);
+ DLOG("OnReferenceChange(%d,%d): cx=%p policy=%d jsobj=%p wrapper=%p "
+ "scriptable=%s", ref_count, change, js_context_,
+ ownership_policy_, js_object_, this, name_.c_str());
#endif
- // As the native side has deleted the object, now the script side can also
- // delete it if there is no other active references.
- DetachJS();
+ if (ref_count == 0 && change == 0) {
+ // As the native side has deleted the object, now the script side can also
+ // delete it if there is no other active references.
+ DetachJS();
+
+ // Remove the wrapper mapping from the context, but leave this wrapper
+ // alive to accept mistaken JavaScript calls gracefully.
+ JSScriptContext::FinalizeNativeJSWrapper(js_context_, this);
- // Remove the wrapper mapping from the context, but leave this wrapper
- // alive to accept mistaken JavaScript calls gracefully.
- JSScriptContext::FinalizeNativeJSWrapper(js_context_, this);
-
#ifdef DEBUG_JS_WRAPPER_MEMORY
#ifdef DEBUG_FORCE_GC
- // This GC forces many hidden memory allocation errors to expose.
- DLOG("ForceGC");
- JS_GC(js_context_);
+ // This GC forces many hidden memory allocation errors to expose.
+ DLOG("ForceGC");
+ JS_GC(js_context_);
#endif
#endif
+ } else {
+ ASSERT(change == 1 || change == -1);
+ if (change == 1 && ref_count > 0) {
+ // There must be at least one native reference, let JavaScript know it
+ // by adding the object to root.
+ JS_AddNamedRoot(js_context_, &js_object_, name_.c_str());
+ }
+ if (change == -1 && ref_count == 2) {
+ // The last native reference is about to be released, let JavaScript know
+ // it by removing the root reference.
+ JS_RemoveRoot(js_context_, &js_object_);
+ }
+ }
}
JSBool NativeJSWrapper::CallSelf(uintN argc, jsval *argv, jsval *rval) {
=== ggadget/smjs/js_native_wrapper.h
==================================================================
--- ggadget/smjs/js_native_wrapper.h (revision 654)
+++ ggadget/smjs/js_native_wrapper.h (revision 655)
@@ -17,6 +17,7 @@
#ifndef GGADGET_SMJS_JS_NATIVE_WRAPPER_H__
#define GGADGET_SMJS_JS_NATIVE_WRAPPER_H__
+#include <string>
#include <jsapi.h>
#include <ggadget/common.h>
#include <ggadget/scriptable_helper.h>
@@ -55,6 +56,7 @@
int ref_count_;
JSContext *js_context_;
JSObject *js_object_;
+ std::string name_;
};
} // namespace smjs
=== ggadget/smjs/native_js_wrapper.h
==================================================================
--- ggadget/smjs/native_js_wrapper.h (revision 654)
+++ ggadget/smjs/native_js_wrapper.h (revision 655)
@@ -18,6 +18,7 @@
#define GGADGET_SMJS_NATIVE_JS_WRAPPER_H__
#include <set>
+#include <string>
#include <jsapi.h>
#include <ggadget/common.h>
#include <ggadget/scriptable_interface.h>
@@ -82,7 +83,7 @@
private:
DISALLOW_EVIL_CONSTRUCTORS(NativeJSWrapper);
- void OnDelete();
+ void OnReferenceChange(int ref_count, int change);
// Callback for invocation of the object itself as a function.
static JSBool CallWrapperSelf(JSContext *cx, JSObject *obj,
@@ -139,7 +140,8 @@
JSContext *js_context_;
JSObject *js_object_;
ScriptableInterface *scriptable_;
- Connection *ondelete_connection_;
+ std::string name_;
+ Connection *on_reference_change_connection_;
ScriptableInterface::OwnershipPolicy ownership_policy_;
typedef std::set<JSFunctionSlot *> JSFunctionSlots;
=== ggadget/scriptable_helper.h
==================================================================
--- ggadget/scriptable_helper.h (revision 654)
+++ ggadget/scriptable_helper.h (revision 655)
@@ -46,9 +46,11 @@
virtual void SetArrayHandler(Slot *getter, Slot *setter) = 0;
virtual void SetDynamicPropertyHandler(Slot *getter, Slot *setter) = 0;
virtual void SetPendingException(ScriptableInterface *exception) = 0;
+ virtual int GetRefCount() = 0;
};
-ScriptableHelperImplInterface *NewScriptableHelperImpl();
+ScriptableHelperImplInterface *NewScriptableHelperImpl(
+ ScriptableInterface::OwnershipPolicy policy);
} // namespace internal
@@ -67,12 +69,10 @@
public:
ScriptableHelper()
- : impl_(internal::NewScriptableHelperImpl()),
- ref_count_(0) {
+ : impl_(internal::NewScriptableHelperImpl(Policy)) {
}
virtual ~ScriptableHelper() {
- ASSERT(ref_count_ == 0);
delete impl_;
}
@@ -224,25 +224,18 @@
}
/** Gets current reference count. */
- int GetRefCount() const { return ref_count_; }
+ int GetRefCount() const { return impl_->GetRefCount(); }
/** @see ScriptableInterface::Attach() */
virtual ScriptableInterface::OwnershipPolicy Attach() {
- if (Policy == ScriptableInterface::OWNERSHIP_SHARED) {
- ASSERT(ref_count_ >= 0);
- ref_count_++;
- }
- return Policy;
+ return impl_->Attach();
}
/** @see ScriptableInterface::Detach() */
virtual bool Detach() {
- if (Policy == ScriptableInterface::OWNERSHIP_SHARED) {
- ASSERT(ref_count_ > 0);
- if (--ref_count_ == 0) {
- delete this;
- return true;
- }
+ if (impl_->Detach()) {
+ delete this;
+ return true;
}
return false;
}
@@ -253,9 +246,9 @@
*/
virtual bool IsStrict() const { return true; }
- /** @see ScriptableInterface::ConnectionToOnDeleteSignal() */
- virtual Connection *ConnectToOnDeleteSignal(Slot0<void> *slot) {
- return impl_->ConnectToOnDeleteSignal(slot);
+ /** @see ScriptableInterface::ConnectionOnReferenceChange() */
+ virtual Connection *ConnectOnReferenceChange(Slot2<void, int, int> *slot) {
+ return impl_->ConnectOnReferenceChange(slot);
}
/** @see ScriptableInterface::GetPropertyInfoByName() */
@@ -302,7 +295,6 @@
DISALLOW_EVIL_CONSTRUCTORS(ScriptableHelper);
internal::ScriptableHelperImplInterface *impl_;
- int ref_count_;
};
typedef ScriptableHelper<ScriptableInterface,
=== ggadget/display_window.cc
==================================================================
--- ggadget/display_window.cc (revision 654)
+++ ggadget/display_window.cc (revision 655)
@@ -38,6 +38,7 @@
static const double kZoomRatio = 1.1;
static const char *kControlBorderColor = "#808080";
static const char *kBackgroundColor = "#FFFFFF";
+static const int kMinComboBoxHeight = 80;
static const int kMaxComboBoxHeight = 150;
class DisplayWindow::Impl {
@@ -233,45 +234,40 @@
return GetText();
}
- void SetListBoxValue(ListBoxElement *listbox, const char *value) {
- ItemElement *item = listbox->FindItemByString(value);
- if (item)
- listbox->SetSelectedItem(item);
+ bool SetListBoxValue(ListBoxElement *listbox, const Variant &value) {
+ std::string value_str;
+ if (value.ConvertToString(&value_str)) {
+ ItemElement *item = listbox->FindItemByString(value_str.c_str());
+ if (item)
+ listbox->SetSelectedItem(item);
+ return true;
+ }
+ return false;
}
void SetValue(const Variant &value) {
- bool invalid = false;
- std::string value_str;
- if (value.ConvertToString(&value_str)) {
- if (element_->IsInstanceOf(ButtonElement::CLASS_ID) ||
- element_->IsInstanceOf(LabelElement::CLASS_ID) ||
- element_->IsInstanceOf(EditElement::CLASS_ID)) {
- SetText(value);
- } else if (element_->IsInstanceOf(ListBoxElement::CLASS_ID)) {
- ListBoxElement *listbox = down_cast<ListBoxElement *>(element_);
- SetListBoxValue(listbox, VariantValue<const char *>()(value));
- } else if (element_->IsInstanceOf(ComboBoxElement::CLASS_ID)) {
- ComboBoxElement *combobox = down_cast<ComboBoxElement *>(element_);
- SetListBoxValue(combobox->GetListBox(),
- VariantValue<const char *>()(value));
- } else {
- invalid = true;
- }
- } else {
+ bool valid = true;
+ if (element_->IsInstanceOf(ButtonElement::CLASS_ID) ||
+ element_->IsInstanceOf(LabelElement::CLASS_ID) ||
+ element_->IsInstanceOf(EditElement::CLASS_ID)) {
+ SetText(value);
+ } else if (element_->IsInstanceOf(ListBoxElement::CLASS_ID)) {
+ valid = SetListBoxValue(down_cast<ListBoxElement *>(element_), value);
+ } else if (element_->IsInstanceOf(ComboBoxElement::CLASS_ID)) {
+ ComboBoxElement *combobox = down_cast<ComboBoxElement *>(element_);
+ valid = SetListBoxValue(combobox->GetListBox(), value);
+ } else if (element_->IsInstanceOf(CheckBoxElement::CLASS_ID)) {
bool value_bool;
if (value.ConvertToBool(&value_bool)) {
- if (element_->IsInstanceOf(CheckBoxElement::CLASS_ID)) {
- // For check box it is a boolean idicating the check state.
- CheckBoxElement *checkbox = down_cast<CheckBoxElement *>(element_);
- checkbox->SetValue(VariantValue<bool>()(value));
- } else {
- invalid = true;
- }
+ CheckBoxElement *checkbox = down_cast<CheckBoxElement *>(element_);
+ checkbox->SetValue(value_bool);
} else {
- invalid = true;
- }
+ valid = false;
+ }
+ } else {
+ valid = false;
}
- if (invalid) {
+ if (!valid) {
LOG("Invalid type of value(%s) for control %s",
value.Print().c_str(), element_->GetName().c_str());
}
@@ -424,7 +420,9 @@
div->SetPixelHeight(kListItemHeight + 2);
// Because our combobox can't pop out of the dialog box, we must
// limit the height of the combobox
- if (height > kMaxComboBoxHeight)
+ if (height < kMinComboBoxHeight)
+ height = kMinComboBoxHeight;
+ else if (height > kMaxComboBoxHeight)
height = kMaxComboBoxHeight;
} else {
div->SetPixelHeight(height);
=== ggadget/scriptable_options.cc
==================================================================
--- ggadget/scriptable_options.cc (revision 654)
+++ ggadget/scriptable_options.cc (revision 655)
@@ -69,7 +69,7 @@
ScriptableOptions::ScriptableOptions(OptionsInterface *options,
bool raw_objects)
- : impl_(NULL) {
+ : impl_(new Impl(options)) {
RegisterProperty("count",
NewSlot(options, &OptionsInterface::GetCount), NULL);
RegisterMethod("exists", NewSlot(options, &OptionsInterface::Exists));
@@ -86,7 +86,6 @@
NewSlot(options, &OptionsInterface::PutDefaultValue));
RegisterMethod("putValue", NewSlot(options, &OptionsInterface::PutValue));
} else {
- impl_ = new Impl(options);
// Partly support the deprecated "item" property.
RegisterMethod("item", NewSlot(impl_, &Impl::OldGetValue));
// Partly support the deprecated "defaultValue" property.
@@ -114,4 +113,12 @@
delete impl_;
}
+const OptionsInterface *ScriptableOptions::GetOptions() const {
+ return impl_->options_;
+}
+
+OptionsInterface *ScriptableOptions::GetOptions() {
+ return impl_->options_;
+}
+
} // namespace ggadget
=== ggadget/scriptable_options.h
==================================================================
--- ggadget/scriptable_options.h (revision 654)
+++ ggadget/scriptable_options.h (revision 655)
@@ -23,7 +23,7 @@
class OptionsInterface;
-class ScriptableOptions : public ScriptableHelper<ScriptableInterface> {
+class ScriptableOptions : public ScriptableHelperNativePermanent {
public:
DEFINE_CLASS_ID(0x1a7bc9215ef74743, ScriptableInterface)
@@ -37,6 +37,9 @@
virtual OwnershipPolicy Attach() { return NATIVE_PERMANENT; }
+ const OptionsInterface *GetOptions() const;
+ OptionsInterface *GetOptions();
+
private:
DISALLOW_EVIL_CONSTRUCTORS(ScriptableOptions);
class Impl;
=== ggadget/scriptable_interface.h
==================================================================
--- ggadget/scriptable_interface.h (revision 654)
+++ ggadget/scriptable_interface.h (revision 655)
@@ -20,14 +20,11 @@
#include <limits.h>
#include <stdint.h>
#include <ggadget/variant.h>
+#include <ggadget/slot.h>
namespace ggadget {
class Connection;
-template <typename R> class Slot0;
-template <typename R, typename P1, typename P2> class Slot2;
-template <typename R, typename P1, typename P2, typename P3, typename P4>
-class Slot4;
/**
* Object interface that can be called from script languages.
@@ -142,12 +139,18 @@
virtual bool IsStrict() const = 0;
/**
- * Connect a callback @c Slot to the "ondelete" signal.
- * @param slot the callback @c Slot to be called when the @c Scriptable
- * object is to be deleted.
- * @return the connected @c Connection or @c NULL if fails.
+ * Connect a callback which will be called when @c Attach(), @c Detach() is
+ * called or the object is about to be deleted.
+ * @param slot the callback. The parameters of the slot are:
+ * - the reference count before change; or 0 if the object is about to be
+ * deleted;
+ * - 1 or -1 indicating whether the reference count is about to be
+ * increased or decreased; or 0 if the object is about to be deleted.
+ * For native owned object, the slot will only be called when the object
+ * is about to be deleted, and the two parameters are always 0.
+ * @return the connected @c Connection.
*/
- virtual Connection *ConnectToOnDeleteSignal(Slot0<void> *slot) = 0;
+ virtual Connection *ConnectOnReferenceChange(Slot2<void, int, int> *slot) = 0;
/**
* Get the info of a property by its name.
=== ggadget/details_view.cc
==================================================================
--- ggadget/details_view.cc (revision 654)
+++ ggadget/details_view.cc (revision 655)
@@ -141,12 +141,12 @@
impl_->is_view_ = is_view;
}
-const OptionsInterface *DetailsView::GetDetailsViewData() const {
- return &impl_->data_;
+const ScriptableOptions *DetailsView::GetDetailsViewData() const {
+ return &impl_->scriptable_data_;
}
-OptionsInterface *DetailsView::GetDetailsViewData() {
- return &impl_->data_;
+ScriptableOptions *DetailsView::GetDetailsViewData() {
+ return &impl_->scriptable_data_;
}
ScriptableInterface *DetailsView::GetExternalObject() const {
=== ggadget/gadget.cc
==================================================================
--- ggadget/gadget.cc (revision 654)
+++ ggadget/gadget.cc (revision 655)
@@ -86,7 +86,7 @@
RegisterMethod("RemoveMe",
NewSlot(gadget_host_, &GadgetHostInterface::RemoveMe));
RegisterMethod("ShowDetailsView",
- NewSlot(gadget_impl, &Impl::ScriptShowDetailsView));
+ NewSlot(gadget_impl, &Impl::DelayedShowDetailsView));
RegisterMethod("CloseDetailsView",
NewSlot(gadget_impl, &Impl::DelayedCloseDetailsView));
RegisterMethod("ShowOptionsDialog",
@@ -232,9 +232,9 @@
main_view_host_(host->NewViewHost(GadgetHostInterface::VIEW_MAIN,
&gadget_global_prototype_)),
plugin_(this),
- details_view_host_(NULL),
+ details_view_host_(NULL), details_view_(NULL),
has_options_xml_(false),
- close_details_view_timer_(0) {
+ close_details_view_timer_(0), show_details_view_timer_(0) {
RegisterConstant("debug", &debug_);
RegisterConstant("storage", &storage_);
}
@@ -244,6 +244,10 @@
host_->GetMainLoop()->RemoveWatch(close_details_view_timer_);
close_details_view_timer_ = 0;
}
+ if (show_details_view_timer_ != 0) {
+ host_->GetMainLoop()->RemoveWatch(show_details_view_timer_);
+ show_details_view_timer_ = 0;
+ }
// unload any fonts
for (GadgetStringMap::const_iterator i = manifest_info_map_.begin();
@@ -334,24 +338,28 @@
bool ShowDetailsView(DetailsView *details_view, const char *title, int flags,
Slot1<void, int> *feedback_handler) {
+ details_view->Attach();
CloseDetailsView();
+ details_view_ = details_view;
details_view_host_ = host_->NewViewHost(GadgetHostInterface::VIEW_DETAILS,
&gadget_global_prototype_);
- OptionsInterface *data = details_view->GetDetailsViewData();
+ ScriptContextInterface *script_context =
+ details_view_host_->GetScriptContext();
+ ScriptableOptions *scriptable_data = details_view->GetDetailsViewData();
+ OptionsInterface *data = scriptable_data->GetOptions();
// Set up the detailsViewData variable in the opened details view.
- details_view_host_->GetScriptContext()->AssignFromContext(
- NULL, "", "detailsViewData",
- main_view_host_->GetScriptContext(), details_view, "detailsViewData");
+ script_context->AssignFromNative(NULL, "", "detailsViewData",
+ Variant(scriptable_data));
std::string xml_file;
if (details_view->ContentIsHTML() || !details_view->ContentIsView()) {
- xml_file = kDetailsView;
if (details_view->ContentIsHTML()) {
- details_view_host_->GetScriptContext()->AssignFromContext(
- NULL, "", "external",
- main_view_host_->GetScriptContext(), details_view, "external");
+ xml_file = kHTMLDetailsView;
+ details_view_host_->GetScriptContext()->AssignFromNative(
+ NULL, "", "external", Variant(details_view->GetExternalObject()));
data->PutValue("contentType", Variant("text/html"));
} else {
+ xml_file = kTextDetailsView;
data->PutValue("contentType", Variant("text/plain"));
}
data->PutValue("content", Variant(details_view->GetText()));
@@ -363,17 +371,50 @@
LOG("Failed to load details view from %s", xml_file.c_str());
delete details_view_host_;
details_view_host_ = NULL;
+ details_view->Detach();
return false;
}
details_view_host_->ShowInDetailsView(title, flags, feedback_handler);
return true;
}
- void ScriptShowDetailsView(DetailsView *details_view,
- const char *title, int flags,
- Slot *callback) {
- ShowDetailsView(details_view, title, flags,
- callback ? new SlotProxy1<void, int>(callback) : NULL);
+ class ShowDetailsViewCallback : public WatchCallbackInterface {
+ public:
+ ShowDetailsViewCallback(Impl *impl, DetailsView *details_view,
+ const char *title, int flags,
+ Slot1<void, int> *callback)
+ : impl_(impl), details_view_(details_view),
+ title_(title), flags_(flags), callback_(callback) {
+ details_view_->Attach();
+ }
+ virtual bool Call(MainLoopInterface *main_loop, int watch_id) {
+ impl_->ShowDetailsView(details_view_, title_.c_str(), flags_, callback_);
+ impl_->show_details_view_timer_ = 0;
+ callback_ = NULL;
+ return false;
+ }
+ virtual void OnRemove(MainLoopInterface *main_loop, int watch_id) {
+ details_view_->Detach();
+ delete callback_;
+ }
+ private:
+ Impl *impl_;
+ DetailsView *details_view_;
+ std::string title_;
+ int flags_;
+ Slot1<void, int> *callback_;
+ };
+
+ // Show the details view in the next event loop.
+ void DelayedShowDetailsView(DetailsView *details_view,
+ const char *title, int flags,
+ Slot *callback) {
+ if (show_details_view_timer_ == 0) {
+ show_details_view_timer_ = host_->GetMainLoop()->AddTimeoutWatch(0,
+ new ShowDetailsViewCallback(
+ this, details_view, title, flags,
+ callback ? new SlotProxy1<void, int>(callback) : NULL));
+ }
}
void CloseDetailsView() {
@@ -381,6 +422,7 @@
details_view_host_->CloseDetailsView();
delete details_view_host_;
details_view_host_ = NULL;
+ details_view_->Detach();
}
}
@@ -464,9 +506,10 @@
ViewHostInterface *main_view_host_;
Plugin plugin_;
ViewHostInterface *details_view_host_;
+ DetailsView *details_view_;
GadgetStringMap manifest_info_map_;
bool has_options_xml_;
- int close_details_view_timer_;
+ int close_details_view_timer_, show_details_view_timer_;
};
Gadget::Gadget(GadgetHostInterface *host)
=== ggadget/gtk/gtk_view_host.cc
==================================================================
--- ggadget/gtk/gtk_view_host.cc (revision 654)
+++ ggadget/gtk/gtk_view_host.cc (revision 655)
@@ -145,7 +145,7 @@
}
XMLHttpRequestInterface *GtkViewHost::NewXMLHttpRequest() {
- return CreateXMLHttpRequest(gadget_host_->GetMainLoop(), script_context_,
+ return CreateXMLHttpRequest(gadget_host_->GetMainLoop(),
gadget_host_->GetXMLParser());
}
=== ggadget/gtk/gadget_view_widget.cc
==================================================================
--- ggadget/gtk/gadget_view_widget.cc (revision 654)
+++ ggadget/gtk/gadget_view_widget.cc (revision 655)
@@ -38,7 +38,7 @@
using ggadget::gtk::GtkMenuImpl;
struct _GadgetViewWidget {
- GtkFixed fixed;
+ GtkFixed parent_widget;
ggadget::gtk::GtkViewHost *host;
ggadget::ViewInterface *view;
@@ -61,12 +61,11 @@
void (* gadgetviewwidget)(GadgetViewWidget *gvw);
};
+G_DEFINE_TYPE(GadgetViewWidget, GadgetViewWidget, GTK_TYPE_FIXED)
-static GtkWidgetClass *parent_class = NULL;
-
static void GadgetViewWidget_destroy(GtkObject* object) {
- if (GTK_OBJECT_CLASS(parent_class)->destroy) {
- (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
+ if (GTK_OBJECT_CLASS(GadgetViewWidget_parent_class)->destroy) {
+ (*GTK_OBJECT_CLASS(GadgetViewWidget_parent_class)->destroy)(object);
}
GadgetViewWidget *gvw = GADGETVIEWWIDGET(object);
@@ -77,8 +76,8 @@
}
static void GadgetViewWidget_realize(GtkWidget *widget) {
- if (GTK_WIDGET_CLASS(parent_class)->realize) {
- (*GTK_WIDGET_CLASS(parent_class)->realize)(widget);
+ if (GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->realize) {
+ (*GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->realize)(widget);
}
GadgetViewWidget *gvw = GADGETVIEWWIDGET(widget);
@@ -87,8 +86,8 @@
}
static void GadgetViewWidget_unrealize(GtkWidget *widget) {
- if (GTK_WIDGET_CLASS(parent_class)->unrealize) {
- (*GTK_WIDGET_CLASS(parent_class)->unrealize)(widget);
+ if (GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->unrealize) {
+ (*GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->unrealize)(widget);
}
}
@@ -99,20 +98,24 @@
requisition->height = (gint)(gvw->view->GetHeight() * gvw->zoom);
}
-static gboolean GadgetViewWidget_configure(GtkWidget *widget,
- GdkEventConfigure *event) {
+static void GadgetViewWidget_size_allocate(GtkWidget *widget,
+ GtkAllocation *allocation) {
+ if (GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->size_allocate) {
+ (*GTK_WIDGET_CLASS(GadgetViewWidget_parent_class)->size_allocate)(
+ widget, allocation);
+ }
GadgetViewWidget *gvw = GADGETVIEWWIDGET(widget);
// Capture only changes to width, height, and not x, y.
- if (event->width != gvw->widget_width ||
- event->height != gvw->widget_height) {
- gvw->widget_width = event->width;
- gvw->widget_height = event->height;
+ if (allocation->width != gvw->widget_width ||
+ allocation->height != gvw->widget_height) {
+ gvw->widget_width = allocation->width;
+ gvw->widget_height = allocation->height;
ViewInterface::ResizableMode mode = gvw->view->GetResizable();
- DLOG("configure %d %d", event->width, event->height);
+ DLOG("allocate %d %d", allocation->width, allocation->height);
if (mode == ViewInterface::RESIZABLE_TRUE) {
- int width = static_cast<int>(event->width / gvw->zoom);
- int height = static_cast<int>(event->height / gvw->zoom);
+ int width = static_cast<int>(allocation->width / gvw->zoom);
+ int height = static_cast<int>(allocation->height / gvw->zoom);
ggadget::SizingEvent onsizing_event(width, height);
if (gvw->view->OnOtherEvent(onsizing_event, &onsizing_event) !=
ggadget::EVENT_RESULT_CANCELED) {
@@ -121,30 +124,27 @@
gvw->view->SetSize(width, height);
gtk_widget_queue_resize(widget);
LOG("View resized.");
- return FALSE;
}
} else if (mode == ViewInterface::RESIZABLE_ZOOM) {
int view_width = gvw->view->GetWidth();
int view_height = gvw->view->GetHeight();
if (view_width && view_height) {
double xzoom =
- static_cast<double>(event->width) / gvw->view->GetWidth();
+ static_cast<double>(allocation->width) / gvw->view->GetWidth();
double yzoom =
- static_cast<double>(event->height) / gvw->view->GetHeight();
+ static_cast<double>(allocation->height) / gvw->view->GetHeight();
double zoom = std::min(xzoom, yzoom);
gvw->zoom = zoom;
gvw->host->ChangeZoom(zoom);
gtk_widget_queue_resize(widget);
gtk_widget_queue_draw(widget);
LOG("View zoomed.");
- return FALSE;
}
+ } else {
+ LOG("Can't resize view.");
+ // TODO: do something here.
}
-
- LOG("Can't resize view.");
- // TODO: do something here.
}
- return FALSE;
}
static gboolean GadgetViewWidget_expose(GtkWidget *widget,
@@ -655,16 +655,14 @@
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(c);
GtkObjectClass *object_class = GTK_OBJECT_CLASS(c);
- parent_class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_FIXED));
-
object_class->destroy = GadgetViewWidget_destroy;
widget_class->realize = GadgetViewWidget_realize;
widget_class->unrealize = GadgetViewWidget_unrealize;
- widget_class->configure_event = GadgetViewWidget_configure;
widget_class->expose_event = GadgetViewWidget_expose;
widget_class->size_request = GadgetViewWidget_size_request;
+ widget_class->size_allocate = GadgetViewWidget_size_allocate;
widget_class->button_press_event = GadgetViewWidget_button_press;
widget_class->button_release_event = GadgetViewWidget_button_release;
@@ -686,31 +684,6 @@
widget_class->scroll_event = GadgetViewWidget_scroll;
}
-GType GadgetViewWidget_get_type() {
- static GType gw_type = 0;
-
- if (!gw_type) {
- static const GTypeInfo gw_info = {
- sizeof(GadgetViewWidgetClass),
- NULL, // base_init
- NULL, // base_finalize
- (GClassInitFunc)GadgetViewWidget_class_init,
- NULL, // class_finalize
- NULL, // class_data
- sizeof(GadgetViewWidget),
- 0, // n_preallocs
- (GInstanceInitFunc)GadgetViewWidget_init,
- };
-
- gw_type = g_type_register_static(GTK_TYPE_FIXED,
- "GadgetViewWidget",
- &gw_info,
- (GTypeFlags)0);
- }
-
- return gw_type;
-}
-
GtkWidget *GadgetViewWidget_new(ggadget::gtk::GtkViewHost *host, double zoom,
bool composited, bool useshapemask) {
GtkWidget *widget = GTK_WIDGET(g_object_new(GadgetViewWidget_get_type(),
@@ -723,7 +696,7 @@
gvw->composited = composited;
gvw->useshapemask = useshapemask;
- gtk_fixed_set_has_window(&gvw->fixed, TRUE);
+ gtk_fixed_set_has_window(&gvw->parent_widget, TRUE);
static const GtkTargetEntry kDragTargets[] = {
{ const_cast<char *>(kUriListTarget), 0, 0 },
=== ggadget/xml_http_request.cc
==================================================================
--- ggadget/xml_http_request.cc (revision 654)
+++ ggadget/xml_http_request.cc (revision 655)
@@ -49,10 +49,8 @@
DEFINE_CLASS_ID(0xda25f528f28a4319, XMLHttpRequestInterface);
XMLHttpRequest(MainLoopInterface *main_loop,
- ScriptContextInterface *script_context,
XMLParserInterface *xml_parser)
: main_loop_(main_loop),
- script_context_(script_context),
xml_parser_(xml_parser),
async_(false),
curl_(NULL),
@@ -257,6 +255,10 @@
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, this);
if (async_) {
+ // Add an internal reference when this request is working to prevent
+ // this object from being GC'ed.
+ Attach();
+
send_flag_ = true;
curlm_ = curl_multi_init();
curl_multi_setopt(curlm_, CURLMOPT_SOCKETFUNCTION, SocketCallback);
@@ -294,10 +296,6 @@
DLOG("XMLHttpRequest: Send(async): DONE");
Done();
}
-
- // Prevent this object from being GC'ed during handling the request.
- if (script_context_)
- script_context_->LockObject(this, "XMLHttpRequest");
} else {
// As described in the spec, here don't change the state, but send
// an event for historical reasons.
@@ -636,10 +634,13 @@
if ((state_ == OPENED && send_flag_) ||
state_ == HEADERS_RECEIVED || state_ == LOADING)
ChangeState(DONE);
- send_flag_ = false;
- if (async_ && script_context_)
- script_context_->UnlockObject(this);
+ if (async_ && send_flag_) {
+ // Detach the internal reference that was added when the request was
+ // started.
+ Detach();
+ }
+ send_flag_ = false;
}
virtual void Abort() {
@@ -892,7 +893,6 @@
}
MainLoopInterface *main_loop_;
- ScriptContextInterface *script_context_;
XMLParserInterface *xml_parser_;
Signal0<void> onreadystatechange_signal_;
@@ -926,10 +926,9 @@
} // anonymous namespace
-XMLHttpRequestInterface *CreateXMLHttpRequest(
- MainLoopInterface *main_loop, ScriptContextInterface *script_context,
- XMLParserInterface *xml_parser) {
- return new XMLHttpRequest(main_loop, script_context, xml_parser);
+XMLHttpRequestInterface *CreateXMLHttpRequest(MainLoopInterface *main_loop,
+ XMLParserInterface *xml_parser) {
+ return new XMLHttpRequest(main_loop, xml_parser);
}
} // namespace ggadget
=== ggadget/content_item.cc
==================================================================
--- ggadget/content_item.cc (revision 654)
+++ ggadget/content_item.cc (revision 655)
@@ -185,6 +185,13 @@
void ContentItem::DetachContentArea(ContentAreaElement *content_area) {
ASSERT(impl_->content_area_ == content_area);
impl_->content_area_ = NULL;
+ // The object may still be referenced by Script after detaching from
+ // the content area. Destroy the images to prevent them from being referenced
+ // after the graphics is destroyed.
+ DestroyImage(impl_->image_);
+ impl_->image_ = NULL;
+ DestroyImage(impl_->notifier_image_);
+ impl_->notifier_image_ = NULL;
Detach();
}
=== ggadget/view.cc
==================================================================
--- ggadget/view.cc (revision 654)
+++ ggadget/view.cc (revision 655)
@@ -31,7 +31,6 @@
#include "math_utils.h"
#include "script_context_interface.h"
#include "scriptable_binary_data.h"
-#include "scriptable_delegator.h"
#include "scriptable_event.h"
#include "slot.h"
#include "texture.h"
=== ggadget/scriptable_helper.cc
==================================================================
--- ggadget/scriptable_helper.cc (revision 654)
+++ ggadget/scriptable_helper.cc (revision 655)
@@ -29,7 +29,7 @@
class ScriptableHelperImpl : public ScriptableHelperImplInterface {
public:
- ScriptableHelperImpl();
+ ScriptableHelperImpl(OwnershipPolicy policy);
virtual ~ScriptableHelperImpl();
virtual void RegisterProperty(const char *name, Slot *getter, Slot *setter);
@@ -44,18 +44,19 @@
virtual void SetPrototype(ScriptableInterface *prototype);
virtual void SetArrayHandler(Slot *getter, Slot *setter);
virtual void SetDynamicPropertyHandler(Slot *getter, Slot *setter);
+ virtual int GetRefCount() { return ref_count_; }
- // The following 5 methods declared in ScriptableInterface should never be
+ // The following 3 methods declared in ScriptableInterface should never be
// called.
virtual uint64_t GetClassId() const { return 0; }
virtual bool IsInstanceOf(uint64_t class_id) const {
ASSERT(false); return false;
}
virtual bool IsStrict() const { ASSERT(false); return false; }
- virtual OwnershipPolicy Attach() { ASSERT(false); return NATIVE_OWNED; }
- virtual bool Detach() { ASSERT(false); return false; }
+ virtual OwnershipPolicy Attach();
+ virtual bool Detach();
- virtual Connection *ConnectToOnDeleteSignal(Slot0<void> *slot);
+ virtual Connection *ConnectOnReferenceChange(Slot2<void, int, int> *slot);
virtual bool GetPropertyInfoByName(const char *name,
int *id, Variant *prototype,
bool *is_method);
@@ -81,6 +82,9 @@
typedef std::vector<const char *> NameVector;
typedef std::map<const char *, Variant, GadgetCharPtrComparator> ConstantMap;
+ OwnershipPolicy policy_;
+ int ref_count_;
+
// If true, no more new RegisterXXX or SetPrototype can be called.
// It'll be set to true in any ScriptableInterface operation on properties.
bool sealed_;
@@ -102,7 +106,7 @@
// values are constant values.
ConstantMap constants_;
- Signal0<void> ondelete_signal_;
+ Signal2<void, int, int> on_reference_change_signal_;
ScriptableInterface *prototype_;
Slot *array_getter_;
Slot *array_setter_;
@@ -114,12 +118,15 @@
ScriptableInterface *pending_exception_;
};
-ScriptableHelperImplInterface *NewScriptableHelperImpl() {
- return new ScriptableHelperImpl();
+ScriptableHelperImplInterface *NewScriptableHelperImpl(
+ ScriptableInterface::OwnershipPolicy policy) {
+ return new ScriptableHelperImpl(policy);
}
-ScriptableHelperImpl::ScriptableHelperImpl()
- : sealed_(false),
+ScriptableHelperImpl::ScriptableHelperImpl(OwnershipPolicy policy)
+ : policy_(policy),
+ ref_count_(0),
+ sealed_(false),
property_count_(0),
prototype_(NULL),
array_getter_(NULL),
@@ -131,8 +138,10 @@
}
ScriptableHelperImpl::~ScriptableHelperImpl() {
+ ASSERT(ref_count_ == 0);
// Emit the ondelete signal, as early as possible.
- ondelete_signal_();
+ if (policy_ != OWNERSHIP_SHARED)
+ on_reference_change_signal_(0, 0);
// Free all owned slots.
for (VariantVector::const_iterator it = slot_prototypes_.begin();
@@ -349,10 +358,34 @@
dynamic_property_setter_ = setter;
}
-Connection *ScriptableHelperImpl::ConnectToOnDeleteSignal(Slot0<void> *slot) {
- return ondelete_signal_.ConnectGeneral(slot);
+ScriptableInterface::OwnershipPolicy ScriptableHelperImpl::Attach() {
+ // DLOG("Attach ref_count_ = %d", ref_count_);
+ if (policy_ == ScriptableInterface::OWNERSHIP_SHARED) {
+ ASSERT(ref_count_ >= 0);
+ on_reference_change_signal_(ref_count_, 1);
+ ref_count_++;
+ }
+ return policy_;
}
+bool ScriptableHelperImpl::Detach() {
+ // DLOG("Detach ref_count_ = %d", ref_count_);
+ if (policy_ == ScriptableInterface::OWNERSHIP_SHARED) {
+ ASSERT(ref_count_ > 0);
+ on_reference_change_signal_(ref_count_, -1);
+ if (--ref_count_ == 0) {
+ // Don't delete this now. Let ScriptableHelper do it.
+ return true;
+ }
+ }
+ return false;
+}
+
+Connection *ScriptableHelperImpl::ConnectOnReferenceChange(
+ Slot2<void, int, int> *slot) {
+ return on_reference_change_signal_.Connect(slot);
+}
+
// NOTE: Must be exception-safe because the handler may throw exceptions.
bool ScriptableHelperImpl::GetPropertyInfoByName(const char *name,
int *id, Variant *prototype,
=== ggadget/script_context_interface.h
==================================================================
--- ggadget/script_context_interface.h (revision 654)
+++ ggadget/script_context_interface.h (revision 655)
@@ -79,41 +79,21 @@
virtual bool RegisterClass(const char *name, Slot *constructor) = 0;
/**
- * Locks an scriptable object to prevent the script engine from garbage
- * collecting the object. Object with @c ScriptableInterface::NATIVE_OWNED
- * or @c ScriptableInterface::NATIVE_PERMANENT ownership policies need NOT
- * call this because the script adapter should do this automatically.
- * The object must has already been attached into the script engine when
- * this method is called, otherwise this method does nothing.
- * @param object the object to be locked.
- * @param name the name of the object for debug purpose.
- */
- virtual void LockObject(ScriptableInterface *object, const char *name) = 0;
-
- /**
- * Unlocks an scriptable object to allow the script engine to garbage
- * collect the object when possible.
- * The object must has already been attached into the script engine when
- * this method is called, otherwise this method does nothing.
- */
- virtual void UnlockObject(ScriptableInterface *object) = 0;
-
- /**
* Evaluates an expression in another context, and assigns the result to
* a property of an object in this context.
*
* @param dest_object the object against which to evaluate
* @a dest_object_expr. If it is @c NULL, the global object of this
- * context will be used to evaluate @c dest_object_expr.
+ * context will be used to evaluate @a dest_object_expr.
* @param dest_object_expr an expression to evaluate in this context that
- * results an object whose property is to be assigned. If it is empty or
- * @c NULL, @a dest_object (or global object if @a dest_object_expr is
- * @c NULL) will be the destination object.
+ * results an object whose property is to be assigned. If it is empty or
+ * @c NULL, @a dest_object (or global object if @a dest_object_expr is
+ * @c NULL) will be the destination object.
* @param dest_property the name of the destination property to be assigned.
* @param src_context source context in which to evaluate @a src_expr.
* @param src_object the source object against which to evaluate @c src_expr.
* If it is @c NULL, the global object of @a src_context will be used.
- * @param src_expr the expression to evaluate.
+ * @param src_expr the expression to evaluate in @a src_context.
* @return @c true if succeeded.
*/
virtual bool AssignFromContext(ScriptableInterface *dest_object,
@@ -123,6 +103,36 @@
ScriptableInterface *src_object,
const char *src_expr) = 0;
+ /**
+ * Assigns a native value to a property of an object in this context.
+ *
+ * @param object the object against which to evaluate @a object_expr.
+ * If it is @c NULL, the global object of this context will be used
+ * to evaluate @a object_expr.
+ * @param object_expr an expression to evaluate in this context that results
+ * an object whose property is to be assigned. If it is empty or @c NULL,
+ * @a object (or global object if @a object_expr is @c NULL) will be
+ * the destination object.
+ * @param property the name of the destination property to be assigned.
+ * @param value the native value.
+ * @return @c true if succeeded.
+ */
+ virtual bool AssignFromNative(ScriptableInterface *object,
+ const char *object_expr,
+ const char *property,
+ const Variant &value) = 0;
+
+ /**
+ * Evaluates an expression against a given object.
+ *
+ * @param object the object against which to evaluate @a expr. If it is
+ * @c NULL, the global object of this context will be used to evaluate
+ * @a expr.
+ * @param expr an expression to evaluate.
+ * @return @c the evaluated result value.
+ */
+ virtual Variant Evaluate(ScriptableInterface *object, const char *expr) = 0;
+
};
} // namespace ggadget
=== ggadget/details_view.h
==================================================================
--- ggadget/details_view.h (revision 654)
+++ ggadget/details_view.h (revision 655)
@@ -23,7 +23,7 @@
namespace ggadget {
-class OptionsInterface;
+class ScriptableOptions;
/**
* DetailsView data structure. It has no built-in logic.
@@ -85,8 +85,8 @@
/**
* Gets the "detailsViewData" property used in XML details views.
*/
- const OptionsInterface *GetDetailsViewData() const;
- OptionsInterface *GetDetailsViewData();
+ const ScriptableOptions *GetDetailsViewData() const;
+ ScriptableOptions *GetDetailsViewData();
/**
* Gets and sets the "external" property used in HTML details views.
=== ggadget/scriptable_delegator.h
==================================================================
--- ggadget/scriptable_delegator.h (revision 654)
+++ ggadget/scriptable_delegator.h (revision 655)
@@ -1,81 +0,0 @@
-/*
- Copyright 2007 Google Inc.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#ifndef GGADGET_SCRIPTABLE_DELEGATOR_H__
-#define GGADGET_SCRIPTABLE_DELEGATOR_H__
-
-#include <ggadget/scriptable_interface.h>
-
-namespace ggadget {
-
-/**
- * Wraps another @c ScriptableInterface instance and delegates all method
- * calls except @c IsStrict() to it.
- *
- * It's useful when registering different script objects backed with the same
- * C++ object in a script context. For example, in a view's script context,
- * the view object is registered as the global object which is not strict,
- * while at the same time, as the global 'view' variable which is strict.
- * In this case, we register the non-strict view object as the global object,
- * while register a strict @c ScriptableDelegator of the view object as the
- * 'view' variable.
- *
- * @see ScriptableInterface::IsStrict()
- */
-class ScriptableDelegator : public ScriptableInterface {
- public:
- ScriptableDelegator(ScriptableInterface *scriptable, bool strict)
- : scriptable_(scriptable),
- strict_(strict) { }
- virtual ~ScriptableDelegator() { }
-
- virtual uint64_t GetClassId() const { return scriptable_->GetClassId(); }
- virtual bool IsInstanceOf(uint64_t class_id) const {
- return scriptable_->IsInstanceOf(class_id);
- }
- virtual OwnershipPolicy Attach() { return NATIVE_OWNED; }
- virtual bool Detach() { return false; }
- virtual bool IsStrict() const { return strict_; }
- virtual Connection *ConnectToOnDeleteSignal(Slot0<void> *slot) {
- return scriptable_->ConnectToOnDeleteSignal(slot);
- }
- virtual bool GetPropertyInfoByName(const char *name,
- int *id, Variant *prototype,
- bool *is_method) {
- return scriptable_->GetPropertyInfoByName(name, id, prototype, is_method);
- }
- virtual bool GetPropertyInfoById(int id, Variant *prototype,
- bool *is_method, const char **name) {
- return scriptable_->GetPropertyInfoById(id, prototype, is_method, name);
- }
- virtual Variant GetProperty(int id) {
- return scriptable_->GetProperty(id);
- }
- virtual bool SetProperty(int id, Variant value) {
- return scriptable_->SetProperty(id, value);
- }
- virtual ScriptableInterface *GetPendingException(bool clear) {
- return scriptable_->GetPendingException(clear);
- }
-
- private:
- ScriptableInterface *scriptable_;
- bool strict_;
-};
-
-} // namespace ggadget
-
-#endif // GGADGET_SCRIPTABLE_DELEGATOR_H__
Property changes on: ggadget/scriptable_delegator.h
___________________________________________________________________
Name: svn:keywords
-URL HeadURL Author LastChangedBy Date LastChangedDate Rev Revision LastChangedRevision Id
This is a semiautomated message from "svkmail". Complaints or suggestions?
Mail edy...@gmail.com.
Hello zhuang.dev, james.su,
I'd like you to do a code review. Please review the following patch:
----------------------------------------------------------------------
r655: wangxianzhu | 2008-01-06 15:59:56 +0800
Finished Text/HTML details view feature:
1. Added text details view;
2. Reuse browser child process;
3. Removed ScriptContextInterface::LockObject() and UnlockObject(), and changed to using scriptable reference counting;
Added OnReferenceChange notification in ScriptableInterface.
----------------------------------------------------------------------
=== hosts/simple/resources/text_details_view.xml
==================================================================
--- hosts/simple/resources/text_details_view.xml (revision 654)
+++ hosts/simple/resources/text_details_view.xml (revision 655)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<view width="300" height="250" resizable="true" onopen="onopen()">
+ <script>
+<!--
+function onopen() {
+ label.innerText = detailsViewData.GetValue ("content");
+ Write(down_fd_, buffer.c_str (), buffer.size());
===...
[Message clipped]