diff options
-rw-r--r-- | chrome/browser/automation/automation_provider.cc | 2 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_observers.cc | 13 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_observers.h | 3 | ||||
-rw-r--r-- | chrome/browser/automation/testing_automation_provider.cc | 134 | ||||
-rw-r--r-- | chrome/browser/automation/testing_automation_provider.h | 16 | ||||
-rw-r--r-- | chrome/test/data/extensions/js_injection_background/bg.html | 10 | ||||
-rw-r--r-- | chrome/test/data/extensions/js_injection_background/manifest.json | 10 | ||||
-rw-r--r-- | chrome/test/functional/execute_javascript.py | 42 | ||||
-rw-r--r-- | chrome/test/functional/memory.py | 2 | ||||
-rw-r--r-- | chrome/test/functional/process_count.py | 3 | ||||
-rw-r--r-- | chrome/test/pyautolib/pyauto.py | 51 |
11 files changed, 249 insertions, 37 deletions
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 80530abd..707c75f 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -781,6 +781,7 @@ void AutomationProvider::InstallExtension( // The observer will delete itself when done. new ExtensionReadyNotificationObserver( manager, + service, this, AutomationMsg_InstallExtension::ID, reply_message); @@ -826,6 +827,7 @@ void AutomationProvider::EnableExtension(int extension_handle, // The observer will delete itself when done. new ExtensionReadyNotificationObserver( manager, + service, this, AutomationMsg_EnableExtension::ID, reply_message); diff --git a/chrome/browser/automation/automation_provider_observers.cc b/chrome/browser/automation/automation_provider_observers.cc index 3f2a306..c62dd8a 100644 --- a/chrome/browser/automation/automation_provider_observers.cc +++ b/chrome/browser/automation/automation_provider_observers.cc @@ -28,6 +28,7 @@ #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_tabs_module.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/history/history_types.h" @@ -67,6 +68,7 @@ #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/tab_contents.h" +#include "content/common/json_value_serializer.h" #include "content/common/notification_service.h" #include "googleurl/src/gurl.h" #include "ui/gfx/codec/png_codec.h" @@ -523,9 +525,10 @@ void ExtensionUninstallObserver::Observe( } ExtensionReadyNotificationObserver::ExtensionReadyNotificationObserver( - ExtensionProcessManager* manager, AutomationProvider* automation, int id, - IPC::Message* reply_message) + ExtensionProcessManager* manager, ExtensionService* service, + AutomationProvider* automation, int id, IPC::Message* reply_message) : manager_(manager), + service_(service), automation_(automation->AsWeakPtr()), id_(id), reply_message_(reply_message), @@ -562,6 +565,12 @@ void ExtensionReadyNotificationObserver::Observe( extension_ = Details<const Extension>(details).ptr(); if (!DidExtensionHostsStopLoading(manager_)) return; + // For some reason, the background ExtensionHost is not yet + // created at this point so just checking whether all ExtensionHosts + // are loaded is not sufficient. If background page is not ready, + // we wait for NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING. + if(!service_->IsBackgroundPageReady(extension_)) + return; break; case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED: diff --git a/chrome/browser/automation/automation_provider_observers.h b/chrome/browser/automation/automation_provider_observers.h index d6054b8..f7358d7 100644 --- a/chrome/browser/automation/automation_provider_observers.h +++ b/chrome/browser/automation/automation_provider_observers.h @@ -57,6 +57,7 @@ class BalloonCollection; class Browser; class Extension; class ExtensionProcessManager; +class ExtensionService; class NavigationController; class Profile; class RenderViewHost; @@ -313,6 +314,7 @@ class ExtensionUninstallObserver : public NotificationObserver { class ExtensionReadyNotificationObserver : public NotificationObserver { public: ExtensionReadyNotificationObserver(ExtensionProcessManager* manager, + ExtensionService* service, AutomationProvider* automation, int id, IPC::Message* reply_message); @@ -326,6 +328,7 @@ class ExtensionReadyNotificationObserver : public NotificationObserver { private: NotificationRegistrar registrar_; ExtensionProcessManager* manager_; + ExtensionService* service_; base::WeakPtr<AutomationProvider> automation_; int id_; scoped_ptr<IPC::Message> reply_message_; diff --git a/chrome/browser/automation/testing_automation_provider.cc b/chrome/browser/automation/testing_automation_provider.cc index 48cc884..233cc0e 100644 --- a/chrome/browser/automation/testing_automation_provider.cc +++ b/chrome/browser/automation/testing_automation_provider.cc @@ -45,7 +45,9 @@ #include "chrome/browser/debugger/devtools_window.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/save_package_file_picker.h" +#include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/history/top_sites.h" @@ -104,6 +106,7 @@ #include "content/browser/tab_contents/interstitial_page.h" #include "content/common/common_param_traits.h" #include "content/common/notification_service.h" +#include "content/common/view_types.h" #include "net/base/cookie_store.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" #include "ui/base/events.h" @@ -1291,6 +1294,30 @@ void TestingAutomationProvider::GetFullscreenBubbleVisibility(int handle, } } +namespace { + +void ExecuteJavascriptInRenderViewFrame( + const string16& frame_xpath, + const string16& script, + IPC::Message* reply_message, + RenderViewHost* render_view_host) { + // Set the routing id of this message with the controller. + // This routing id needs to be remembered for the reverse + // communication while sending back the response of + // this javascript execution. + std::string set_automation_id; + base::SStringPrintf(&set_automation_id, + "window.domAutomationController.setAutomationId(%d);", + reply_message->routing_id()); + + render_view_host->ExecuteJavascriptInWebFrame( + frame_xpath, UTF8ToUTF16(set_automation_id)); + render_view_host->ExecuteJavascriptInWebFrame( + frame_xpath, script); +} + +} // namespace + void TestingAutomationProvider::ExecuteJavascript( int handle, const std::wstring& frame_xpath, @@ -1303,20 +1330,10 @@ void TestingAutomationProvider::ExecuteJavascript( return; } - // Set the routing id of this message with the controller. - // This routing id needs to be remembered for the reverse - // communication while sending back the response of - // this javascript execution. - std::string set_automation_id; - base::SStringPrintf(&set_automation_id, - "window.domAutomationController.setAutomationId(%d);", - reply_message->routing_id()); - new DomOperationMessageSender(this, reply_message, false); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - WideToUTF16Hack(frame_xpath), UTF8ToUTF16(set_automation_id)); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - WideToUTF16Hack(frame_xpath), WideToUTF16Hack(script)); + ExecuteJavascriptInRenderViewFrame(WideToUTF16Hack(frame_xpath), + WideToUTF16Hack(script), reply_message, + tab_contents->render_view_host()); } void TestingAutomationProvider::GetConstrainedWindowCount(int handle, @@ -2164,6 +2181,8 @@ void TestingAutomationProvider::SendJSONRequest(int handle, &TestingAutomationProvider::NavigateToURL; handler_map["ExecuteJavascript"] = &TestingAutomationProvider::ExecuteJavascriptJSON; + handler_map["ExecuteJavascriptInRenderView"] = + &TestingAutomationProvider::ExecuteJavascriptInRenderView; handler_map["GoForward"] = &TestingAutomationProvider::GoForward; handler_map["GoBack"] = @@ -2771,7 +2790,7 @@ void TestingAutomationProvider::GetBrowserInfo( // Add all extension processes in a list of dictionaries, one dictionary // item per extension process. - ListValue* extension_processes = new ListValue; + ListValue* extension_views = new ListValue; ProfileManager* profile_manager = g_browser_process->profile_manager(); std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles()); for (size_t i = 0; i < profiles.size(); ++i) { @@ -2787,13 +2806,43 @@ void TestingAutomationProvider::GetBrowserInfo( continue; DictionaryValue* item = new DictionaryValue; item->SetString("name", ex_host->extension()->name()); + item->SetString("extension_id", ex_host->extension()->id()); item->SetInteger( "pid", base::GetProcId(ex_host->render_process_host()->GetHandle())); - extension_processes->Append(item); + DictionaryValue* view = new DictionaryValue; + view->SetInteger( + "render_process_id", + ex_host->render_process_host()->id()); + view->SetInteger( + "render_view_id", + ex_host->render_view_host()->routing_id()); + item->Set("view", view); + std::string type; + switch (ex_host->extension_host_type()) { + case ViewType::EXTENSION_BACKGROUND_PAGE: + type = "EXTENSION_BACKGROUND_PAGE"; + break; + case ViewType::EXTENSION_POPUP: + type = "EXTENSION_POPUP"; + break; + case ViewType::EXTENSION_INFOBAR: + type = "EXTENSION_INFOBAR"; + break; + case ViewType::EXTENSION_DIALOG: + type = "EXTENSION_DIALOG"; + break; + default: + type = "unknown"; + break; + } + item->SetString("view_type", type); + item->SetString("url", ex_host->GetURL().spec()); + item->SetBoolean("loaded", ex_host->did_stop_loading()); + extension_views->Append(item); } } - return_value->Set("extension_processes", extension_processes); + return_value->Set("extension_views", extension_views); AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); } @@ -5735,20 +5784,49 @@ void TestingAutomationProvider::ExecuteJavascriptJSON( return; } - // Set the routing id of this message with the controller. - // This routing id needs to be remembered for the reverse - // communication while sending back the response of - // this javascript execution. - std::string set_automation_id; - base::SStringPrintf(&set_automation_id, - "window.domAutomationController.setAutomationId(%d);", - reply_message->routing_id()); + new DomOperationMessageSender(this, reply_message, true); + ExecuteJavascriptInRenderViewFrame(frame_xpath, javascript, reply_message, + tab_contents->render_view_host()); +} + +void TestingAutomationProvider::ExecuteJavascriptInRenderView( + DictionaryValue* args, + IPC::Message* reply_message) { + string16 frame_xpath, javascript, extension_id, url_text; + std::string error; + int render_process_id, render_view_id; + if (!args->GetString("frame_xpath", &frame_xpath)) { + AutomationJSONReply(this, reply_message) + .SendError("'frame_xpath' missing or invalid"); + return; + } + if (!args->GetString("javascript", &javascript)) { + AutomationJSONReply(this, reply_message) + .SendError("'javascript' missing or invalid"); + return; + } + if (!args->GetInteger("view.render_process_id", &render_process_id)) { + AutomationJSONReply(this, reply_message) + .SendError("'view.render_process_id' missing or invalid"); + return; + } + if (!args->GetInteger("view.render_view_id", &render_view_id)) { + AutomationJSONReply(this, reply_message) + .SendError("'view.render_view_id' missing or invalid"); + return; + } + + RenderViewHost* rvh = RenderViewHost::FromID(render_process_id, + render_view_id); + if(!rvh) { + AutomationJSONReply(this, reply_message).SendError( + "A RenderViewHost object was not found with the given view ID."); + return; + } new DomOperationMessageSender(this, reply_message, true); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - frame_xpath, UTF8ToUTF16(set_automation_id)); - tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( - frame_xpath, javascript); + ExecuteJavascriptInRenderViewFrame(frame_xpath, javascript, reply_message, + rvh); } void TestingAutomationProvider::GoForward( diff --git a/chrome/browser/automation/testing_automation_provider.h b/chrome/browser/automation/testing_automation_provider.h index fbd486c..653e1a4 100644 --- a/chrome/browser/automation/testing_automation_provider.h +++ b/chrome/browser/automation/testing_automation_provider.h @@ -884,6 +884,22 @@ class TestingAutomationProvider : public AutomationProvider, void ExecuteJavascriptJSON( base::DictionaryValue* args, IPC::Message* reply_message); + // Executes javascript in the specified frame of a render view. + // Uses the JSON interface. Waits for a result from the + // |DOMAutomationController|. The javascript must send a string. + // Example: + // input: { "view": { + // "render_process_id": 1, + // "render_view_id": 2, + // } + // "frame_xpath": "//frames[1]", + // "javascript": + // "window.domAutomationController.send(window.name)", + // } + // output: { "result": "My Window Name" } + void ExecuteJavascriptInRenderView( + base::DictionaryValue* args, IPC::Message* reply_message); + // Goes forward in the specified tab. Uses the JSON interface. // Example: // input: { "windex": 1, "tab_index": 1 } diff --git a/chrome/test/data/extensions/js_injection_background/bg.html b/chrome/test/data/extensions/js_injection_background/bg.html new file mode 100644 index 0000000..b629c2a --- /dev/null +++ b/chrome/test/data/extensions/js_injection_background/bg.html @@ -0,0 +1,10 @@ +<html> + <script> + var bool_var = true; + var int_var = 42; + var str_var = "foo"; + </script> + <body> + <input id="myinput"></input> + </body> +</html> diff --git a/chrome/test/data/extensions/js_injection_background/manifest.json b/chrome/test/data/extensions/js_injection_background/manifest.json new file mode 100644 index 0000000..a6d3076 --- /dev/null +++ b/chrome/test/data/extensions/js_injection_background/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "js_injection_background", + "description": "Tests JS injection into an extension's background page. + The name of a DOM node in the background page is returned and verified.", + "version": "0.1", + "background_page": "bg.html", + "browser_action": { + "default_title": "Browser Action" + } +} diff --git a/chrome/test/functional/execute_javascript.py b/chrome/test/functional/execute_javascript.py index 1173d4a..af7a9d0 100644 --- a/chrome/test/functional/execute_javascript.py +++ b/chrome/test/functional/execute_javascript.py @@ -12,6 +12,11 @@ from pyauto import PyUITest class ExecuteJavascriptTest(PyUITest): + def _GetExtensionInfoById(self, extensions, id): + for x in extensions: + if x['id'] == id: + return x + return None def testExecuteJavascript(self): self.NavigateToURL(self.GetFileURLForDataPath( @@ -28,6 +33,43 @@ class ExecuteJavascriptTest(PyUITest): v = self.GetDOMValue('document.getElementById("myinput").nodeName') self.assertEqual(v, 'INPUT') + def testExecuteJavascriptInExtension(self): + """Test we can inject JavaScript into an extension.""" + dir_path = os.path.abspath( + os.path.join(self.DataDir(), 'extensions', 'js_injection_background')) + ext_id = self.InstallExtension(dir_path, False); + self.assertTrue(ext_id, msg='Failed to install extension: %s.' % dir_path) + + # Verify extension is enabled. + extension = self._GetExtensionInfoById(self.GetExtensionsInfo(), ext_id) + self.assertTrue(extension['is_enabled'], + msg='Extension was disabled by default') + + # Get the background page's view. + info = self.GetBrowserInfo()['extension_views'] + view = [x for x in info if + x['extension_id'] == ext_id and + x['view_type'] == 'EXTENSION_BACKGROUND_PAGE'] + self.assertEqual(1, len(view), + msg='problematic background view: view = %s.' % view) + background_view = view[0] + + # Get values from background page's DOM + v = self.ExecuteJavascriptInRenderView( + 'window.domAutomationController.send(' + 'document.getElementById("myinput").nodeName)', background_view['view']) + self.assertEqual(v, 'INPUT', + msg='Incorrect value returned (v = %s).' % v) + v = self.ExecuteJavascriptInRenderView( + 'window.domAutomationController.send(bool_var)', background_view['view']) + self.assertEqual(v, True, msg='Incorrect value returned (v = %s).' % v) + v = self.ExecuteJavascriptInRenderView( + 'window.domAutomationController.send(int_var)', background_view['view']) + self.assertEqual(v, 42, msg='Incorrect value returned (v = %s).' % v) + v = self.ExecuteJavascriptInRenderView( + 'window.domAutomationController.send(str_var)', background_view['view']) + self.assertEqual(v, 'foo', msg='Incorrect value returned (v = %s).' % v) + if __name__ == '__main__': pyauto_functional.Main() diff --git a/chrome/test/functional/memory.py b/chrome/test/functional/memory.py index a06eb23..bf7e044 100644 --- a/chrome/test/functional/memory.py +++ b/chrome/test/functional/memory.py @@ -56,7 +56,7 @@ class MemoryTest(pyauto.PyUITest): The integer process identifier (PID) for the specified process, or None if the PID cannot be identified. """ - info = self.GetBrowserInfo()['extension_processes'] + info = self.GetBrowserInfo()['extension_views'] pid = [x['pid'] for x in info if x['name'] == '%s' % name] if pid: return pid[0] diff --git a/chrome/test/functional/process_count.py b/chrome/test/functional/process_count.py index f870e10..f45464e 100644 --- a/chrome/test/functional/process_count.py +++ b/chrome/test/functional/process_count.py @@ -27,8 +27,7 @@ class ProcessCountTest(pyauto.PyUITest): actual_count = ( 1 + # Browser process. sum([len(tab_info['tabs']) for tab_info in info['windows']]) + - len(info['child_processes']) + - len(info['extension_processes'])) + len(info['child_processes']) + len(info['extension_views'])) self.assertEqual(actual_count, expected_count, msg='Number of processes should be %d, but was %d.' % diff --git a/chrome/test/pyautolib/pyauto.py b/chrome/test/pyautolib/pyauto.py index 8a2e3d4..fe2143a 100644 --- a/chrome/test/pyautolib/pyauto.py +++ b/chrome/test/pyautolib/pyauto.py @@ -1253,10 +1253,16 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase): u'child_processes': [ { u'name': u'Shockwave Flash', u'pid': 93766, u'type': u'Plug-in'}], - # There's one extension process per extension. - u'extension_processes': [ - { u'name': u'Webpage Screenshot', u'pid': 93938}, - { u'name': u'Google Voice (by Google)', u'pid': 93852}], + u'extension_views': [ { + u'name': u'Webpage Screenshot', + u'pid': 93938, + u'extension_id': u'dgcoklnmbeljaehamekjpeidmbicddfj', + u'url': u'chrome-extension://dgcoklnmbeljaehamekjpeidmbicddfj/' + 'bg.html', + u'view': { + u'render_process_id': 2, + u'render_view_id': 1}, + u'view_type': u'EXTENSION_BACKGROUND_PAGE'}] u'properties': { u'BrowserProcessExecutableName': u'Chromium', u'BrowserProcessExecutablePath': u'Chromium.app/Contents/MacOS/' @@ -2234,6 +2240,43 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase): } return self._GetResultFromJSONRequest(cmd_dict, windex=windex) + def ExecuteJavascriptInRenderView(self, js, view, frame_xpath=''): + """Executes a script in the specified frame of an render view. + + The invoked javascript function must send a result back via the + domAutomationController.send function, or this function will never return. + + Args: + js: script to be executed + view: A dictionary representing a unique id for the render view as + returned for example by + self.GetBrowserInfo()['extension_views'][]['view']. + Example: + { 'render_process_id': 1, + 'render_view_id' : 2} + + frame_xpath: XPath of the frame to execute the script. Default is no + frame. Example: + '//frames[1]' + + Returns: + a value that was sent back via the domAutomationController.send method + + Raises: + pyauto_errors.JSONInterfaceError if the automation call returns an error. + """ + cmd_dict = { + 'command': 'ExecuteJavascriptInRenderView', + 'javascript' : js, + 'view' : view, + 'frame_xpath' : frame_xpath, + } + result = self._GetResultFromJSONRequest(cmd_dict)['result'] + # Wrap result in an array before deserializing because valid JSON has an + # array or an object as the root. + json_string = '[' + result + ']' + return json.loads(json_string)[0] + def CallJavascriptFunc(self, function, args=[], tab_index=0, windex=0): """Executes a script which calls a given javascript function. |