diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-18 09:47:35 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-18 09:47:35 +0000 |
commit | 912256b3517241047095dac6946de191029dda27 (patch) | |
tree | 7799ca1544916d7e1f44b06bd0dd43e080b61996 /chrome | |
parent | 50c4e907cf41e395a5edecd1ae122b9a2b35410d (diff) | |
download | chromium_src-912256b3517241047095dac6946de191029dda27.zip chromium_src-912256b3517241047095dac6946de191029dda27.tar.gz chromium_src-912256b3517241047095dac6946de191029dda27.tar.bz2 |
Try again to land "Implement script API:executeScript"
http://codereview.chromium.org/173556
TBR=mpcomplete@chromium.org
Patch from Jerry Tang <tangjie@google.com>.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26556 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
28 files changed, 584 insertions, 7 deletions
diff --git a/chrome/browser/extensions/execute_code_in_tab_function.cc b/chrome/browser/extensions/execute_code_in_tab_function.cc new file mode 100755 index 0000000..bd62799 --- /dev/null +++ b/chrome/browser/extensions/execute_code_in_tab_function.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/execute_code_in_tab_function.h" + +#include "base/thread.h" +#include "base/file_util.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/extensions/extension_tabs_module_constants.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_error_utils.h" + +namespace keys = extension_tabs_module_constants; + +const wchar_t* kCodeKey = L"code"; +const wchar_t* kFileKey = L"file"; + +bool ExecuteCodeInTabFunction::RunImpl() { + EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST)); + const ListValue* args = static_cast<const ListValue*>(args_); + + DictionaryValue* script_info; + EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(1, &script_info)); + size_t number_of_value = script_info->GetSize(); + if (number_of_value == 0) { + error_ = keys::kNoCodeOrFileToExecuteError; + return false; + } else if (number_of_value > 1) { + error_ = keys::kMoreThanOneValuesError; + return false; + } + + execute_tab_id_ = -1; + Browser* browser = NULL; + TabContents* contents = NULL; + + // If |tab_id| is specified, look for it. Otherwise default to selected tab + // in the current window. + Value* tab_value = NULL; + EXTENSION_FUNCTION_VALIDATE(args->Get(0, &tab_value)); + if (tab_value->IsType(Value::TYPE_NULL)) { + browser = dispatcher()->GetBrowser(); + if (!browser) { + error_ = keys::kNoCurrentWindowError; + return false; + } + if (!ExtensionTabUtil::GetDefaultTab(browser, &contents, &execute_tab_id_)) + return false; + } else { + EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&execute_tab_id_)); + if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), &browser, + NULL, &contents, NULL)) { + return false; + } + } + + DCHECK(browser); + DCHECK(contents); + + if (!GetExtension()->CanAccessHost(contents->GetURL())) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kCannotAccessPageError, contents->GetURL().spec()); + return false; + } + + if (script_info->HasKey(kCodeKey)) { + if (!script_info->GetString(kCodeKey, &code_string_)) + return false; + } + + if (!code_string_.empty()) { + Execute(); + return true; + } + + std::string relative_path; + if (script_info->HasKey(kFileKey)) { + if (!script_info->GetString(kFileKey, &relative_path)) + return false; + file_path_ = GetExtension()->GetResourcePath(relative_path); + } + if (file_path_.empty()) { + error_ = keys::kNoCodeOrFileToExecuteError; + return false; + } + + ui_loop_ = MessageLoop::current(); + MessageLoop* work_loop = g_browser_process->file_thread()->message_loop(); + work_loop->PostTask(FROM_HERE, + NewRunnableMethod(this, &ExecuteCodeInTabFunction::LoadFile)); + + return true; +} + +void ExecuteCodeInTabFunction::LoadFile() { + DCHECK(ui_loop_); + std::string content; + if (!file_util::ReadFileToString(file_path_, &content)) { + +#if defined(OS_POSIX) + error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kLoadFileError, + file_path_.value()); +#elif defined(OS_WIN) + error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kLoadFileError, + WideToUTF8(file_path_.value())); +#endif // OS_WIN + + ui_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &ExecuteCodeInTabFunction::SendResponse, + false)); + } else { + code_string_ = content; + ui_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &ExecuteCodeInTabFunction::Execute)); + } + + return; +} + +void ExecuteCodeInTabFunction::Execute() { + TabContents* contents = NULL; + Browser* browser = NULL; + if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(), &browser, NULL, + &contents, NULL) && contents && browser) { + SendResponse(false); + return; + } + + bool is_js_code = true; + std::string function_name = name(); + if (function_name == keys::kInsertCSSFunction) { + is_js_code = false; + } else if (function_name != keys::kExecuteScriptFunction) { + DCHECK(false); + } + registrar_.Add(this, NotificationType::TAB_CODE_EXECUTED, + NotificationService::AllSources()); + AddRef(); // balanced in Observe() + contents->ExecuteCode(request_id(), extension_id(), is_js_code, + code_string_); +} + +void ExecuteCodeInTabFunction::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + std::pair<int, bool>* result_details = + Details<std::pair<int, bool> >(details).ptr(); + if (result_details->first == request_id()) { + SendResponse(result_details->second); + Release(); // balanced in Execute() + } +} diff --git a/chrome/browser/extensions/execute_code_in_tab_function.h b/chrome/browser/extensions/execute_code_in_tab_function.h new file mode 100755 index 0000000..c1a1579 --- /dev/null +++ b/chrome/browser/extensions/execute_code_in_tab_function.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXECUTE_CODE_IN_TAB_FUNCTION_H__ +#define CHROME_BROWSER_EXTENSIONS_EXECUTE_CODE_IN_TAB_FUNCTION_H__ + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "chrome/browser/extensions/extension_function.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_registrar.h" + +class MessageLoop; + +// Implement API call tabs.executeScript and tabs.insertCSS. +class ExecuteCodeInTabFunction : public AsyncExtensionFunction, + public NotificationObserver { + public: + ExecuteCodeInTabFunction() : execute_tab_id_(-1), ui_loop_(NULL) {} + + private: + virtual bool RunImpl(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Load contents from file whose path is specified in JSON arguments. Run + // in file thread. + void LoadFile(); + + // Run in UI thread. + void Execute(); + + NotificationRegistrar registrar_; + + // Id of tab which executes code. + int execute_tab_id_; + + MessageLoop* ui_loop_; + + // Contain path of file which is specified in JSON arguments. + FilePath file_path_; + + // Contain code to be executed. + std::string code_string_; +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXECUTE_CODE_IN_TAB_FUNCTION_H__ diff --git a/chrome/browser/extensions/execute_script_apitest.cc b/chrome/browser/extensions/execute_script_apitest.cc new file mode 100755 index 0000000..f519702 --- /dev/null +++ b/chrome/browser/extensions/execute_script_apitest.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/extension_apitest.h" + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ExecuteScript) { + host_resolver()->AddRule("a.com", "127.0.0.1"); + host_resolver()->AddRule("b.com", "127.0.0.1"); + StartHTTPServer(); + + ASSERT_TRUE(RunExtensionTest("executescript")) << message_; +} diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index 36f4192..a9de221 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -7,6 +7,7 @@ #include "base/process_util.h" #include "base/singleton.h" #include "base/values.h" +#include "chrome/browser/extensions/execute_code_in_tab_function.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/browser/extensions/extension_bookmarks_module_constants.h" #include "chrome/browser/extensions/extension_function.h" @@ -111,6 +112,10 @@ void FactoryRegistry::ResetFunctions() { &NewExtensionFunction<DetectTabLanguageFunction>; factories_[tabs::kCaptureVisibleTabFunction] = &NewExtensionFunction<CaptureVisibleTabFunction>; + factories_[tabs::kExecuteScriptFunction] = + &NewExtensionFunction<ExecuteCodeInTabFunction>; + factories_[tabs::kInsertCSSFunction] = + &NewExtensionFunction<ExecuteCodeInTabFunction>; // Page Actions. factories_[page_actions::kEnablePageActionFunction] = diff --git a/chrome/browser/extensions/extension_tabs_module.cc b/chrome/browser/extensions/extension_tabs_module.cc index 5cd4242..12028c2 100644 --- a/chrome/browser/extensions/extension_tabs_module.cc +++ b/chrome/browser/extensions/extension_tabs_module.cc @@ -27,7 +27,6 @@ #include "skia/ext/platform_canvas.h" #include "third_party/skia/include/core/SkBitmap.h" - namespace keys = extension_tabs_module_constants; // Forward declare static helper functions defined below. @@ -157,6 +156,22 @@ DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser, return result; } +bool ExtensionTabUtil::GetDefaultTab(Browser* browser, TabContents** contents, + int* tab_id) { + DCHECK(browser); + DCHECK(contents); + DCHECK(tab_id); + + *contents = browser->tabstrip_model()->GetSelectedTabContents(); + if (*contents) { + if (tab_id) + *tab_id = ExtensionTabUtil::GetTabId(*contents); + return true; + } + + return false; +} + bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile, Browser** browser, TabStripModel** tab_strip, diff --git a/chrome/browser/extensions/extension_tabs_module.h b/chrome/browser/extensions/extension_tabs_module.h index eccd823..0914758 100644 --- a/chrome/browser/extensions/extension_tabs_module.h +++ b/chrome/browser/extensions/extension_tabs_module.h @@ -39,6 +39,8 @@ class ExtensionTabUtil { static DictionaryValue* CreateWindowValue(const Browser* browser, bool populate_tabs); + static bool GetDefaultTab(Browser* browser, TabContents** contents, + int* tab_id); // Any out parameter (|browser|, |tab_strip|, |contents|, & |tab_index|) may // be NULL and will not be set within the function. static bool GetTabById(int tab_id, Profile* profile, Browser** browser, @@ -94,9 +96,9 @@ class RemoveTabFunction : public SyncExtensionFunction { }; class DetectTabLanguageFunction : public AsyncExtensionFunction, public NotificationObserver { + private: virtual bool RunImpl(); - private: virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details); diff --git a/chrome/browser/extensions/extension_tabs_module_constants.cc b/chrome/browser/extensions/extension_tabs_module_constants.cc index 7f28314..8c9e153 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.cc +++ b/chrome/browser/extensions/extension_tabs_module_constants.cc @@ -64,4 +64,11 @@ const char kRemoveTabFunction[] = "tabs.remove"; const char kDetectTabLanguageFunction[] = "tabs.detectLanguage"; const char kCaptureVisibleTabFunction[] = "tabs.captureVisibleTab"; +const char kExecuteScriptFunction[] = "tabs.executeScript"; +const char kInsertCSSFunction[] = "tabs.insertCSS"; +const char kNoCodeOrFileToExecuteError[] = "No source code or file specified."; +const char kMoreThanOneValuesError[] = "There should be only one value (either" + "code or file) in the second argument."; +const char kLoadFileError[] = "Failed to load file: \"*\". "; + } // namespace extension_tabs_module_constants diff --git a/chrome/browser/extensions/extension_tabs_module_constants.h b/chrome/browser/extensions/extension_tabs_module_constants.h index f22b48d..cfb3412 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.h +++ b/chrome/browser/extensions/extension_tabs_module_constants.h @@ -69,6 +69,11 @@ extern const char kMoveTabFunction[]; extern const char kRemoveTabFunction[]; extern const char kDetectTabLanguageFunction[]; extern const char kCaptureVisibleTabFunction[]; +extern const char kExecuteScriptFunction[]; +extern const char kInsertCSSFunction[]; +extern const char kNoCodeOrFileToExecuteError[]; +extern const char kMoreThanOneValuesError[]; +extern const char kLoadFileError[]; }; // namespace extension_tabs_module_constants diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index 6d92b81..932bf89 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -774,6 +774,8 @@ void RenderViewHost::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_Find_Reply, OnMsgFindReply) IPC_MESSAGE_HANDLER(ViewMsg_DeterminePageText_Reply, OnDeterminePageTextReply) + IPC_MESSAGE_HANDLER(ViewMsg_ExecuteCodeFinished, + OnExecuteCodeFinished) IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFavIconURL, OnMsgUpdateFavIconURL) IPC_MESSAGE_HANDLER(ViewHostMsg_DidDownloadFavIcon, OnMsgDidDownloadFavIcon) IPC_MESSAGE_HANDLER(ViewHostMsg_ContextMenu, OnMsgContextMenu) @@ -1127,6 +1129,14 @@ void RenderViewHost::OnDeterminePageTextReply( #endif } +void RenderViewHost::OnExecuteCodeFinished(int request_id, bool success) { + std::pair<int, bool> result_details(request_id, success); + NotificationService::current()->Notify( + NotificationType::TAB_CODE_EXECUTED, + NotificationService::AllSources(), + Details<std::pair<int, bool> >(&result_details)); +} + void RenderViewHost::OnMsgUpdateFavIconURL(int32 page_id, const GURL& icon_url) { RenderViewHostDelegate::FavIcon* favicon_delegate = diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index 30f9709..52a0c10 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -14,6 +14,7 @@ #include "chrome/browser/renderer_host/render_widget_host.h" #include "chrome/common/modal_dialog_event.h" #include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" #include "chrome/common/page_zoom.h" #include "webkit/api/public/WebConsoleMessage.h" #include "webkit/api/public/WebDragOperation.h" @@ -495,6 +496,7 @@ class RenderViewHost : public RenderWidgetHost, int active_match_ordinal, bool final_update); void OnDeterminePageTextReply(const std::wstring& tab_text); + void OnExecuteCodeFinished(int request_id, bool success); void OnMsgUpdateFavIconURL(int32 page_id, const GURL& icon_url); void OnMsgDidDownloadFavIcon(int id, const GURL& image_url, diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 991b0e2..c7c1c37 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -845,6 +845,13 @@ void TabContents::CloseAllSuppressedPopups() { blocked_popups_->CloseAll(); } +void TabContents::ExecuteCode(int request_id, const std::string& extension_id, + bool is_js_code, const std::string& code_string) { + render_view_host()->Send( + new ViewMsg_ExecuteCode(render_view_host()->routing_id(), request_id, + extension_id, is_js_code, code_string)); +} + void TabContents::PopupNotificationVisibilityChanged(bool visible) { render_view_host()->PopupNotificationVisibilityChanged(visible); } diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index ee1b5bc..61404f3 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -363,6 +363,10 @@ class TabContents : public PageNavigator, // of unwanted popups. void CloseAllSuppressedPopups(); + // Execute code in this tab. + void ExecuteCode(int request_id, const std::string& extension_id, + bool is_js_code, const std::string& code_string); + // Called when the blocked popup notification is shown or hidden. virtual void PopupNotificationVisibilityChanged(bool visible); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 99dbc97..b9167b2 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -50,6 +50,7 @@ 'browser/gtk/view_id_util_browsertest.cc', 'browser/ssl/ssl_browser_tests.cc', 'browser/extensions/cross_origin_xhr_apitest.cc', + 'browser/extensions/execute_script_apitest.cc', 'browser/extensions/extension_apitest.cc', 'browser/extensions/extension_apitest.h', 'browser/extensions/extension_bookmarks_apitest.cc', @@ -81,6 +82,7 @@ ], 'browser_tests_sources_exclude_on_mac': [ 'browser/extensions/cross_origin_xhr_apitest.cc', + 'browser/extensions/execute_script_apitest.cc', 'browser/extensions/extension_apitest.cc', 'browser/extensions/extension_apitest.h', 'browser/extensions/extension_bookmarks_apitest.cc', @@ -1134,6 +1136,8 @@ 'browser/extensions/extension_dom_ui.h', 'browser/extensions/extension_event_names.cc', 'browser/extensions/extension_event_names.h', + 'browser/extensions/execute_code_in_tab_function.cc', + 'browser/extensions/execute_code_in_tab_function.h', 'browser/extensions/extension_file_util.cc', 'browser/extensions/extension_file_util.h', 'browser/extensions/extension_function.cc', diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index 558b22f..9e36615 100755 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -579,6 +579,64 @@ ] } ] + }, + { + "name": "executeScript", + "type": "function", + "description": "", + "parameters": [ + {"type": "integer", "name": "tabId", "optional": true, "description": "The id of tab which run the script, default to selected tab of current window."}, + { + "type": "object", + "name": "scriptDef", + "description": "Note:scripts are injected in the following definition order.", + "properties": { + "code": {"type": "string", "optional": true, "description": "JavaScript code to execute."}, + "file": {"type": "string", "optional": true, "description": "JavaScript file to execute."} + }, + "optional": true + }, + { + "type": "function", + "name": "callback", + "optional": true, + "description": "When all scripts are executed, this callback is called.", + "parameters": [] + } + ], + "returns": { + "type": "boolean", + "description": "Whether this call is successful" + } + }, + { + "name": "insertCSS", + "type": "function", + "description": "", + "parameters": [ + {"type": "integer", "name": "tabId", "optional": true, "description": "The id of tab which run the script, default to selected tab of current window."}, + { + "type": "object", + "name": "scriptDef", + "description": "Note:css are injected in the following definition order.", + "properties": { + "code": {"type": "string", "optional": true, "description": "CSS code to be injected."}, + "file": {"type": "string", "optional": true, "description": "CSS file to be injected."}, + }, + "optional": true + }, + { + "type": "function", + "name": "callback", + "optional": true, + "description": "When all css are inserted, this callback is called.", + "parameters": [] + } + ], + "returns": { + "type": "boolean", + "description": "Whether this call is successful" + } } ], "events": [ diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 9f30770..dad6dafb 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -212,6 +212,9 @@ class NotificationType { // are Details<std::string> and the source is Source<RenderViewHost>. TAB_LANGUAGE_DETERMINED, + // Send after the code is run in specified tab. + TAB_CODE_EXECUTED, + // The user has changed the browser theme. BROWSER_THEME_CHANGED, diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index 2916c17..da53fef 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -21,6 +21,7 @@ #include "base/values.h" #include "chrome/common/css_colors.h" #include "chrome/common/extensions/update_manifest.h" +#include "chrome/common/notification_type.h" #include "chrome/common/transport_dib.h" #include "chrome/common/view_types.h" #include "ipc/ipc_channel_handle.h" @@ -247,6 +248,11 @@ IPC_BEGIN_MESSAGES(View) IPC_MESSAGE_ROUTED1(ViewMsg_DeterminePageText_Reply, std::wstring /* the language */) + // Send from the renderer to the browser to return the script running result. + IPC_MESSAGE_ROUTED2(ViewMsg_ExecuteCodeFinished, + int, /* request id */ + bool /* whether the script ran successfully */) + // Sent when the headers are available for a resource request. IPC_MESSAGE_ROUTED2(ViewMsg_Resource_ReceivedResponse, int /* request_id */, @@ -685,6 +691,14 @@ IPC_BEGIN_MESSAGES(View) IPC_MESSAGE_ROUTED1(ViewMsg_NotifyRenderViewType, ViewType::Type /* view_type */) + // Notification that renderer should run some JavaScript code. + IPC_MESSAGE_ROUTED4(ViewMsg_ExecuteCode, + int, /* request id */ + std::string, /* id of extension which runs the scripts */ + bool, /* It's true if the code is JavaScript; Otherwise + the code is CSS text. */ + std::string /* code would be executed */) + // Returns a file handle IPC_MESSAGE_CONTROL2(ViewMsg_DatabaseOpenFileResponse, int32 /* the ID of the message we're replying to */, diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 36f472a..d2c9eec 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -37,6 +37,7 @@ #include "chrome/renderer/audio_message_filter.h" #include "chrome/renderer/devtools_agent.h" #include "chrome/renderer/devtools_client.h" +#include "chrome/renderer/extension_groups.h" #include "chrome/renderer/extensions/event_bindings.h" #include "chrome/renderer/extensions/extension_process_bindings.h" #include "chrome/renderer/extensions/renderer_extension_bindings.h" @@ -443,6 +444,8 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewMsg_SetActive, OnSetActive) IPC_MESSAGE_HANDLER(ViewMsg_SetEditCommandsForNextKeyEvent, OnSetEditCommandsForNextKeyEvent); + IPC_MESSAGE_HANDLER(ViewMsg_ExecuteCode, + OnExecuteCode) // Have the super handle all other messages. IPC_MESSAGE_UNHANDLED(RenderWidget::OnMessageReceived(message)) @@ -2080,6 +2083,14 @@ void RenderView::didCreateDocumentElement(WebFrame* frame) { frame, UserScript::DOCUMENT_START); } + while (!pending_code_execution_queue_.empty()) { + scoped_refptr<CodeExecutionInfo> info = + pending_code_execution_queue_.front(); + OnExecuteCode(info->request_id, info->extension_id, info->is_js_code, + info->code_string); + pending_code_execution_queue_.pop(); + } + // Notify the browser about non-blank documents loading in the top frame. GURL url = frame->url(); if (url.is_valid() && url.spec() != chrome::kAboutBlankURL) { @@ -3481,6 +3492,35 @@ void RenderView::OnSetEditCommandsForNextKeyEvent( edit_commands_ = edit_commands; } +void RenderView::OnExecuteCode(int request_id, const std::string& extension_id, + bool is_js_code, + const std::string& code_string) { + if (is_loading_) { + scoped_refptr<CodeExecutionInfo> info = new CodeExecutionInfo( + request_id, extension_id, is_js_code, code_string); + pending_code_execution_queue_.push(info); + return; + } + WebFrame* main_frame = webview() ? webview()->GetMainFrame() : NULL; + if (!main_frame) { + Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, false)); + return; + } + + if (is_js_code) { + std::vector<WebScriptSource> sources; + sources.push_back( + WebScriptSource(WebString::fromUTF8(code_string))); + UserScriptSlave::InsertInitExtensionCode(&sources, extension_id); + main_frame->executeScriptInNewWorld(&sources.front(), sources.size(), + EXTENSION_GROUP_CONTENT_SCRIPTS); + } else { + main_frame->insertStyleText(WebString::fromUTF8(code_string), WebString()); + } + + Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, true)); +} + void RenderView::DidHandleKeyEvent() { edit_commands_.clear(); } diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index b647e65..4b35260 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -22,6 +22,7 @@ #include "build/build_config.h" #include "chrome/common/edit_command.h" #include "chrome/common/navigation_gesture.h" +#include "chrome/common/notification_type.h" #include "chrome/common/renderer_preferences.h" #include "chrome/common/view_types.h" #include "chrome/renderer/automation/dom_automation_controller.h" @@ -610,6 +611,10 @@ class RenderView : public RenderWidget, const MediaPlayerAction& action); void OnNotifyRendererViewType(ViewType::Type view_type); void OnUpdateBrowserWindowId(int window_id); + void OnExecuteCode(int request_id, + const std::string& extension_id, + bool is_js_code, + const std::string& code_string); void OnUpdateBackForwardListCount(int back_list_count, int forward_list_count); void OnGetAccessibilityInfo( @@ -909,6 +914,31 @@ class RenderView : public RenderWidget, // Id number of browser window which RenderView is attached to. int browser_window_id_; + // If page is loading, we can't run code, just create CodeExecutionInfo + // objects store pending execution information and delay the execution until + // page is loaded. + struct CodeExecutionInfo : public base::RefCounted<CodeExecutionInfo> { + CodeExecutionInfo(int id_of_request, const std::string& id_of_extension, + bool is_js, const std::string& code) + : request_id(id_of_request), + extension_id(id_of_extension), + code_string(code), + is_js_code(is_js) {} + int request_id; + + // The id of extension who issues the pending executeScript API call. + std::string extension_id; + + // The code which would be executed. + std::string code_string; + + // It's true if |code_string| is JavaScript; otherwise |code_string| is + // CSS text. + bool is_js_code; + }; + + std::queue<scoped_refptr<CodeExecutionInfo> > pending_code_execution_queue_; + // page id for the last navigation sent to the browser. int32 last_top_level_navigation_page_id_; diff --git a/chrome/renderer/user_script_slave.cc b/chrome/renderer/user_script_slave.cc index 7f7ad31..c120c6b 100644 --- a/chrome/renderer/user_script_slave.cc +++ b/chrome/renderer/user_script_slave.cc @@ -14,12 +14,10 @@ #include "chrome/renderer/extension_groups.h" #include "googleurl/src/gurl.h" #include "webkit/api/public/WebFrame.h" -#include "webkit/api/public/WebScriptSource.h" #include "grit/renderer_resources.h" using WebKit::WebFrame; -using WebKit::WebScriptSource; using WebKit::WebString; // These two strings are injected before and after the Greasemonkey API and @@ -109,6 +107,15 @@ bool UserScriptSlave::UpdateScripts(base::SharedMemoryHandle shared_memory) { return true; } +// static +void UserScriptSlave::InsertInitExtensionCode( + std::vector<WebScriptSource>* sources, const std::string& extension_id) { + DCHECK(sources); + sources->insert(sources->begin(), + WebScriptSource(WebString::fromUTF8( + StringPrintf(kInitExtension, extension_id.c_str())))); +} + bool UserScriptSlave::InjectScripts(WebFrame* frame, UserScript::RunLocation location) { // Don't bother if this is not a URL we inject script into. @@ -157,9 +164,7 @@ bool UserScriptSlave::InjectScripts(WebFrame* frame, } else { // Setup chrome.self to contain an Extension object with the correct // ID. - sources.insert(sources.begin(), - WebScriptSource(WebString::fromUTF8( - StringPrintf(kInitExtension, script->extension_id().c_str())))); + InsertInitExtensionCode(&sources, script->extension_id()); } frame->executeScriptInNewWorld(&sources.front(), sources.size(), diff --git a/chrome/renderer/user_script_slave.h b/chrome/renderer/user_script_slave.h index 614dd85..39407f1 100644 --- a/chrome/renderer/user_script_slave.h +++ b/chrome/renderer/user_script_slave.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_USER_SCRIPT_SLAVE_H_ #include <map> +#include <string> #include <vector> #include "base/scoped_ptr.h" @@ -13,11 +14,14 @@ #include "base/stl_util-inl.h" #include "base/string_piece.h" #include "chrome/common/extensions/user_script.h" +#include "webkit/api/public/WebScriptSource.h" namespace WebKit { class WebFrame; } +using WebKit::WebScriptSource; + // Manages installed UserScripts for a render process. class UserScriptSlave { public: @@ -31,6 +35,8 @@ class UserScriptSlave { // testability. bool InjectScripts(WebKit::WebFrame* frame, UserScript::RunLocation location); + static void InsertInitExtensionCode(std::vector<WebScriptSource>* sources, + const std::string& extension_id); private: // Shared memory containing raw script data. scoped_ptr<base::SharedMemory> shared_memory_; diff --git a/chrome/test/data/extensions/api_test/executescript/1.css b/chrome/test/data/extensions/api_test/executescript/1.css new file mode 100644 index 0000000..a814f5f --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/1.css @@ -0,0 +1 @@ +b {display:block;}
\ No newline at end of file diff --git a/chrome/test/data/extensions/api_test/executescript/1.css~ b/chrome/test/data/extensions/api_test/executescript/1.css~ new file mode 100644 index 0000000..f88e4e8 --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/1.css~ @@ -0,0 +1 @@ +{display:block;}
\ No newline at end of file diff --git a/chrome/test/data/extensions/api_test/executescript/manifest.json b/chrome/test/data/extensions/api_test/executescript/manifest.json new file mode 100644 index 0000000..b2f3856 --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/manifest.json @@ -0,0 +1,7 @@ +{ + "version": "1.0.0.0", + "name": "executeScript and insertCSS test", + "description": "Test extension API: executeScript and insertCSS", + "background_page": "test.html", + "permissions": ["tabs", "http://a.com/"] +} diff --git a/chrome/test/data/extensions/api_test/executescript/script1.js b/chrome/test/data/extensions/api_test/executescript/script1.js new file mode 100644 index 0000000..1e22ba7 --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/script1.js @@ -0,0 +1 @@ +document.title = 'executeScript1'; diff --git a/chrome/test/data/extensions/api_test/executescript/script2.js b/chrome/test/data/extensions/api_test/executescript/script2.js new file mode 100644 index 0000000..7276b1d --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/script2.js @@ -0,0 +1,19 @@ +function getStyle(elem, name) { + if (document.defaultView && document.defaultView.getComputedStyle) { + name = name.replace(/([A-Z])/g, '-$1'); + name = name.toLowerCase(); + + try { + var s = document.defaultView.getComputedStyle(elem, ''); + return s && s.getPropertyValue(name); + } catch (ex) { + return null; + } + } else { + return null; + } +} + +var bElement = document.getElementById('test1'); +var display = getStyle(bElement, 'display').toLowerCase(); +document.title = display; diff --git a/chrome/test/data/extensions/api_test/executescript/script3.js b/chrome/test/data/extensions/api_test/executescript/script3.js new file mode 100644 index 0000000..f8fcaf04 --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/script3.js @@ -0,0 +1,19 @@ +function getStyle(elem, name) { + if (document.defaultView && document.defaultView.getComputedStyle) { + name = name.replace(/([A-Z])/g, '-$1'); + name = name.toLowerCase(); + + try { + var s = document.defaultView.getComputedStyle(elem, ''); + return s && s.getPropertyValue(name); + } catch (ex) { + return null; + } + } else { + return null; + } +} + +var bElement = document.getElementById('test2'); +var display = getStyle(bElement, 'display').toLowerCase(); +document.title = display; diff --git a/chrome/test/data/extensions/api_test/executescript/test.html b/chrome/test/data/extensions/api_test/executescript/test.html new file mode 100644 index 0000000..30433ce --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/test.html @@ -0,0 +1,86 @@ +<script> + +var pass = chrome.test.callbackPass; +var fail = chrome.test.callbackFail; +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; +var relativePath = + '/files/extensions/api_test/executescript/test_executescript.html'; +var testUrl = 'http://a.com:1337' + relativePath; +var testFailureUrl = 'http://b.com:1337' + relativePath; +var testingFailure = false; + +chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) { + if (changeInfo.status == 'complete') + return; + if (testingFailure) { + return; + } + + chrome.test.runTests([ + + function executeJavaScriptCodeShouldSucceed() { + var script_file = {}; + script_file.code = "document.title = 'executeScript';"; + chrome.tabs.executeScript(tabId, script_file, function() { + chrome.tabs.get(tabId, pass(function(tab) { + assertEq(tab.title, 'executeScript'); + })); + }); + }, + + function executeJavaScriptFileShouldSucceed() { + var script_file = {}; + script_file.file = 'script1.js'; + chrome.tabs.executeScript(tabId, script_file, function() { + chrome.tabs.get(tabId, pass(function(tab) { + assertEq(tab.title, 'executeScript1'); + })); + }); + }, + + function insertCSSTextShouldSucceed() { + var css_file = {}; + css_file.code = "p {display:none;}"; + chrome.tabs.insertCSS(tabId, css_file, function() { + var script_file = {}; + script_file.file = 'script3.js'; + chrome.tabs.executeScript(tabId, script_file, function() { + chrome.tabs.get(tabId, pass(function(tab) { + assertEq(tab.title, 'none'); + })); + }); + }); + }, + + function insertCSSFileShouldSucceed() { + var css_file = {}; + css_file.file = '1.css'; + chrome.tabs.insertCSS(tabId, css_file, function() { + var script_file = {}; + script_file.file = 'script2.js'; + chrome.tabs.executeScript(tabId, script_file, function() { + chrome.tabs.get(tabId, pass(function(tab) { + assertEq(tab.title, 'block'); + })); + }); + }); + }, + + function executeJavaScriptCodeShouldFail() { + testingFailure = true; + chrome.tabs.update(tabId, { url: testFailureUrl }, function() { + var script_file = {}; + script_file.code = "document.title = 'executeScript';"; + chrome.tabs.executeScript(tabId, script_file, fail( + 'Cannot access contents of url "' + testFailureUrl + + '". Extension manifest must request permission to access this ' + + 'host.')); + }); + } + ]); +}); + +chrome.tabs.create({ url: testUrl }); + +</script> diff --git a/chrome/test/data/extensions/api_test/executescript/test_executescript.html b/chrome/test/data/extensions/api_test/executescript/test_executescript.html new file mode 100644 index 0000000..0f13642 --- /dev/null +++ b/chrome/test/data/extensions/api_test/executescript/test_executescript.html @@ -0,0 +1,5 @@ +<html> + <title>test</title> + <b id='test1'>text</b> + <p id='test2'>text</p> +</html> |