diff --git a/chrome/browser/extensions/user_script_master.cc b/chrome/browser/extensions/user_script_master.cc index b0552e0fba49968356cda85da9efdeef9bbde52e..69fd746ddbd9999ba88d8a447b28f4e8f4201b7d 100644 --- a/chrome/browser/extensions/user_script_master.cc +++ b/chrome/browser/extensions/user_script_master.cc @@ -166,6 +166,11 @@ void UserScriptMaster::ScriptReloader::LoadScriptsFromDirectory( file = enumerator.Next()) { result->push_back(UserScript()); UserScript& user_script = result->back(); + + // We default standalone user scripts to document-end for better + // Greasemonkey compatibility. + user_script.set_run_location(UserScript::DOCUMENT_END); + // Push single js file in this UserScript. GURL url(std::string(chrome::kUserScriptScheme) + ":/" + net::FilePathToFileURL(file).ExtractFileName()); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index eaec76b60015cfaf2b8d6f042f42c81c9cf77ae3..59946bb9f28670e826262009ee5bf0760cc12219 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -3302,6 +3302,8 @@ 'renderer/renderer_web_database_observer.h', 'renderer/socket_stream_dispatcher.cc', 'renderer/socket_stream_dispatcher.h', + 'renderer/user_script_idle_scheduler.cc', + 'renderer/user_script_idle_scheduler.h', 'renderer/user_script_slave.cc', 'renderer/user_script_slave.h', 'renderer/visitedlink_slave.cc', diff --git a/chrome/common/extensions/docs/content_scripts.html b/chrome/common/extensions/docs/content_scripts.html index 51beb4d5aaa4f0f109a4cf66ce2878f200993c4d..feba6ba489b9a7639ce8763a3679ab4ecab1d970 100644 --- a/chrome/common/extensions/docs/content_scripts.html +++ b/chrome/common/extensions/docs/content_scripts.html @@ -346,7 +346,24 @@ learn about the <tr> <td>run_at</td> <td>string</td> - <td>Optional. Controls when the files in <code>js</code> are injected. Can be <code>"document_start"</code> or <code>"document_end"</code>. Defaults to <code>"document_end"</code>. In the case of <code>"document_start"</code>, the files are injected after any files from <code>"css"</code>, but before any other DOM is constructed or any other script is run. In the case of <code>"document_end"</code>, the files are injected after the DOM is complete, but before subresources like images and frames have necessarily loaded.</td> + <td>Optional. Controls when the files in <code>js</code> are injected. Can be <code>"document_start"</code>, <code>"document_end"</code>, or <code>"document_idle"</code>. Defaults to <code>"document_idle"</code>. + + <br><br> + + In the case of <code>"document_start"</code>, the files are injected after any files from <code>"css"</code>, but before any other DOM is constructed or any other script is run. + + <br><br> + + In the case of <code>"document_end"</code>, the files are injected immediately after the DOM is complete, but before subresources like images and frames have loaded. + + <br><br> + + In the case of <code>"document_idle"</code>, the browser chooses a time to inject scripts between <code>"document_end"</code> and immediately after the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#handler-onload">window.onload</a></code> event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed. + + <br><br> + + <b>NOTE:</b> In <code>document_idle</code>, content scripts may not necessarily receive the window.onload event, because they may run after it has + already fired. In most cases, listening for the onload event is unnecessary for content scripts running at <code>document_idle</code> because they are guaranteed to run after the DOM is complete. If your script definitely needs to run after <code>window.onload</code> you can check if it has already fired by using the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#dom-document-readystate">document.readyState</a></code> property.</td> </tr> </tbody></table> diff --git a/chrome/common/extensions/docs/static/content_scripts.html b/chrome/common/extensions/docs/static/content_scripts.html index cbd738f3ae5fe23afc471007a802d77125e79cde..9f1edf18af2fab383c6a331a15359ea94ba3d250 100644 --- a/chrome/common/extensions/docs/static/content_scripts.html +++ b/chrome/common/extensions/docs/static/content_scripts.html @@ -104,7 +104,24 @@ learn about the <tr> <td>run_at</td> <td>string</td> - <td>Optional. Controls when the files in <code>js</code> are injected. Can be <code>"document_start"</code> or <code>"document_end"</code>. Defaults to <code>"document_end"</code>. In the case of <code>"document_start"</code>, the files are injected after any files from <code>"css"</code>, but before any other DOM is constructed or any other script is run. In the case of <code>"document_end"</code>, the files are injected after the DOM is complete, but before subresources like images and frames have necessarily loaded.</td> + <td>Optional. Controls when the files in <code>js</code> are injected. Can be <code>"document_start"</code>, <code>"document_end"</code>, or <code>"document_idle"</code>. Defaults to <code>"document_idle"</code>. + + <br><br> + + In the case of <code>"document_start"</code>, the files are injected after any files from <code>"css"</code>, but before any other DOM is constructed or any other script is run. + + <br><br> + + In the case of <code>"document_end"</code>, the files are injected immediately after the DOM is complete, but before subresources like images and frames have loaded. + + <br><br> + + In the case of <code>"document_idle"</code>, the browser chooses a time to inject scripts between <code>"document_end"</code> and immediately after the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#handler-onload">window.onload</a></code> event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed. + + <br><br> + + <b>NOTE:</b> In <code>document_idle</code>, content scripts may not necessarily receive the window.onload event, because they may run after it has + already fired. In most cases, listening for the onload event is unnecessary for content scripts running at <code>document_idle</code> because they are guaranteed to run after the DOM is complete. If your script definitely needs to run after <code>window.onload</code> you can check if it has already fired by using the <code><a href="http://www.whatwg.org/specs/web-apps/current-work/#dom-document-readystate">document.readyState</a></code> property.</td> </tr> </table> diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 429dfb5ea9e8dd38c0e4bbb360d7d435374475ee..c6172321800fedc4a807fbb9957394f9591cb0b3 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -208,6 +208,8 @@ bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script, result->set_run_location(UserScript::DOCUMENT_START); } else if (run_location == values::kRunAtDocumentEnd) { result->set_run_location(UserScript::DOCUMENT_END); + } else if (run_location == values::kRunAtDocumentIdle) { + result->set_run_location(UserScript::DOCUMENT_IDLE); } else { *error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidRunAt, IntToString(definition_index)); diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index 8259009f54f8e876f0531eba7a95194ce33031bb..59e7c1492ebaf5a58aae6bec31ce0063075cccb4 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -52,6 +52,7 @@ const wchar_t* kOptionsPage = L"options_page"; namespace extension_manifest_values { const char* kRunAtDocumentStart = "document_start"; const char* kRunAtDocumentEnd = "document_end"; +const char* kRunAtDocumentIdle = "document_idle"; const char* kPageActionTypeTab = "tab"; const char* kPageActionTypePermanent = "permanent"; } // namespace extension_manifest_values diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 7f44bf6661a5588e4d16bcaf32a61fc4abab011a..b7826a79cbf662e3934480f47bb51ff4321fc1be 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -54,6 +54,7 @@ namespace extension_manifest_keys { namespace extension_manifest_values { extern const char* kRunAtDocumentStart; extern const char* kRunAtDocumentEnd; + extern const char* kRunAtDocumentIdle; extern const char* kPageActionTypeTab; extern const char* kPageActionTypePermanent; } // namespace extension_manifest_values diff --git a/chrome/common/extensions/user_script.h b/chrome/common/extensions/user_script.h index 28b3aaec1928117c1bcda7f21f49eb41a688ddf0..bdd1e5435fdd7f201eee32617192cf5195af2c22 100644 --- a/chrome/common/extensions/user_script.h +++ b/chrome/common/extensions/user_script.h @@ -28,6 +28,10 @@ class UserScript { // anything else happens. DOCUMENT_END, // After the entire document is parsed. Same as // DOMContentLoaded. + DOCUMENT_IDLE, // Sometime after DOMContentLoaded, as soon as the document + // is "idle". Currently this uses the simple heuristic of: + // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no + // particular injection point is guaranteed. RUN_LOCATION_LAST // Leave this as the last item. }; @@ -88,9 +92,10 @@ class UserScript { typedef std::vector<File> FileList; - // Constructor. Default the run location to document end, which is like - // Greasemonkey and probably more useful for typical scripts. - UserScript() : run_location_(DOCUMENT_END) {} + // Constructor. Default the run location to document idle, which is similar + // to Greasemonkey but should result in better page load times for fast- + // loading pages. + UserScript() : run_location_(DOCUMENT_IDLE) {} // The place in the document to run the script. RunLocation run_location() const { return run_location_; } diff --git a/chrome/common/extensions/user_script_unittest.cc b/chrome/common/extensions/user_script_unittest.cc index b171f4e13dbaa2d6e67e9196557c74af0bf03a4a..58ef77e4c490737dda62527e3c92a127caf3a3a7 100644 --- a/chrome/common/extensions/user_script_unittest.cc +++ b/chrome/common/extensions/user_script_unittest.cc @@ -122,5 +122,5 @@ TEST(UserScriptTest, Pickle) { TEST(UserScriptTest, Defaults) { UserScript script; - ASSERT_EQ(UserScript::DOCUMENT_END, script.run_location()); + ASSERT_EQ(UserScript::DOCUMENT_IDLE, script.run_location()); } diff --git a/chrome/renderer/navigation_state.h b/chrome/renderer/navigation_state.h index 46031ddd1bedba54a58bb0092beb9201b58a20e4..394aee9e995b69dc3de43d18a947e80c3a912da2 100644 --- a/chrome/renderer/navigation_state.h +++ b/chrome/renderer/navigation_state.h @@ -8,6 +8,7 @@ #include "base/scoped_ptr.h" #include "base/time.h" #include "chrome/common/page_transition_types.h" +#include "chrome/renderer/user_script_idle_scheduler.h" #include "webkit/api/public/WebDataSource.h" #include "webkit/glue/alt_error_page_resource_fetcher.h" #include "webkit/glue/password_form.h" @@ -33,6 +34,13 @@ class NavigationState : public WebKit::WebDataSource::ExtraData { return static_cast<NavigationState*>(ds->extraData()); } + UserScriptIdleScheduler* user_script_idle_scheduler() { + return user_script_idle_scheduler_.get(); + } + void set_user_script_idle_scheduler(UserScriptIdleScheduler* scheduler) { + user_script_idle_scheduler_.reset(scheduler); + } + // Contains the page_id for this navigation or -1 if there is none yet. int32 pending_page_id() const { return pending_page_id_; } @@ -173,7 +181,8 @@ class NavigationState : public WebKit::WebDataSource::ExtraData { request_committed_(false), is_content_initiated_(is_content_initiated), pending_page_id_(pending_page_id), - postpone_loading_data_(false) { + postpone_loading_data_(false), + user_script_idle_scheduler_(NULL) { } PageTransition::Type transition_type_; @@ -195,6 +204,7 @@ class NavigationState : public WebKit::WebDataSource::ExtraData { std::string security_info_; bool postpone_loading_data_; std::string postponed_data_; + scoped_ptr<UserScriptIdleScheduler> user_script_idle_scheduler_; DISALLOW_COPY_AND_ASSIGN(NavigationState); }; diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 95f5e349f54cf623dfb79f211f335cc3d51b2af3..ce8a521e7ab0152c8383d4f28b42dcee35d89fda 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -1829,6 +1829,10 @@ void RenderView::willClose(WebFrame* frame) { if (url.SchemeIs("http") || url.SchemeIs("https")) DumpLoadHistograms(); } + + WebDataSource* ds = frame->dataSource(); + NavigationState* navigation_state = NavigationState::FromDataSource(ds); + navigation_state->user_script_idle_scheduler()->Cancel(); } void RenderView::loadURLExternally( @@ -2031,11 +2035,13 @@ void RenderView::didCompleteClientRedirect( void RenderView::didCreateDataSource(WebFrame* frame, WebDataSource* ds) { // The rest of RenderView assumes that a WebDataSource will always have a // non-null NavigationState. - if (pending_navigation_state_.get()) { - ds->setExtraData(pending_navigation_state_.release()); - } else { - ds->setExtraData(NavigationState::CreateContentInitiated()); - } + NavigationState* state = pending_navigation_state_.get() ? + pending_navigation_state_.release() : + NavigationState::CreateContentInitiated(); + + state->set_user_script_idle_scheduler( + new UserScriptIdleScheduler(this, frame)); + ds->setExtraData(state); } void RenderView::didStartProvisionalLoad(WebFrame* frame) { @@ -2250,14 +2256,6 @@ void RenderView::didCreateDocumentElement(WebFrame* frame) { ExtensionProcessBindings::SetViewType(webview(), view_type_); } - 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) { @@ -2292,6 +2290,26 @@ void RenderView::didFinishDocumentLoad(WebFrame* frame) { RenderThread::current()->user_script_slave()->InjectScripts( frame, UserScript::DOCUMENT_END); } + + navigation_state->user_script_idle_scheduler()->DidFinishDocumentLoad(); +} + +void RenderView::OnUserScriptIdleTriggered(WebFrame* frame) { + if (RenderThread::current()) { // Will be NULL during unit tests. + RenderThread::current()->user_script_slave()->InjectScripts( + frame, UserScript::DOCUMENT_IDLE); + } + + WebFrame* main_frame = webview()->mainFrame(); + if (frame == main_frame) { + while (!pending_code_execution_queue_.empty()) { + scoped_refptr<CodeExecutionInfo> info = + pending_code_execution_queue_.front(); + ExecuteCodeImpl(main_frame, info->request_id, info->extension_id, + info->is_js_code, info->code_string); + pending_code_execution_queue_.pop(); + } + } } void RenderView::didHandleOnloadEvents(WebFrame* frame) { @@ -2307,6 +2325,7 @@ void RenderView::didFinishLoad(WebFrame* frame) { NavigationState* navigation_state = NavigationState::FromDataSource(ds); DCHECK(navigation_state); navigation_state->set_finish_load_time(Time::Now()); + navigation_state->user_script_idle_scheduler()->DidFinishLoad(); } void RenderView::didChangeLocationWithinPage( @@ -3662,28 +3681,40 @@ void RenderView::OnSetEditCommandsForNextKeyEvent( 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()->mainFrame() : NULL; if (!main_frame) { Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, false)); return; } + WebDataSource* ds = main_frame->dataSource(); + NavigationState* navigation_state = NavigationState::FromDataSource(ds); + if (!navigation_state->user_script_idle_scheduler()->has_run()) { + scoped_refptr<CodeExecutionInfo> info = new CodeExecutionInfo( + request_id, extension_id, is_js_code, code_string); + pending_code_execution_queue_.push(info); + return; + } + + ExecuteCodeImpl(main_frame, request_id, extension_id, is_js_code, + code_string); +} + +void RenderView::ExecuteCodeImpl(WebFrame* frame, + int request_id, + const std::string& extension_id, + bool is_js_code, + const std::string& code_string) { if (is_js_code) { std::vector<WebScriptSource> sources; sources.push_back( WebScriptSource(WebString::fromUTF8(code_string))); UserScriptSlave::InsertInitExtensionCode(&sources, extension_id); - main_frame->executeScriptInIsolatedWorld( + frame->executeScriptInIsolatedWorld( UserScriptSlave::GetIsolatedWorldId(extension_id), &sources.front(), sources.size(), EXTENSION_GROUP_CONTENT_SCRIPTS); } else { - main_frame->insertStyleText(WebString::fromUTF8(code_string), WebString()); + frame->insertStyleText(WebString::fromUTF8(code_string), WebString()); } Send(new ViewMsg_ExecuteCodeFinished(routing_id_, request_id, true)); diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index 900cb2c5603e1c97b0e51df5bfce3d5cb556734e..814d8bd45faf269b1ba766fa419cc30edec7b8f0 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -435,6 +435,10 @@ class RenderView : public RenderWidget, // Sends a message and runs a nested message loop. bool SendAndRunNestedMessageLoop(IPC::SyncMessage* message); + // Called when the "idle" user script state has been reached. See + // UserScript::DOCUMENT_IDLE. + void OnUserScriptIdleTriggered(WebKit::WebFrame* frame); + protected: // RenderWidget overrides: virtual void Close(); @@ -620,6 +624,11 @@ class RenderView : public RenderWidget, const std::string& extension_id, bool is_js_code, const std::string& code_string); + void ExecuteCodeImpl(WebKit::WebFrame* frame, + 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( diff --git a/chrome/renderer/user_script_idle_scheduler.cc b/chrome/renderer/user_script_idle_scheduler.cc new file mode 100644 index 0000000000000000000000000000000000000000..2468bf13e4ded8254c356ba828c21fdef2ac788a --- /dev/null +++ b/chrome/renderer/user_script_idle_scheduler.cc @@ -0,0 +1,47 @@ +// 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/renderer/user_script_idle_scheduler.h" + +#include "base/message_loop.h" +#include "chrome/renderer/render_view.h" + +namespace { +// The length of time to wait after the DOM is complete to try and run user +// scripts. +const int kUserScriptIdleTimeoutMs = 200; +} + +UserScriptIdleScheduler::UserScriptIdleScheduler(RenderView* view, + WebKit::WebFrame* frame) + : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), view_(view), + frame_(frame), has_run_(false) { +} + +void UserScriptIdleScheduler::DidFinishDocumentLoad() { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + method_factory_.NewRunnableMethod(&UserScriptIdleScheduler::MaybeRun), + kUserScriptIdleTimeoutMs); +} + +void UserScriptIdleScheduler::DidFinishLoad() { + // Ensure that running scripts does not keep any progress UI running. + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod(&UserScriptIdleScheduler::MaybeRun)); +} + +void UserScriptIdleScheduler::Cancel() { + view_ = NULL; + frame_ = NULL; +} + +void UserScriptIdleScheduler::MaybeRun() { + if (!view_) + return; + + DCHECK(frame_); + view_->OnUserScriptIdleTriggered(frame_); + Cancel(); + has_run_ = true; +} diff --git a/chrome/renderer/user_script_idle_scheduler.h b/chrome/renderer/user_script_idle_scheduler.h new file mode 100644 index 0000000000000000000000000000000000000000..772d6526ecb1768516bfa3460b904b7f42ef5729 --- /dev/null +++ b/chrome/renderer/user_script_idle_scheduler.h @@ -0,0 +1,58 @@ +// 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_RENDERER_USER_SCRIPT_IDLE_SCHEDULER_H_ +#define CHROME_RENDERER_USER_SCRIPT_IDLE_SCHEDULER_H_ + +#include "base/task.h" + +class RenderView; + +namespace WebKit { +class WebFrame; +} + +// Implements support for injecting scripts at "document idle". Currently, +// determining idleness is simple: it is whichever of the following happens +// first: +// +// a) When the initial DOM for a page is complete + kUserScriptIdleTimeout, +// b) or when the page has completely loaded including all subresources. +// +// The intent of this mechanism is to prevent user scripts from slowing down +// fast pages (run after load), while still allowing them to run relatively +// timelily for pages with lots of slow subresources. +class UserScriptIdleScheduler { + public: + UserScriptIdleScheduler(RenderView* view, WebKit::WebFrame* frame); + + bool has_run() { return has_run_; } + + // Called when the DOM has been completely constructed. + void DidFinishDocumentLoad(); + + // Called when the document has completed loading. + void DidFinishLoad(); + + // Called when the client has gone away and we should no longer run scripts. + void Cancel(); + + private: + // Run user scripts, except if they've already run for this frame, or the + // frame has been destroyed. + void MaybeRun(); + + ScopedRunnableMethodFactory<UserScriptIdleScheduler> method_factory_; + + // The RenderView we will call back to when it is time to run scripts. + RenderView* view_; + + // The Frame we will run scripts in. + WebKit::WebFrame* frame_; + + // Whether we have already run scripts. + bool has_run_; +}; + +#endif // CHROME_RENDERER_USER_SCRIPT_IDLE_SCHEDULER_H_ diff --git a/chrome/test/data/extensions/good/Extensions/bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0/page.js b/chrome/test/data/extensions/good/Extensions/bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0/page.js index 34d27fc654bde11b1cd333c5753e78f3ab7ae655..24f7a9ac3ec0d2634f653b3e75d44081cd9be4f1 100644 --- a/chrome/test/data/extensions/good/Extensions/bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0/page.js +++ b/chrome/test/data/extensions/good/Extensions/bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0/page.js @@ -3,28 +3,24 @@ if (typeof(contentWindow) != 'undefined') { win = contentWindow; } -win.onload = function() { - // Do this in an onload handler because I'm not sure if chrome.extension - // is available before then. - chrome.extension.onConnect.addListener(function(port) { - console.log('connected'); - port.onMessage.addListener(function(msg) { - console.log('got ' + msg); - if (msg.testPostMessage) { - port.postMessage({success: true}); - } else if (msg.testPostMessageFromTab) { - testPostMessageFromTab(port); - } else if (msg.testDisconnect) { - port.disconnect(); - } else if (msg.testDisconnectOnClose) { - win.location = "about:blank"; - } else if (msg.testPortName) { - port.postMessage({portName:port.name}); - } - // Ignore other messages since they are from us. - }); +chrome.extension.onConnect.addListener(function(port) { + console.log('connected'); + port.onMessage.addListener(function(msg) { + console.log('got ' + msg); + if (msg.testPostMessage) { + port.postMessage({success: true}); + } else if (msg.testPostMessageFromTab) { + testPostMessageFromTab(port); + } else if (msg.testDisconnect) { + port.disconnect(); + } else if (msg.testDisconnectOnClose) { + win.location = "about:blank"; + } else if (msg.testPortName) { + port.postMessage({portName:port.name}); + } + // Ignore other messages since they are from us. }); -}; +}); // Tests that postMessage to the extension and its response works. function testPostMessageFromTab(origPort) {