diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 23:33:45 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 23:33:45 +0000 |
commit | 7dfa463ed34c7ebefa224b2079ce48fa49eef680 (patch) | |
tree | 17e4b1ed9b6786179c00852dd7ea834e552beaf2 | |
parent | 5ee5970eb2109cd58e2269d794b18c2dc28ff9a7 (diff) | |
download | chromium_src-7dfa463ed34c7ebefa224b2079ce48fa49eef680.zip chromium_src-7dfa463ed34c7ebefa224b2079ce48fa49eef680.tar.gz chromium_src-7dfa463ed34c7ebefa224b2079ce48fa49eef680.tar.bz2 |
Re-land alexbost's experimental offscreenTabs API.
Original code review: http://codereview.chromium.org/7720002/
A followup code review: http://chromiumcodereview.appspot.com/9150052/
This includes some refactoring to simplify and reduce the code size:
- sharing more code between tabs and offscreenTabs
- splitting up and fixing the browser tests
- forbidding use of the API from a background page
BUG=110833
TEST=OffscreenTabsApiTest.*
Review URL: https://chromiumcodereview.appspot.com/9234042
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127833 0039d316-1c4b-4281-b951-d872f2087c98
41 files changed, 3760 insertions, 106 deletions
diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc new file mode 100644 index 0000000..9fa4758 --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc @@ -0,0 +1,833 @@ +// Copyright (c) 2012 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/api/offscreen_tabs/offscreen_tabs_api.h" + +#include <algorithm> +#include <vector> + +#include "base/hash_tables.h" +#include "base/json/json_writer.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/lazy_instance.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.h" +#include "chrome/browser/extensions/extension_event_names.h" +#include "chrome/browser/extensions/extension_event_router.h" +#include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/extensions/extension_tabs_module_constants.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "chrome/browser/ui/window_sizer.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_error_utils.h" +#include "chrome/common/extensions/extension_messages.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" + +using content::NavigationController; +using content::NotificationDetails; +using content::NotificationSource; +using content::WebContents; +using WebKit::WebInputEvent; + +namespace keys = extensions::offscreen_tabs_constants; +namespace tabs_keys = extension_tabs_module_constants; +namespace events = extension_event_names; + +namespace { + +class ParentTab; + +// This class is responsible for the life cycle of an offscreen tab. +class OffscreenTab : public content::NotificationObserver { + public: + OffscreenTab(); + virtual ~OffscreenTab(); + void Init(const GURL& url, + const int width, + const int height, + Profile* profile, + ParentTab* parent_tab); + + TabContentsWrapper* tab_contents() const { + return tab_contents_wrapper_.get(); + } + WebContents* web_contents() const { + return tab_contents()->web_contents(); + } + int GetID() const { return ExtensionTabUtil::GetTabId(web_contents()); } + ParentTab* parent_tab() { return parent_tab_; } + + // Creates a representation of this OffscreenTab for use by the API methods. + // Passes ownership to the caller. + DictionaryValue* CreateValue() const; + + // Navigates the tab to the |url|. + void NavigateToURL(const GURL& url) { + web_contents()->GetController().LoadURL( + url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); + } + + // Adjusts the tab's dimensions to the specified |width| and |height|. + void SetSize(int width, int height) { + // TODO(jstritar): this doesn't seem to work on ChromeOS. + web_contents()->GetView()->SizeContents(gfx::Size(width, height)); + } + + private: + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + content::NotificationRegistrar registrar_; + scoped_ptr<TabContentsWrapper> tab_contents_wrapper_; + ParentTab* parent_tab_; + + DISALLOW_COPY_AND_ASSIGN(OffscreenTab); +}; + +typedef ScopedVector<OffscreenTab> OffscreenTabs; + +// Holds info about a tab that has spawned at least one offscreen tab. +// Each ParentTab keeps track of its child offscreen tabs. The ParentTab is also +// responsible for killing its children when it navigates away or gets closed. +class ParentTab : public content::NotificationObserver { + public: + ParentTab(); + virtual ~ParentTab(); + void Init(WebContents* web_contents, + const std::string& extension_id); + + TabContentsWrapper* tab_contents() { return tab_contents_wrapper_; } + int GetID() { return ExtensionTabUtil::GetTabId(web_contents()); } + WebContents* web_contents() { + return tab_contents()->web_contents(); + } + + // Returns the offscreen tabs spawned by this tab. + const OffscreenTabs& offscreen_tabs() { return offscreen_tabs_; } + const std::string& extension_id() const { return extension_id_; } + + // Tab takes ownership of OffscreenTab. + void AddOffscreenTab(OffscreenTab *tab); + + // Removes the offscreen |tab| and returns true if this parent has no more + // offscreen tabs. + bool RemoveOffscreenTab(OffscreenTab *tab); + + private: + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + content::NotificationRegistrar registrar_; + + TabContentsWrapper* tab_contents_wrapper_; + OffscreenTabs offscreen_tabs_; + std::string extension_id_; + + DISALLOW_COPY_AND_ASSIGN(ParentTab); +}; + +// This map keeps track of all tabs that are happy parents of offscreen tabs. +class OffscreenTabMap { + public: + OffscreenTabMap(); + ~OffscreenTabMap(); + + // Returns true if this map tracks |parent_tab|. + bool ContainsTab(ParentTab* parent_tab); + + // Gets an offscreen tab by ID. + bool GetOffscreenTab(const int offscreen_tab_id, + UIThreadExtensionFunction* function, + OffscreenTab** offscreen_tab, + std::string* error); + + // Gets a parent tab from its contents for the given extension id. + // Returns NULL if no such tab exists. + ParentTab* GetParentTab(WebContents* parent_contents, + const std::string& extension_id); + + // Creates a new offscreen tab and a mapping between the |parent_tab| and + // the offscreen tab. Takes ownership of |parent_tab|, if it does not already + // have a mapping for it. + const OffscreenTab& CreateOffscreenTab(ParentTab* parent_tab, + const GURL& url, + const int width, + const int height, + const std::string& extension_id); + + // Removes the mapping between a parent tab and an offscreen tab. + // May cause the OffscreenTab object associated with the parent to be deleted. + bool RemoveOffscreenTab(const int offscreen_tab_id, + UIThreadExtensionFunction* function, + std::string* error); + + // Removes and deletes |parent_tab| and all it's offscreen tabs. + void RemoveParentTab(ParentTab* parent_tab); + + private: + typedef base::hash_map<int, linked_ptr<ParentTab> > TabMap; + TabMap map_; + + DISALLOW_COPY_AND_ASSIGN(OffscreenTabMap); +}; + +static base::LazyInstance<OffscreenTabMap> g_offscreen_tab_map = + LAZY_INSTANCE_INITIALIZER; + +// Gets the map of parent tabs to offscreen tabs. +OffscreenTabMap* GetMap() { + return &g_offscreen_tab_map.Get(); +} + +// Gets the WebContents of the tab that instantiated the extension API call. +// Returns NULL if there was an error. +// Note that you can't create offscreen tabs from background pages, since they +// don't have an associated WebContents. The lifetime of offscreen tabs is tied +// to their creating tab, so requiring visible tabs as the parent helps prevent +// offscreen tab leaking. +WebContents* GetCurrentWebContents(UIThreadExtensionFunction* function, + std::string* error) { + WebContents* web_contents = + function->dispatcher()->delegate()->GetAssociatedWebContents(); + if (web_contents) + return web_contents; + + *error = keys::kCurrentTabNotFound; + return NULL; +} + +OffscreenTab::OffscreenTab() : parent_tab_(NULL) {} +OffscreenTab::~OffscreenTab() {} + +void OffscreenTab::Init(const GURL& url, + const int width, + const int height, + Profile* profile, + ParentTab* parent_tab) { + // Create the offscreen tab. + WebContents* web_contents = WebContents::Create( + profile, NULL, MSG_ROUTING_NONE, NULL, NULL); + tab_contents_wrapper_.reset(new TabContentsWrapper(web_contents)); + + // Setting the size starts the renderer. + SetSize(width, height); + NavigateToURL(url); + + parent_tab_ = parent_tab; + + // Register for tab notifications. + registrar_.Add( + this, + content::NOTIFICATION_NAV_ENTRY_COMMITTED, + content::Source<NavigationController>(&(web_contents->GetController()))); +} + +DictionaryValue* OffscreenTab::CreateValue() const { + DictionaryValue* result = new DictionaryValue(); + result->SetInteger( + tabs_keys::kIdKey, ExtensionTabUtil::GetTabId(web_contents())); + result->SetString(tabs_keys::kUrlKey, web_contents()->GetURL().spec()); + result->SetInteger(tabs_keys::kWidthKey, + web_contents()->GetView()->GetContainerSize().width()); + result->SetInteger(tabs_keys::kHeightKey, + web_contents()->GetView()->GetContainerSize().height()); + return result; +} + +void OffscreenTab::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + CHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); + + DictionaryValue* changed_properties = new DictionaryValue(); + changed_properties->SetString( + tabs_keys::kUrlKey, web_contents()->GetURL().spec()); + + ListValue args; + args.Append(Value::CreateIntegerValue( + ExtensionTabUtil::GetTabId(web_contents()))); + args.Append(changed_properties); + args.Append(CreateValue()); + std::string json_args; + base::JSONWriter::Write(&args, &json_args); + + // The event router only dispatches the event to renderers listening for the + // event. + Profile* profile = parent_tab_->tab_contents()->profile(); + profile->GetExtensionEventRouter()->DispatchEventToRenderers( + events::kOnOffscreenTabUpdated, json_args, profile, GURL()); +} + +ParentTab::ParentTab() : tab_contents_wrapper_(NULL) {} +ParentTab::~ParentTab() {} + +void ParentTab::Init(WebContents* web_contents, + const std::string& extension_id) { + CHECK(web_contents); + + extension_id_ = extension_id; + tab_contents_wrapper_ = + TabContentsWrapper::GetCurrentWrapperForContents(web_contents); + + CHECK(tab_contents_wrapper_); + + // Register for tab notifications. + registrar_.Add( + this, + content::NOTIFICATION_NAV_ENTRY_COMMITTED, + content::Source<NavigationController>(&(web_contents->GetController()))); + registrar_.Add( + this, + content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::Source<WebContents>(web_contents)); +} + +void ParentTab::AddOffscreenTab(OffscreenTab *offscreen_tab) { + offscreen_tabs_.push_back(offscreen_tab); +} + +bool ParentTab::RemoveOffscreenTab(OffscreenTab *offscreen_tab) { + OffscreenTabs::iterator it_tab = std::find( + offscreen_tabs_.begin(), offscreen_tabs_.end(), offscreen_tab); + offscreen_tabs_.erase(it_tab); + return offscreen_tabs_.empty(); +} + +void ParentTab::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + CHECK(type == content::NOTIFICATION_NAV_ENTRY_COMMITTED || + type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED); + GetMap()->RemoveParentTab(this); +} + +OffscreenTabMap::OffscreenTabMap() {} +OffscreenTabMap::~OffscreenTabMap() {} + +bool OffscreenTabMap::ContainsTab(ParentTab* parent_tab) { + return map_.find(parent_tab->GetID()) != map_.end(); +} + +bool OffscreenTabMap::GetOffscreenTab(const int offscreen_tab_id, + UIThreadExtensionFunction* function, + OffscreenTab** offscreen_tab, + std::string* error) { + // Ensure that the current tab is the parent of the offscreen tab. + WebContents* web_contents = GetCurrentWebContents(function, error); + if (!web_contents) + return false; + + ParentTab* parent_tab = GetMap()->GetParentTab( + web_contents, function->extension_id()); + if (parent_tab) { + const OffscreenTabs& tabs = parent_tab->offscreen_tabs(); + for (OffscreenTabs::const_iterator i = tabs.begin(); i != tabs.end(); ++i) { + if ((*i)->GetID() == offscreen_tab_id) { + *offscreen_tab = *i; + return true; + } + } + } + + *error = ExtensionErrorUtils::FormatErrorMessage( + keys::kOffscreenTabNotFoundError, base::IntToString(offscreen_tab_id)); + return false; +} + +ParentTab* OffscreenTabMap::GetParentTab(WebContents* parent_contents, + const std::string& extension_id) { + CHECK(parent_contents); + + int parent_tab_id = ExtensionTabUtil::GetTabId(parent_contents); + if (map_.find(parent_tab_id) == map_.end()) + return NULL; + + return map_[parent_tab_id].get(); +} + +const OffscreenTab& OffscreenTabMap::CreateOffscreenTab( + ParentTab* parent_tab, + const GURL& url, + const int width, + const int height, + const std::string& ext_id) { + CHECK(parent_tab); + + // Assume ownership of |parent_tab| if we haven't already. + if (!ContainsTab(parent_tab)) { + map_[ExtensionTabUtil::GetTabId(parent_tab->web_contents())].reset( + parent_tab); + } + + OffscreenTab* offscreen_tab = new OffscreenTab(); + offscreen_tab->Init( + url, width, height, parent_tab->tab_contents()->profile(), parent_tab); + parent_tab->AddOffscreenTab(offscreen_tab); + + return *offscreen_tab; +} + +bool OffscreenTabMap::RemoveOffscreenTab( + const int offscreen_tab_id, + UIThreadExtensionFunction* function, + std::string* error) { + OffscreenTab* offscreen_tab = NULL; + if (!GetOffscreenTab(offscreen_tab_id, function, &offscreen_tab, error)) + return false; + + // Tell the parent tab to remove the offscreen tab, and then remove the + // parent tab if there are no more children. + ParentTab* parent_tab = offscreen_tab->parent_tab(); + if (parent_tab->RemoveOffscreenTab(offscreen_tab)) + RemoveParentTab(parent_tab); + + return true; +} + +void OffscreenTabMap::RemoveParentTab(ParentTab* parent_tab) { + CHECK(parent_tab); + CHECK(ContainsTab(parent_tab)); + + map_.erase(parent_tab->GetID()); +} + +bool CopyModifiers(const DictionaryValue* js_event, + WebInputEvent* event) { + bool alt_key = false; + if (js_event->HasKey(keys::kEventAltKeyKey)) { + if (!js_event->GetBoolean(keys::kEventAltKeyKey, &alt_key)) + return false; + } + if (alt_key) + event->modifiers |= WebInputEvent::AltKey; + + bool ctrl_key = false; + if (js_event->HasKey(keys::kEventCtrlKeyKey)) { + if (!js_event->GetBoolean(keys::kEventCtrlKeyKey, &ctrl_key)) + return false; + } + if (ctrl_key) + event->modifiers |= WebInputEvent::ControlKey; + + bool meta_key = false; + if (js_event->HasKey(keys::kEventMetaKeyKey)) { + if (!js_event->GetBoolean(keys::kEventMetaKeyKey, &meta_key)) + return false; + } + if (meta_key) + event->modifiers |= WebInputEvent::MetaKey; + + bool shift_key; + if (js_event->HasKey(keys::kEventShiftKeyKey)) { + if (!js_event->GetBoolean(keys::kEventShiftKeyKey, &shift_key)) + return false; + } + if (shift_key) + event->modifiers |= WebInputEvent::ShiftKey; + return true; +} + +} // namespace + +CreateOffscreenTabFunction::CreateOffscreenTabFunction() {} +CreateOffscreenTabFunction::~CreateOffscreenTabFunction() {} + +bool CreateOffscreenTabFunction::RunImpl() { + DictionaryValue* create_props; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &create_props)); + + std::string url_string; + EXTENSION_FUNCTION_VALIDATE(create_props->GetString( + tabs_keys::kUrlKey, &url_string)); + + GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( + url_string, GetExtension()); + if (!url.is_valid()) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + tabs_keys::kInvalidUrlError, url_string); + return false; + } + + if (ExtensionTabUtil::IsCrashURL(url)) { + error_ = tabs_keys::kNoCrashBrowserError; + return false; + } + + gfx::Rect window_bounds; + Browser* browser = GetCurrentBrowser(); + if (!browser) { + error_ = tabs_keys::kNoCurrentWindowError; + return false; + } + + WindowSizer::GetBrowserWindowBounds( + std::string(), gfx::Rect(), browser, &window_bounds); + + int width = window_bounds.width(); + if (create_props->HasKey(tabs_keys::kWidthKey)) + EXTENSION_FUNCTION_VALIDATE( + create_props->GetInteger(tabs_keys::kWidthKey, &width)); + + int height = window_bounds.height(); + if (create_props->HasKey(tabs_keys::kHeightKey)) + EXTENSION_FUNCTION_VALIDATE( + create_props->GetInteger(tabs_keys::kHeightKey, &height)); + + + // Add the offscreen tab to the map so we don't lose track of it. + WebContents* web_contents = GetCurrentWebContents(this, &error_); + if (!web_contents) + return false; + + ParentTab* parent_tab = GetMap()->GetParentTab(web_contents, extension_id()); + if (!parent_tab) { + // Ownership is passed to the OffscreenMap in CreateOffscreenTab. + parent_tab = new ParentTab(); + parent_tab->Init(web_contents, extension_id()); + } + + const OffscreenTab& offscreen_tab = GetMap()->CreateOffscreenTab( + parent_tab, url, width, height, extension_id()); + + // TODO(alexbost): Maybe the callback is called too soon. It should probably + // be called once we have navigated to the url. + if (has_callback()) { + result_.reset(offscreen_tab.CreateValue()); + SendResponse(true); + } + + return true; +} + +GetOffscreenTabFunction::GetOffscreenTabFunction() {} +GetOffscreenTabFunction::~GetOffscreenTabFunction() {} + +bool GetOffscreenTabFunction::RunImpl() { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kOffscreenTabNotFoundError, base::IntToString(offscreen_tab_id)); + return false; + } + + result_.reset(offscreen_tab->CreateValue()); + return true; +} + +GetAllOffscreenTabFunction::GetAllOffscreenTabFunction() {} +GetAllOffscreenTabFunction::~GetAllOffscreenTabFunction() {} + +bool GetAllOffscreenTabFunction::RunImpl() { + WebContents* web_contents = GetCurrentWebContents(this, &error_); + if (!web_contents) + return NULL; + + ParentTab* parent_tab = GetMap()->GetParentTab(web_contents, extension_id()); + ListValue* tab_list = new ListValue(); + if (parent_tab) { + for (OffscreenTabs::const_iterator i = parent_tab->offscreen_tabs().begin(); + i != parent_tab->offscreen_tabs().end(); ++i) + tab_list->Append((*i)->CreateValue()); + } + + result_.reset(tab_list); + return true; +} + +RemoveOffscreenTabFunction::RemoveOffscreenTabFunction() {} +RemoveOffscreenTabFunction::~RemoveOffscreenTabFunction() {} + +bool RemoveOffscreenTabFunction::RunImpl() { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) + return false; + + if (!GetMap()->RemoveOffscreenTab(offscreen_tab_id, this, &error_)) + return false; + + return true; +} + +SendKeyboardEventOffscreenTabFunction:: + SendKeyboardEventOffscreenTabFunction() {} +SendKeyboardEventOffscreenTabFunction:: + ~SendKeyboardEventOffscreenTabFunction() {} + +bool SendKeyboardEventOffscreenTabFunction::RunImpl() { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) + return false; + + // JavaScript KeyboardEvent. + DictionaryValue* js_keyboard_event = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &js_keyboard_event)); + + NativeWebKeyboardEvent keyboard_event; + + std::string type; + if (js_keyboard_event->HasKey(keys::kEventTypeKey)) { + EXTENSION_FUNCTION_VALIDATE( + js_keyboard_event->GetString(keys::kEventTypeKey, &type)); + } else { + error_ = keys::kInvalidKeyboardEventObjectError; + return false; + } + + if (type.compare(keys::kKeyboardEventTypeValueKeypress) == 0) { + keyboard_event.type = WebInputEvent::Char; + } else if (type.compare(keys::kKeyboardEventTypeValueKeydown) == 0) { + keyboard_event.type = WebInputEvent::KeyDown; + } else if (type.compare(keys::kKeyboardEventTypeValueKeyup) == 0) { + keyboard_event.type = WebInputEvent::KeyUp; + } else { + error_ = keys::kInvalidKeyboardEventObjectError; + return false; + } + + int key_code; + if (js_keyboard_event->HasKey(keys::kKeyboardEventKeyCodeKey)) { + EXTENSION_FUNCTION_VALIDATE(js_keyboard_event-> + GetInteger(keys::kKeyboardEventKeyCodeKey, &key_code)); + keyboard_event.nativeKeyCode = key_code; + keyboard_event.windowsKeyCode = key_code; + keyboard_event.setKeyIdentifierFromWindowsKeyCode(); + } + + // Keypress = type character + if (type.compare(keys::kKeyboardEventTypeValueKeypress) == 0) { + int char_code; + if (js_keyboard_event->HasKey(keys::kKeyboardEventCharCodeKey)) { + EXTENSION_FUNCTION_VALIDATE(js_keyboard_event-> + GetInteger(keys::kKeyboardEventCharCodeKey, &char_code)); + keyboard_event.text[0] = char_code; + keyboard_event.unmodifiedText[0] = char_code; + } else { + error_ = keys::kInvalidKeyboardEventObjectError; + return false; + } + } + + EXTENSION_FUNCTION_VALIDATE( + CopyModifiers(js_keyboard_event, &keyboard_event)); + + // Forward the event. + offscreen_tab->web_contents()->GetRenderViewHost()-> + ForwardKeyboardEvent(keyboard_event); + + return true; +} + +SendMouseEventOffscreenTabFunction::SendMouseEventOffscreenTabFunction() {} +SendMouseEventOffscreenTabFunction::~SendMouseEventOffscreenTabFunction() {} + +bool SendMouseEventOffscreenTabFunction::RunImpl() { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) + return false; + + // JavaScript MouseEvent. + DictionaryValue* js_mouse_event = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &js_mouse_event)); + + std::string type; + if (js_mouse_event->HasKey(keys::kEventTypeKey)) { + EXTENSION_FUNCTION_VALIDATE( + js_mouse_event->GetString(keys::kEventTypeKey, &type)); + } else { + error_ = keys::kInvalidMouseEventObjectError; + return false; + } + + if (type.compare(keys::kMouseEventTypeValueMousewheel) == 0) { + WebKit::WebMouseWheelEvent wheel_event; + + wheel_event.type = WebInputEvent::MouseWheel; + + if (js_mouse_event->HasKey(keys::kMouseEventWheelDeltaXKey) && + js_mouse_event->HasKey(keys::kMouseEventWheelDeltaYKey)) { + int delta_x, delta_y; + EXTENSION_FUNCTION_VALIDATE(js_mouse_event-> + GetInteger(keys::kMouseEventWheelDeltaXKey, &delta_x)); + EXTENSION_FUNCTION_VALIDATE(js_mouse_event-> + GetInteger(keys::kMouseEventWheelDeltaYKey, &delta_y)); + wheel_event.deltaX = delta_x; + wheel_event.deltaY = delta_y; + } else { + error_ = keys::kInvalidMouseEventObjectError; + return false; + } + + // Forward the event. + offscreen_tab->web_contents()->GetRenderViewHost()-> + ForwardWheelEvent(wheel_event); + } else { + WebKit::WebMouseEvent mouse_event; + + if (type.compare(keys::kMouseEventTypeValueMousedown) == 0 || + type.compare(keys::kMouseEventTypeValueClick) == 0) { + mouse_event.type = WebInputEvent::MouseDown; + } else if (type.compare(keys::kMouseEventTypeValueMouseup) == 0) { + mouse_event.type = WebInputEvent::MouseUp; + } else if (type.compare(keys::kMouseEventTypeValueMousemove) == 0) { + mouse_event.type = WebInputEvent::MouseMove; + } else { + error_ = keys::kInvalidMouseEventObjectError; + return false; + } + + int button; + if (js_mouse_event->HasKey(keys::kMouseEventButtonKey)) { + EXTENSION_FUNCTION_VALIDATE( + js_mouse_event->GetInteger(keys::kMouseEventButtonKey, &button)); + } else { + error_ = keys::kInvalidMouseEventObjectError; + return false; + } + + if (button == keys::kMouseEventButtonValueLeft) { + mouse_event.button = WebKit::WebMouseEvent::ButtonLeft; + } else if (button == keys::kMouseEventButtonValueMiddle) { + mouse_event.button = WebKit::WebMouseEvent::ButtonMiddle; + } else if (button == keys::kMouseEventButtonValueRight) { + mouse_event.button = WebKit::WebMouseEvent::ButtonRight; + } else { + error_ = keys::kInvalidMouseEventObjectError; + return false; + } + + if (HasOptionalArgument(2) && HasOptionalArgument(3)) { + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(2, &mouse_event.x)); + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(3, &mouse_event.y)); + } else { + error_ = keys::kNoMouseCoordinatesError; + return false; + } + + EXTENSION_FUNCTION_VALIDATE(CopyModifiers(js_mouse_event, &mouse_event)); + + mouse_event.clickCount = 1; + + // Forward the event. + offscreen_tab->web_contents()->GetRenderViewHost()-> + ForwardMouseEvent(mouse_event); + + // If the event is a click, + // fire a mouseup event in addition to the mousedown. + if (type.compare(keys::kMouseEventTypeValueClick) == 0) { + mouse_event.type = WebInputEvent::MouseUp; + offscreen_tab->web_contents()->GetRenderViewHost()-> + ForwardMouseEvent(mouse_event); + } + } + + return true; +} + +ToDataUrlOffscreenTabFunction::ToDataUrlOffscreenTabFunction() {} +ToDataUrlOffscreenTabFunction::~ToDataUrlOffscreenTabFunction() {} + +bool ToDataUrlOffscreenTabFunction::GetTabToCapture( + WebContents** web_contents, TabContentsWrapper** wrapper) { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + // TODO(alexbost): We want to optimize this function in order to get more + // image updates on the browser side. One improvement would be to implement + // another hash map in order to get offscreen tabs in O(1). + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) + return false; + + *web_contents = offscreen_tab->web_contents(); + *wrapper = offscreen_tab->tab_contents(); + return true; +} + +UpdateOffscreenTabFunction::UpdateOffscreenTabFunction() {} +UpdateOffscreenTabFunction::~UpdateOffscreenTabFunction() {} + +bool UpdateOffscreenTabFunction::RunImpl() { + int offscreen_tab_id; + EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &offscreen_tab_id)); + + OffscreenTab* offscreen_tab = NULL; + if (!GetMap()->GetOffscreenTab( + offscreen_tab_id, this, &offscreen_tab, &error_)) + return false; + + DictionaryValue* update_props; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); + + web_contents_ = offscreen_tab->web_contents(); + bool is_async = false; + if (!UpdateURLIfPresent(update_props, &is_async)) + return false; + + // Update the width and height, if specified. + if (update_props->HasKey(tabs_keys::kWidthKey) || + update_props->HasKey(tabs_keys::kHeightKey)) { + int width; + if (update_props->HasKey(tabs_keys::kWidthKey)) + EXTENSION_FUNCTION_VALIDATE( + update_props->GetInteger(tabs_keys::kWidthKey, &width)); + else + web_contents_->GetView()->GetContainerSize().width(); + + int height; + if (update_props->HasKey(tabs_keys::kHeightKey)) + EXTENSION_FUNCTION_VALIDATE( + update_props->GetInteger(tabs_keys::kHeightKey, &height)); + else + web_contents_->GetView()->GetContainerSize().height(); + + offscreen_tab->SetSize(width, height); + } + + // The response is sent from UpdateTabFunction::OnExecuteCodeFinish in the + // async case (when a "javascript": URL is sent to a tab). + if (!is_async) + SendResponse(true); + + return true; +} + +void UpdateOffscreenTabFunction::PopulateResult() { + // There's no result associated with this callback. +} diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.h b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.h new file mode 100644 index 0000000..32dec51 --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.h @@ -0,0 +1,132 @@ +// Copyright (c) 2012 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_API_OFFSCREEN_TABS_OFFSCREEN_TABS_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_OFFSCREEN_TABS_OFFSCREEN_TABS_API_H_ +#pragma once + +#include <string> + +#include "base/values.h" +#include "chrome/browser/extensions/extension_function.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_observer.h" + +// The offscreen tabs module depends on the tabs module so we can share +// code between them. The long-term goal is to fold the offscreen tabs +// functionality into the tabs API. While these methods seem very similar to +// those in tabs, there are a few key differences that need to be resolved. +// - Offscreen tabs are invisible (maybe this could be a property on Tab). +// - The lifetime of an offscreen tab is tied to the parent tab that opened +// it. We do this to prevent leaking these tabs, since users wouldn't have +// a way of directly closing them or knowing they're open. +// - Offscreen tabs have a width and height, while regular tabs don't. This +// lets clients control the dimensions of the images in ToDataUrl. + +class BackingStore; +class SkBitmap; +class TabContentsWrapper; +namespace content { +class WebContents; +} // namespace content + +// Creates an offscreen tab. +class CreateOffscreenTabFunction : public SyncExtensionFunction { + public: + CreateOffscreenTabFunction(); + private: + virtual ~CreateOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.create") + DISALLOW_COPY_AND_ASSIGN(CreateOffscreenTabFunction); +}; + +// Gets info about an offscreen tab. +class GetOffscreenTabFunction : public SyncExtensionFunction { + public: + GetOffscreenTabFunction(); + private: + virtual ~GetOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.get") + DISALLOW_COPY_AND_ASSIGN(GetOffscreenTabFunction); +}; + +// Gets all offscreen tabs created by the tab that invoked this function. +class GetAllOffscreenTabFunction : public SyncExtensionFunction { + public: + GetAllOffscreenTabFunction(); + private: + virtual ~GetAllOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.getAll") + DISALLOW_COPY_AND_ASSIGN(GetAllOffscreenTabFunction); +}; + +// Removes an offscreen tab. +class RemoveOffscreenTabFunction : public SyncExtensionFunction { + public: + RemoveOffscreenTabFunction(); + private: + virtual ~RemoveOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.remove") + DISALLOW_COPY_AND_ASSIGN(RemoveOffscreenTabFunction); +}; + +// Synthesizes a keyboard event based on a passed-in JavaScript keyboard event. +// TODO(jstritar): This would be useful on the chrome.tabs API. +class SendKeyboardEventOffscreenTabFunction : public SyncExtensionFunction { + public: + SendKeyboardEventOffscreenTabFunction(); + private: + virtual ~SendKeyboardEventOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME( + "experimental.offscreenTabs.sendKeyboardEvent") + DISALLOW_COPY_AND_ASSIGN(SendKeyboardEventOffscreenTabFunction); +}; + +// Synthesizes a mouse event based on a passed-in JavaScript mouse event. +// Since only the application knows where the user clicks, x and y coordinates +// need to be passed in as well in this case of click, mousedown, and mouseup. +// TODO(jstritar): This would be useful on the chrome.tabs API. +class SendMouseEventOffscreenTabFunction : public SyncExtensionFunction { + public: + SendMouseEventOffscreenTabFunction(); + private: + virtual ~SendMouseEventOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.sendMouseEvent") + DISALLOW_COPY_AND_ASSIGN(SendMouseEventOffscreenTabFunction); +}; + +// Gets a snapshot of the offscreen tab and returns it as a data URL. +class ToDataUrlOffscreenTabFunction : public CaptureVisibleTabFunction { + public: + ToDataUrlOffscreenTabFunction(); + private: + virtual ~ToDataUrlOffscreenTabFunction(); + virtual bool GetTabToCapture(content::WebContents** web_contents, + TabContentsWrapper** wrapper) OVERRIDE; + // TODO(jstritar): Rename to toDataUrl. + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.toDataUrl") + DISALLOW_COPY_AND_ASSIGN(ToDataUrlOffscreenTabFunction); +}; + +// Updates an offscreen tab. +class UpdateOffscreenTabFunction : public UpdateTabFunction { + public: + UpdateOffscreenTabFunction(); + private: + virtual ~UpdateOffscreenTabFunction(); + virtual bool RunImpl() OVERRIDE; + virtual void PopulateResult() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.offscreenTabs.update") + DISALLOW_COPY_AND_ASSIGN(UpdateOffscreenTabFunction); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_API_OFFSCREEN_TABS_OFFSCREEN_TABS_API_H_ diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_apitest.cc b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_apitest.cc new file mode 100644 index 0000000..377300f --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_apitest.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 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 "base/command_line.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/common/chrome_switches.h" + +// Offscreen Tabs does not work on ChromeOS right now. +#if !defined(OS_CHROMEOS) + +class OffscreenTabsApiTest : public ExtensionApiTest { + public: + void SetUpCommandLine(CommandLine* command_line) { + ExtensionApiTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis); + } +}; + +IN_PROC_BROWSER_TEST_F(OffscreenTabsApiTest, OffscreenTabBasics) { + ASSERT_TRUE(RunExtensionSubtest("offscreen_tabs", "crud.html")) << message_; +} + + +IN_PROC_BROWSER_TEST_F(OffscreenTabsApiTest, OffscreenTabMouseEvents) { + ASSERT_TRUE(RunExtensionSubtest("offscreen_tabs", + "mouse_events.html")) << message_; +} + +IN_PROC_BROWSER_TEST_F(OffscreenTabsApiTest, OffscreenTabKeyboardEvents) { + ASSERT_TRUE(RunExtensionSubtest("offscreen_tabs", + "keyboard_events.html")) << message_; +} + +IN_PROC_BROWSER_TEST_F(OffscreenTabsApiTest, OffscreenTabDisplay) { + ASSERT_TRUE(RunExtensionSubtest("offscreen_tabs", + "display.html")) << message_; +} + +#endif // #if !defined(OS_CHROMEOS) diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.cc b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.cc new file mode 100644 index 0000000..2e3b603 --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2012 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/api/offscreen_tabs/offscreen_tabs_constants.h" + +namespace extensions { +namespace offscreen_tabs_constants { + +const char kEventTypeKey[] = "type"; +const char kEventAltKeyKey[] = "altKey"; +const char kEventCtrlKeyKey[] = "ctrlKey"; +const char kEventMetaKeyKey[] = "metaKey"; +const char kEventShiftKeyKey[] = "shiftKey"; + +const char kMouseEventButtonKey[] = "button"; +const char kMouseEventWheelDeltaXKey[] = "wheelDeltaX"; +const char kMouseEventWheelDeltaYKey[] = "wheelDeltaY"; + +const char kMouseEventTypeValueMousedown[] = "mousedown"; +const char kMouseEventTypeValueMouseup[] = "mouseup"; +const char kMouseEventTypeValueClick[] = "click"; +const char kMouseEventTypeValueMousemove[] = "mousemove"; +const char kMouseEventTypeValueMousewheel[] = "mousewheel"; +const int kMouseEventButtonValueLeft = 0; +const int kMouseEventButtonValueMiddle = 1; +const int kMouseEventButtonValueRight = 2; + +const char kKeyboardEventCharCodeKey[] = "charCode"; +const char kKeyboardEventKeyCodeKey[] = "keyCode"; + +const char kKeyboardEventTypeValueKeypress[] = "keypress"; +const char kKeyboardEventTypeValueKeydown[] = "keydown"; +const char kKeyboardEventTypeValueKeyup[] = "keyup"; + +const char kCurrentTabNotFound[] = "No current tab found"; +const char kInvalidKeyboardEventObjectError[] = + "Invalid or unexpected KeyboardEvent object"; +const char kInvalidMouseEventObjectError[] = + "Invalid or unexpected MouseEvent object"; +const char kNoMouseCoordinatesError[] = "No mouse coordinates specified"; +const char kOffscreenTabNotFoundError[] = "No offscreen tab with id: *."; + +} // namespace offscreen_Tabs_constants +} // namespace extensions diff --git a/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.h b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.h new file mode 100644 index 0000000..d574590 --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012 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. + +// Constants used for the Offscreen Tabs API. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_OFFSCREEN_TABS_OFFSCREEN_TABS_CONSTANTS_H_ +#define CHROME_BROWSER_EXTENSIONS_API_OFFSCREEN_TABS_OFFSCREEN_TABS_CONSTANTS_H_ +#pragma once + +namespace extensions { +namespace offscreen_tabs_constants { + +// Input event keys. +extern const char kEventTypeKey[]; +extern const char kEventAltKeyKey[]; +extern const char kEventCtrlKeyKey[]; +extern const char kEventMetaKeyKey[]; +extern const char kEventShiftKeyKey[]; + +// Mouse event keys. +extern const char kMouseEventTypeKey[]; +extern const char kMouseEventButtonKey[]; +extern const char kMouseEventWheelDeltaXKey[]; +extern const char kMouseEventWheelDeltaYKey[]; + +// Mouse event values. +extern const char kMouseEventTypeValueMousedown[]; +extern const char kMouseEventTypeValueMouseup[]; +extern const char kMouseEventTypeValueClick[]; +extern const char kMouseEventTypeValueMousemove[]; +extern const char kMouseEventTypeValueMousewheel[]; +extern const int kMouseEventButtonValueLeft; +extern const int kMouseEventButtonValueMiddle; +extern const int kMouseEventButtonValueRight; + +// Keyboard event keys. +extern const char kKeyboardEventTypeKey[]; +extern const char kKeyboardEventCharCodeKey[]; +extern const char kKeyboardEventKeyCodeKey[]; + +// Keyboard event values. +extern const char kKeyboardEventTypeValueKeypress[]; +extern const char kKeyboardEventTypeValueKeydown[]; +extern const char kKeyboardEventTypeValueKeyup[]; + +// Errors. +extern const char kCurrentTabNotFound[]; +extern const char kInvalidKeyboardEventObjectError[]; +extern const char kInvalidMouseEventObjectError[]; +extern const char kNoMouseCoordinatesError[]; +extern const char kOffscreenTabNotFoundError[]; + +} // namespace offscreen_tabs_constants +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_OFFSCREEN_TABS_OFFSCREEN_TABS_CONSTANTS_H_ diff --git a/chrome/browser/extensions/extension_event_names.cc b/chrome/browser/extensions/extension_event_names.cc index c127cd5..821cdf9 100644 --- a/chrome/browser/extensions/extension_event_names.cc +++ b/chrome/browser/extensions/extension_event_names.cc @@ -44,4 +44,6 @@ const char kOnSettingsChanged[] = "storage.onChanged"; const char kOnTerminalProcessOutput[] = "terminalPrivate.onProcessOutput"; +const char kOnOffscreenTabUpdated[] = "experimental.offscreenTabs.onUpdated"; + } // namespace extension_event_names diff --git a/chrome/browser/extensions/extension_event_names.h b/chrome/browser/extensions/extension_event_names.h index 200328d..e1a7fc8 100644 --- a/chrome/browser/extensions/extension_event_names.h +++ b/chrome/browser/extensions/extension_event_names.h @@ -54,6 +54,9 @@ extern const char kOnSettingsChanged[]; // TerminalPrivate. extern const char kOnTerminalProcessOutput[]; +// OffscreenTabs. +extern const char kOnOffscreenTabUpdated[]; + }; // namespace extension_event_names #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_EVENT_NAMES_H_ diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h index 6a8dbe9..1fca5ad 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.h +++ b/chrome/browser/extensions/extension_function_dispatcher.h @@ -55,7 +55,7 @@ class ExtensionFunctionDispatcher // Returns NULL otherwise. virtual Browser* GetBrowser() = 0; - // Asks the delegate for any relevant WebbContents associated with this + // Asks the delegate for any relevant WebContents associated with this // context. For example, the WebbContents in which an infobar or // chrome-extension://<id> URL are being shown. Callers must check for a // NULL return value (as in the case of a background page). diff --git a/chrome/browser/extensions/extension_function_registry.cc b/chrome/browser/extensions/extension_function_registry.cc index ffcbe04..e828ead 100644 --- a/chrome/browser/extensions/extension_function_registry.cc +++ b/chrome/browser/extensions/extension_function_registry.cc @@ -13,6 +13,7 @@ #include "chrome/browser/extensions/api/declarative/declarative_api.h" #include "chrome/browser/extensions/api/extension_action/extension_browser_actions_api.h" #include "chrome/browser/extensions/api/extension_action/extension_page_actions_api.h" +#include "chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.h" #include "chrome/browser/extensions/api/permissions/permissions_api.h" #include "chrome/browser/extensions/api/serial/serial_api.h" #include "chrome/browser/extensions/api/socket/socket_api.h" @@ -466,6 +467,16 @@ void ExtensionFunctionRegistry::ResetFunctions() { RegisterFunction<extensions::RemoveRulesFunction>(); RegisterFunction<extensions::GetRulesFunction>(); + // Experimental Offscreen Tabs + RegisterFunction<CreateOffscreenTabFunction>(); + RegisterFunction<GetOffscreenTabFunction>(); + RegisterFunction<GetAllOffscreenTabFunction>(); + RegisterFunction<RemoveOffscreenTabFunction>(); + RegisterFunction<SendKeyboardEventOffscreenTabFunction>(); + RegisterFunction<SendMouseEventOffscreenTabFunction>(); + RegisterFunction<ToDataUrlOffscreenTabFunction>(); + RegisterFunction<UpdateOffscreenTabFunction>(); + // Generated APIs extensions::api::GeneratedFunctionRegistry::RegisterAll(this); } diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc index 98bd098..9f91a3a 100644 --- a/chrome/browser/extensions/extension_tab_util.cc +++ b/chrome/browser/extensions/extension_tab_util.cc @@ -12,9 +12,13 @@ #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/sessions/restore_tab_helper.h" #include "chrome/browser/extensions/extension_tabs_module_constants.h" +#include "chrome/browser/net/url_fixer_upper.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/url_constants.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/web_contents.h" +#include "googleurl/src/gurl.h" namespace keys = extension_tabs_module_constants; namespace errors = extension_manifest_errors; @@ -223,3 +227,21 @@ bool ExtensionTabUtil::GetTabById(int tab_id, } return false; } + +GURL ExtensionTabUtil::ResolvePossiblyRelativeURL(const std::string& url_string, + const Extension* extension) { + GURL url = GURL(url_string); + if (!url.is_valid()) + url = extension->GetResourceURL(url_string); + + return url; +} + +bool ExtensionTabUtil::IsCrashURL(const GURL& url) { + // Check a fixed-up URL, to normalize the scheme and parse hosts correctly. + GURL fixed_url = + URLFixerUpper::FixupURL(url.possibly_invalid_spec(), std::string()); + return (fixed_url.SchemeIs(chrome::kChromeUIScheme) && + (fixed_url.host() == chrome::kChromeUIBrowserCrashHost || + fixed_url.host() == chrome::kChromeUICrashHost)); +} diff --git a/chrome/browser/extensions/extension_tab_util.h b/chrome/browser/extensions/extension_tab_util.h index 5a2fe77..ed11e2c 100644 --- a/chrome/browser/extensions/extension_tab_util.h +++ b/chrome/browser/extensions/extension_tab_util.h @@ -9,6 +9,8 @@ #include <string> class Browser; +class Extension; +class GURL; class Profile; class TabContents; class TabContentsWrapper; @@ -62,6 +64,20 @@ class ExtensionTabUtil { TabStripModel** tab_strip, TabContentsWrapper** contents, int* tab_index); + + // Takes |url_string| and returns a GURL which is either valid and absolute + // or invalid. If |url_string| is not directly interpretable as a valid (it is + // likely a relative URL) an attempt is made to resolve it. |extension| is + // provided so it can be resolved relative to its extension base + // (chrome-extension://<id>/). Using the source frame url would be more + // correct, but because the api shipped with urls resolved relative to their + // extension base, we decided it wasn't worth breaking existing extensions to + // fix. + static GURL ResolvePossiblyRelativeURL(const std::string& url_string, + const Extension* extension); + + // Returns true if |url| is used for testing crashes. + static bool IsCrashURL(const GURL& url); }; #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TAB_UTIL_H__ diff --git a/chrome/browser/extensions/extension_tabs_module.cc b/chrome/browser/extensions/extension_tabs_module.cc index a9c8ded..d15bb6b 100644 --- a/chrome/browser/extensions/extension_tabs_module.cc +++ b/chrome/browser/extensions/extension_tabs_module.cc @@ -26,7 +26,6 @@ #include "chrome/browser/extensions/extension_tabs_module_constants.h" #include "chrome/browser/extensions/extension_window_controller.h" #include "chrome/browser/extensions/extension_window_list.h" -#include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/prefs/incognito_mode_prefs.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/restore_tab_helper.h" @@ -191,31 +190,6 @@ bool GetTabById(int tab_id, return false; } -// Takes |url_string| and returns a GURL which is either valid and absolute -// or invalid. If |url_string| is not directly interpretable as a valid (it is -// likely a relative URL) an attempt is made to resolve it. |extension| is -// provided so it can be resolved relative to its extension base -// (chrome-extension://<id>/). Using the source frame url would be more correct, -// but because the api shipped with urls resolved relative to their extension -// base, we decided it wasn't worth breaking existing extensions to fix. -GURL ResolvePossiblyRelativeURL(const std::string& url_string, - const Extension* extension) { - GURL url = GURL(url_string); - if (!url.is_valid()) - url = extension->GetResourceURL(url_string); - - return url; -} - -bool IsCrashURL(const GURL& url) { - // Check a fixed-up URL, to normalize the scheme and parse hosts correctly. - GURL fixed_url = - URLFixerUpper::FixupURL(url.possibly_invalid_spec(), std::string()); - return (fixed_url.SchemeIs(chrome::kChromeUIScheme) && - (fixed_url.host() == chrome::kChromeUIBrowserCrashHost || - fixed_url.host() == chrome::kChromeUICrashHost)); -} - // Reads the |value| as either a single integer value or a list of integers. bool ReadOneOrMoreIntegers( Value* value, std::vector<int>* result) { @@ -442,14 +416,15 @@ bool CreateWindowFunction::RunImpl() { // Second, resolve, validate and convert them to GURLs. for (std::vector<std::string>::iterator i = url_strings.begin(); i != url_strings.end(); ++i) { - GURL url = ResolvePossiblyRelativeURL(*i, GetExtension()); + GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( + *i, GetExtension()); if (!url.is_valid()) { error_ = ExtensionErrorUtils::FormatErrorMessage( keys::kInvalidUrlError, *i); return false; } // Don't let the extension crash the browser or renderers. - if (IsCrashURL(url)) { + if (ExtensionTabUtil::IsCrashURL(url)) { error_ = keys::kNoCrashBrowserError; return false; } @@ -1010,7 +985,8 @@ bool CreateTabFunction::RunImpl() { if (args->HasKey(keys::kUrlKey)) { EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, &url_string)); - url = ResolvePossiblyRelativeURL(url_string, GetExtension()); + url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, + GetExtension()); if (!url.is_valid()) { error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string); @@ -1019,7 +995,7 @@ bool CreateTabFunction::RunImpl() { } // Don't let extensions crash the browser or renderers. - if (IsCrashURL(url)) { + if (ExtensionTabUtil::IsCrashURL(url)) { error_ = keys::kNoCrashBrowserError; return false; } @@ -1216,69 +1192,15 @@ bool UpdateTabFunction::RunImpl() { } web_contents_ = contents->web_contents(); - NavigationController& controller = web_contents_->GetController(); // TODO(rafaelw): handle setting remaining tab properties: // -title // -favIconUrl - // We wait to fire the callback when executing 'javascript:' URLs in tabs. - bool is_async = false; - // Navigate the tab to a new location if the url is different. - std::string url_string; - if (update_props->HasKey(keys::kUrlKey)) { - EXTENSION_FUNCTION_VALIDATE(update_props->GetString( - keys::kUrlKey, &url_string)); - GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension()); - - if (!url.is_valid()) { - error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, - url_string); - return false; - } - - // Don't let the extension crash the browser or renderers. - if (IsCrashURL(url)) { - error_ = keys::kNoCrashBrowserError; - return false; - } - - // JavaScript URLs can do the same kinds of things as cross-origin XHR, so - // we need to check host permissions before allowing them. - if (url.SchemeIs(chrome::kJavaScriptScheme)) { - if (!GetExtension()->CanExecuteScriptOnPage( - web_contents_->GetURL(), NULL, &error_)) { - return false; - } - - ExtensionMsg_ExecuteCode_Params params; - params.request_id = request_id(); - params.extension_id = extension_id(); - params.is_javascript = true; - params.code = url.path(); - params.all_frames = false; - params.in_main_world = true; - - RenderViewHost* render_view_host = web_contents_->GetRenderViewHost(); - render_view_host->Send( - new ExtensionMsg_ExecuteCode(render_view_host->GetRoutingID(), - params)); - - Observe(web_contents_); - AddRef(); // Balanced in OnExecuteCodeFinished(). - - is_async = true; - } - - controller.LoadURL( - url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); - - // The URL of a tab contents never actually changes to a JavaScript URL, so - // this check only makes sense in other cases. - if (!url.SchemeIs(chrome::kJavaScriptScheme)) - DCHECK_EQ(url.spec(), web_contents_->GetURL().spec()); - } + bool is_async = false; + if (!UpdateURLIfPresent(update_props, &is_async)) + return false; bool active = false; // TODO(rafaelw): Setting |active| from js doesn't make much sense. @@ -1340,6 +1262,67 @@ bool UpdateTabFunction::RunImpl() { return true; } +bool UpdateTabFunction::UpdateURLIfPresent(DictionaryValue* update_props, + bool* is_async) { + if (!update_props->HasKey(keys::kUrlKey)) + return true; + + std::string url_string; + EXTENSION_FUNCTION_VALIDATE(update_props->GetString( + keys::kUrlKey, &url_string)); + GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( + url_string, GetExtension()); + + if (!url.is_valid()) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kInvalidUrlError, url_string); + return false; + } + + // Don't let the extension crash the browser or renderers. + if (ExtensionTabUtil::IsCrashURL(url)) { + error_ = keys::kNoCrashBrowserError; + return false; + } + + // JavaScript URLs can do the same kinds of things as cross-origin XHR, so + // we need to check host permissions before allowing them. + if (url.SchemeIs(chrome::kJavaScriptScheme)) { + if (!GetExtension()->CanExecuteScriptOnPage( + web_contents_->GetURL(), NULL, &error_)) { + return false; + } + + ExtensionMsg_ExecuteCode_Params params; + params.request_id = request_id(); + params.extension_id = extension_id(); + params.is_javascript = true; + params.code = url.path(); + params.all_frames = false; + params.in_main_world = true; + + RenderViewHost* render_view_host = web_contents_->GetRenderViewHost(); + render_view_host->Send( + new ExtensionMsg_ExecuteCode(render_view_host->GetRoutingID(), params)); + + Observe(web_contents_); + AddRef(); // Balanced in OnExecuteCodeFinished(). + + *is_async = true; + return true; + } + + web_contents_->GetController().LoadURL( + url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); + + // The URL of a tab contents never actually changes to a JavaScript URL, so + // this check only makes sense in other cases. + if (!url.SchemeIs(chrome::kJavaScriptScheme)) + DCHECK_EQ(url.spec(), web_contents_->GetURL().spec()); + + return true; +} + void UpdateTabFunction::PopulateResult() { if (!has_callback()) return; @@ -1391,7 +1374,7 @@ void UpdateTabFunction::OnExecuteCodeFinished(int request_id, SendResponse(success); Observe(NULL); - Release(); // Balanced in RunImpl(). + Release(); // Balanced in UpdateURLIfPresent(). } bool MoveTabsFunction::RunImpl() { @@ -1594,7 +1577,8 @@ bool RemoveTabsFunction::RunImpl() { return true; } -bool CaptureVisibleTabFunction::RunImpl() { +bool CaptureVisibleTabFunction::GetTabToCapture( + WebContents** web_contents, TabContentsWrapper** wrapper) { Browser* browser = NULL; // windowId defaults to "current" window. int window_id = extension_misc::kCurrentWindowId; @@ -1605,6 +1589,23 @@ bool CaptureVisibleTabFunction::RunImpl() { if (!GetBrowserFromWindowID(this, window_id, &browser)) return false; + *web_contents = browser->GetSelectedWebContents(); + if (*web_contents == NULL) { + error_ = keys::kInternalVisibleTabCaptureError; + return false; + } + + *wrapper = browser->GetSelectedTabContentsWrapper(); + + return true; +}; + +bool CaptureVisibleTabFunction::RunImpl() { + WebContents* web_contents = NULL; + TabContentsWrapper* wrapper = NULL; + if (!GetTabToCapture(&web_contents, &wrapper)) + return false; + image_format_ = FORMAT_JPEG; // Default format is JPEG. image_quality_ = kDefaultQuality; // Default quality setting. @@ -1633,12 +1634,6 @@ bool CaptureVisibleTabFunction::RunImpl() { } } - WebContents* web_contents = browser->GetSelectedWebContents(); - if (!web_contents) { - error_ = keys::kInternalVisibleTabCaptureError; - return false; - } - // captureVisibleTab() can return an image containing sensitive information // that the browser would otherwise protect. Ensure the extension has // permission to do this. @@ -1649,7 +1644,6 @@ bool CaptureVisibleTabFunction::RunImpl() { // If a backing store is cached for the tab we want to capture, // and it can be copied into a bitmap, then use it to generate the image. - // This may fail if we can not copy a backing store into a bitmap. // For example, some uncommon X11 visual modes are not supported by // CopyFromBackingStore(). skia::PlatformCanvas temp_canvas; @@ -1661,7 +1655,7 @@ bool CaptureVisibleTabFunction::RunImpl() { } // Ask the renderer for a snapshot of the tab. - TabContentsWrapper* wrapper = browser->GetSelectedTabContentsWrapper(); + wrapper->snapshot_tab_helper()->CaptureSnapshot(); registrar_.Add(this, chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN, content::Source<WebContents>(wrapper->web_contents())); diff --git a/chrome/browser/extensions/extension_tabs_module.h b/chrome/browser/extensions/extension_tabs_module.h index 7038bc2..7738d75 100644 --- a/chrome/browser/extensions/extension_tabs_module.h +++ b/chrome/browser/extensions/extension_tabs_module.h @@ -17,13 +17,17 @@ #include "googleurl/src/gurl.h" class BackingStore; +class Extension; +class GURL; class SkBitmap; +class TabContentsWrapper; namespace base { class DictionaryValue; } // namespace base namespace content { class WebContents; } // namespace content + // Windows class GetWindowFunction : public SyncExtensionFunction { virtual ~GetWindowFunction() {} @@ -110,17 +114,23 @@ class UpdateTabFunction : public AsyncExtensionFunction, public content::WebContentsObserver { public: UpdateTabFunction(); - private: + + protected: virtual ~UpdateTabFunction() {} + virtual bool UpdateURLIfPresent(base::DictionaryValue* update_props, + bool* is_async); + virtual void PopulateResult(); + + content::WebContents* web_contents_; + + private: virtual bool RunImpl() OVERRIDE; virtual void WebContentsDestroyed(content::WebContents* tab) OVERRIDE; virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; void OnExecuteCodeFinished(int request_id, bool success, const std::string& error); - void PopulateResult(); - content::WebContents* web_contents_; DECLARE_EXTENSION_FUNCTION_NAME("tabs.update") }; class MoveTabsFunction : public SyncExtensionFunction { @@ -153,7 +163,7 @@ class DetectTabLanguageFunction : public AsyncExtensionFunction, }; class CaptureVisibleTabFunction : public AsyncExtensionFunction, public content::NotificationObserver { - private: + protected: enum ImageFormat { FORMAT_JPEG, FORMAT_PNG @@ -164,10 +174,11 @@ class CaptureVisibleTabFunction : public AsyncExtensionFunction, virtual ~CaptureVisibleTabFunction() {} virtual bool RunImpl() OVERRIDE; + virtual bool GetTabToCapture(content::WebContents** web_contents, + TabContentsWrapper** wrapper); virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; - bool CaptureSnapshotFromBackingStore(BackingStore* backing_store); void SendResultFromBitmap(const SkBitmap& screen_capture); content::NotificationRegistrar registrar_; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 817119d..b2844b8 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -90,6 +90,10 @@ 'browser/extensions/api/extension_action/extension_page_actions_api.h', 'browser/extensions/api/extension_action/extension_page_actions_api_constants.cc', 'browser/extensions/api/extension_action/extension_page_actions_api_constants.h', + 'browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc', + 'browser/extensions/api/offscreen_tabs/offscreen_tabs_api.h', + 'browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.cc', + 'browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.h', 'browser/extensions/api/permissions/permissions_api.cc', 'browser/extensions/api/permissions/permissions_api.h', 'browser/extensions/api/permissions/permissions_api_helpers.cc', diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index b986668..ae6ca57 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -137,6 +137,7 @@ 'renderer/resources/extensions/devtools_custom_bindings.js', 'renderer/resources/extensions/event.js', 'renderer/resources/extensions/experimental.declarative_custom_bindings.js', + 'renderer/resources/extensions/experimental.offscreenTabs_custom_bindings.js', 'renderer/resources/extensions/experimental.socket_custom_bindings.js', 'renderer/resources/extensions/extension_custom_bindings.js', 'renderer/resources/extensions/file_browser_handler_custom_bindings.js', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index a7383c1..1ef66c8 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -2638,6 +2638,7 @@ 'browser/extensions/api/dns/dns_apitest.cc', 'browser/extensions/api/extension_action/browser_action_apitest.cc', 'browser/extensions/api/extension_action/page_action_apitest.cc', + 'browser/extensions/api/offscreen_tabs/offscreen_tabs_apitest.cc', 'browser/extensions/api/permissions/permissions_apitest.cc', 'browser/extensions/api/proxy/proxy_apitest.cc', 'browser/extensions/api/serial/serial_apitest.cc', diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd index 536fd1b..0de3608 100644 --- a/chrome/common/common_resources.grd +++ b/chrome/common/common_resources.grd @@ -35,6 +35,7 @@ <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD" file="extensions\api\experimental.input.virtualKeyboard.json" type="BINDATA" /> <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING" file="extensions\api\experimental.keybinding.json" type="BINDATA" /> <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGED_MODE" file="extensions\api\experimental.managedMode.json" type="BINDATA" /> + <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS" file="extensions\api\experimental.offscreenTabs.json" type="BINDATA" /> <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES" file="extensions\api\experimental.processes.json" type="BINDATA" /> <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ" file="extensions\api\experimental.rlz.json" type="BINDATA" /> <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL" file="extensions\api\experimental.serial.json" type="BINDATA" /> diff --git a/chrome/common/extensions/api/experimental.offscreenTabs.json b/chrome/common/extensions/api/experimental.offscreenTabs.json new file mode 100644 index 0000000..56e8f8e --- /dev/null +++ b/chrome/common/extensions/api/experimental.offscreenTabs.json @@ -0,0 +1,353 @@ +[ + { + "namespace": "experimental.offscreenTabs", + "types": [ + { + "id": "OffscreenTab", + "type": "object", + "properties": { + "id": { + "type": "integer", + "minimum": 0, + "description": "The ID of the offscreen tab. Tab IDs are unique within a browser session." + }, + "url": { + "type": + "string", + "description": "URL of the offscreen tab." + }, + "width": { + "type": "integer", + "minimum": 0, + "description": "Width of the window." + }, + "height": { + "type": "integer", + "minimum": 0, + "description": "Height of the window." + } + } + }, + { + "id": "MouseEvent", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "mousedown", "mouseup", "click", "mousemove", "mousewheel" ] + }, + "button": { "type": "integer", "minimum": 0, "optional": true }, + "wheelDeltaX": { "type": "integer", "optional": true }, + "wheelDeltaY": { "type": "integer", "optional": true }, + "altKey": { "type": "boolean" }, + "ctrlKey": { "type": "boolean" }, + "shiftKey": { "type": "boolean" }, + "metaKey": { "type": "boolean", "optional": true } + } + }, + { + "id": "KeyboardEvent", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "keypress", "keydown", "keyup" ] + }, + "charCode": { "type": "integer" }, + "keyCode": { "type": "integer" }, + "altKey": { "type": "boolean" }, + "ctrlKey": { "type": "boolean" }, + "shiftKey": { "type": "boolean" }, + "metaKey": { "type": "boolean", "optional": true } + } + } + ], + "functions": [ + { + "name": "create", + "type": "function", + "description": "Creates a new offscreen tab.", + "parameters": [ + { + "name": "createProperties", + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The URL to navigate the offscreen tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Note that you can't create offscreen tabs from background pages. The lifetime of the offscreen tab is tied to the context of its creator (tab, browser action, etc.)." + }, + "width": { + "type": "integer", + "optional": true, + "minimum": 0, + "description": "Width of the offscreen tab. Defaults to width of the current tab." + }, + "height": { + "type": "integer", + "optional": true, + "minimum": 0, + "description": "Height of the offscreen tab. Defaults to height of the current tab." + } + } + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "offscreenTab", + "$ref": "OffscreenTab", + "optional": true, + "description": "Details of the offscreen tab." + } + ] + } + ] + }, + { + "name": "get", + "type": "function", + "description": "Gets details about the specified offscreen tab.", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "ID of the offscreen tab." + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "offscreenTab", + "$ref": "OffscreenTab", + "description": "Details of the offscreen tab." + } + ] + } + ] + }, + { + "name": "getAll", + "type": "function", + "description": "Gets an array of all offscreen tabs.", + "parameters": [ + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "offscreenTabs", + "type": "array", + "items": { + "$ref": "OffscreenTab" + } + } + ] + } + ] + }, + { + "name": "remove", + "type": "function", + "description": "Removes an offscreen tab.", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "ID of the offscreen tab." + }, + { + "name": "callback", + "type": "function", + "optional": true + } + ] + }, + { + "name": "sendMouseEvent", + "type": "function", + "description": "Dispatches a mouse event in the offscreen tab.", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "ID of the offscreen tab." + }, + { + "name": "mouseEvent", + "$ref": "MouseEvent", + "description": "A JavaScript MouseEvent object. Supported event types: <i>mousedown</i>, <i>mouseup</i>, <i>click</i>, <i>mousemove</i>, <i>mousewheel</i>." + }, + { + "name": "x", + "type": "integer", + "optional": true, + "minimum": 0, + "description": "X position of where the mouse event should be dispatched on the offscreen web page. Not required in the case of a mousewheel event." + }, + { + "name": "y", + "type": "integer", + "optional": true, + "minimum": 0, + "description": "Y position of where the mouse event should be dispatched on the offscreen web page. Not required in the case of a mousewheel event." + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "sendKeyboardEvent", + "type": "function", + "description": "Dispatches a keyboard event in the offscreen tab.", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "ID of the offscreen tab." + }, + { + "name": "keyboardEvent", + "$ref": "KeyboardEvent", + "description": "A JavaScript KeyboardEvent object. Supported event types: <i>keydown</i>, <i>keyup</i>, <i>keypress</i>. Note, only <i>keypress</i> produces a visible result on screen." + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "toDataUrl", + "type": "function", + "description": "Captures the visible area of an offscreen tab. ", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "The ID of the offscreen tab." + }, + { + "name": "options", + "type": "object", + "optional": true, + "description": "Set parameters of image capture, such as the format of the resulting image.", + "properties": { + "format": { + "type": "string", + "optional": true, + "enum": ["jpeg", "png"], + "description": "The format of the resulting image. Default is jpeg." + }, + "quality": { + "type": "integer", + "optional": true, + "minimum": 0, + "maximum": 100, + "description": "When format is 'jpeg', controls the quality of the resulting image. This value is ignored for PNG images. As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease." + } + } + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "dataUrl", + "type": "string", + "description": "A data URL which encodes an image of the visible area of the captured offscreen tab. May be assigned to the 'src' property of an HTML Image element or WebGL texture source for display." + } + ] + } + ] + }, + { + "name": "update", + "type": "function", + "description": "Modifies the properties of an offscreen tab. Properties that are not specified in updateProperties are not modified.", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "The ID of the offscreen tab." + }, + { + "name": "updateProperties", + "type": "object", + "properties": { + "url": { + "type": "string", + "optional": true, + "description": "The URL the offscreen tab is displaying." + }, + "width": { + "type": "integer", + "optional": true, + "minimum": 0, + "description": "Width of the window." + }, + "height": { + "type": "integer", + "optional": true, + "minimum": 0, + "description": "Height of the window." + } + } + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [] + } + ] + } + ], + "events": [ + { + "name": "onUpdated", + "type": "function", + "description": "Fires when an offscreen tab is updated. ", + "parameters": [ + { + "name": "offscreenTabId", + "type": "integer", + "minimum": 0, + "description": "ID of the updated offscreen tab" + }, + { + "name": "changeInfo", + "type": "object", + "description": "Lists the changes to the state of the offscreen tab that was updated.", + "properties": { + "url": { + "type": "string", + "optional": true, + "description": "The offscreen tab's URL if it has changed." + } + } + }, + { + "name": "offscreenTab", + "$ref": "OffscreenTab", + "description": "Details of the offscreen tab." + } + ] + } + ] + } +] diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc index f8909b0..511f134 100644 --- a/chrome/common/extensions/api/extension_api.cc +++ b/chrome/common/extensions/api/extension_api.cc @@ -119,6 +119,7 @@ ExtensionAPI::ExtensionAPI() { IDR_EXTENSION_API_JSON_EXPERIMENTAL_INPUT_VIRTUALKEYBOARD, IDR_EXTENSION_API_JSON_EXPERIMENTAL_KEYBINDING, IDR_EXTENSION_API_JSON_EXPERIMENTAL_MANAGED_MODE, + IDR_EXTENSION_API_JSON_EXPERIMENTAL_OFFSCREENTABS, IDR_EXTENSION_API_JSON_EXPERIMENTAL_PROCESSES, IDR_EXTENSION_API_JSON_EXPERIMENTAL_RLZ, IDR_EXTENSION_API_JSON_EXPERIMENTAL_SERIAL, diff --git a/chrome/common/extensions/api/tabs.json b/chrome/common/extensions/api/tabs.json index de8ed67..299d3a3 100644 --- a/chrome/common/extensions/api/tabs.json +++ b/chrome/common/extensions/api/tabs.json @@ -548,7 +548,6 @@ }, "quality": { "type": "integer", - "name": "quality", "optional": true, "minimum": 0, "maximum": 100, diff --git a/chrome/common/extensions/docs/experimental.html b/chrome/common/extensions/docs/experimental.html index 3713d1f..eae95f4 100644 --- a/chrome/common/extensions/docs/experimental.html +++ b/chrome/common/extensions/docs/experimental.html @@ -246,6 +246,7 @@ on the following experimental APIs: <a href="experimental.infobars.html">experimental.infobars</a></li><li> <a href="experimental.keybinding.html">experimental.keybinding</a></li><li> <a href="experimental.managedMode.html">experimental.managedMode</a></li><li> + <a href="experimental.offscreenTabs.html">experimental.offscreenTabs</a></li><li> <a href="experimental.speechInput.html">experimental.speechInput</a></li><li> <a href="experimental.webRequest.html">experimental.webRequest</a></li> </ul> diff --git a/chrome/common/extensions/docs/experimental.offscreenTabs.html b/chrome/common/extensions/docs/experimental.offscreenTabs.html new file mode 100644 index 0000000..d8292d7 --- /dev/null +++ b/chrome/common/extensions/docs/experimental.offscreenTabs.html @@ -0,0 +1,1655 @@ +<!DOCTYPE html><!-- This page is a placeholder for generated extensions api doc. Note: + 1) The <head> information in this page is significant, should be uniform + across api docs and should be edited only with knowledge of the + templating mechanism. + 3) All <body>.innerHTML is genereated as an rendering step. If viewed in a + browser, it will be re-generated from the template, json schema and + authored overview content. + 4) The <body>.innerHTML is also generated by an offline step so that this + page may easily be indexed by search engines. +--><html xmlns="http://www.w3.org/1999/xhtml"><head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link href="css/ApiRefStyles.css" rel="stylesheet" type="text/css"> + <link href="css/print.css" rel="stylesheet" type="text/css" media="print"> + <script type="text/javascript" src="../../../third_party/jstemplate/jstemplate_compiled.js"> + </script> + <script type="text/javascript" src="../../../../third_party/json_minify/minify-sans-regexp.js"> + </script> + <script type="text/javascript" src="js/api_page_generator.js"></script> + <script type="text/javascript" src="js/bootstrap.js"></script> + <script type="text/javascript" src="js/sidebar.js"></script> + <meta name="description" content="Documentation for the chrome.experimental.offscreenTabs module, which is part of the Google Chrome extension APIs."><title>chrome.experimental.offscreenTabs - Google Chrome Extensions - Google Code</title></head> + <body> <div id="devModeWarning" class="displayModeWarning"> + You are viewing extension docs in chrome via the 'file:' scheme: are you expecting to see local changes when you refresh? You'll need run chrome with --allow-file-access-from-files. + </div> + <div id="branchWarning" class="displayModeWarning"> + <span>WARNING: This is the <span id="branchName">BETA</span> documentation. + It may not work with the stable release of Chrome.</span> + <select id="branchChooser"> + <option>Choose a different version... + </option><option value="">Stable + </option><option value="beta">Beta + </option><option value="dev">Dev + </option><option value="trunk">Trunk + </option></select> + </div> + <div id="unofficialWarning" class="displayModeWarning"> + <span>WARNING: This is unofficial documentation. It may not work with the + current release of Chrome.</span> + <button id="goToOfficialDocs">Go to the official docs</button> + </div> + <div id="gc-container" class="labs"> + <!-- SUBTEMPLATES: DO NOT MOVE FROM THIS LOCATION --> + <!-- In particular, sub-templates that recurse, must be used by allowing + jstemplate to make a copy of the template in this section which + are not operated on by way of the jsskip="true" --> + <!-- /SUBTEMPLATES --> + <a id="top"></a> + <div id="skipto"> + <a href="#gc-pagecontent">Skip to page content</a> + <a href="#gc-toc">Skip to main navigation</a> + </div> + <!-- API HEADER --> + <table id="header" width="100%" cellspacing="0" border="0"> + <tbody><tr> + <td valign="middle"><a href="http://code.google.com/"><img src="images/code_labs_logo.gif" height="43" width="161" alt="Google Code Labs" style="border:0; margin:0;"></a></td> + <td valign="middle" width="100%" style="padding-left:0.6em;"> + <form action="http://www.google.com/cse" id="cse" style="margin-top:0.5em"> + <div id="gsc-search-box"> + <input type="hidden" name="cx" value="002967670403910741006:61_cvzfqtno"> + <input type="hidden" name="ie" value="UTF-8"> + <input type="text" name="q" value="" size="55"> + <input class="gsc-search-button" type="submit" name="sa" value="Search"> + <br> + <span class="greytext">e.g. "page action" or "tabs"</span> + </div> + </form> + <script type="text/javascript" src="https://www.google.com/jsapi"></script> + <script type="text/javascript">google.load("elements", "1", {packages: "transliteration"});</script> + <script type="text/javascript" src="https://www.google.com/coop/cse/t13n?form=cse&t13n_langs=en"></script> + <script type="text/javascript" src="https://www.google.com/coop/cse/brand?form=cse&lang=en"></script> + </td> + </tr> + </tbody></table> + <div id="codesiteContent" class=""> + <a id="gc-topnav-anchor"></a> + <div id="gc-topnav"> + <h1>Google Chrome Extensions (<a href="http://code.google.com/labs/">Labs</a>)</h1> + <ul id="home" class="gc-topnav-tabs"> + <li id="home_link"> + <a href="index.html" title="Google Chrome Extensions home page">Home</a> + </li> + <li id="docs_link"> + <a href="docs.html" title="Official Google Chrome Extensions documentation">Docs</a> + </li> + <li id="faq_link"> + <a href="faq.html" title="Answers to frequently asked questions about Google Chrome Extensions">FAQ</a> + </li> + <li id="samples_link"> + <a href="samples.html" title="Sample extensions (with source code)">Samples</a> + </li> + <li id="group_link"> + <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions" title="Google Chrome Extensions developer forum">Group</a> + </li> + <li id="so_link"> + <a href="http://stackoverflow.com/questions/tagged/google-chrome-extension" title="[google-chrome-extension] tag on Stack Overflow">Questions?</a> + </li> + </ul> + </div> <!-- end gc-topnav --> + <div class="g-section g-tpl-170"> + <!-- SIDENAV --> + <div class="g-unit g-first" id="gc-toc"> + <ul> + <li><a href="getstarted.html">Getting Started</a></li> + <li><a href="overview.html">Overview</a></li> + <li><a href="whats_new.html">What's New?</a></li> + <li><h2><a href="devguide.html">Developer's Guide</a></h2> + <ul> + <li>Browser UI + <ul> + <li><a href="browserAction.html">Browser Actions</a></li> + <li><a href="contextMenus.html">Context Menus</a></li> + <li><a href="notifications.html">Desktop Notifications</a></li> + <li><a href="omnibox.html">Omnibox</a></li> + <li><a href="options.html">Options Pages</a></li> + <li><a href="override.html">Override Pages</a></li> + <li><a href="pageAction.html">Page Actions</a></li> + </ul> + </li> + <li>Browser Interaction + <ul> + <li><a href="bookmarks.html">Bookmarks</a></li> + <li><a href="cookies.html">Cookies</a></li> + <li><a href="devtools.html">Developer Tools</a></li> + <li><a href="events.html">Events</a></li> + <li><a href="history.html">History</a></li> + <li><a href="management.html">Management</a></li> + <li><a href="tabs.html">Tabs</a></li> + <li><a href="windows.html">Windows</a></li> + </ul> + </li> + <li>Implementation + <ul> + <li><a href="a11y.html">Accessibility</a></li> + <li><a href="background_pages.html">Background Pages</a></li> + <li><a href="content_scripts.html">Content Scripts</a></li> + <li><a href="xhr.html">Cross-Origin XHR</a></li> + <li><a href="i18n.html">Internationalization</a></li> + <li><a href="messaging.html">Message Passing</a></li> + <li><a href="permissions.html">Optional Permissions</a></li> + <li><a href="npapi.html">NPAPI Plugins</a></li> + </ul> + </li> + <li>Finishing + <ul> + <li><a href="hosting.html">Hosting</a></li> + <li><a href="external_extensions.html">Other Deployment Options</a></li> + </ul> + </li> + </ul> + </li> + <li><h2><a href="apps.html">Packaged Apps</a></h2></li> + <li><h2><a href="tutorials.html">Tutorials</a></h2> + <ul> + <li><a href="tut_debugging.html">Debugging</a></li> + <li><a href="tut_analytics.html">Google Analytics</a></li> + <li><a href="tut_oauth.html">OAuth</a></li> + </ul> + </li> + <li><h2>Reference</h2> + <ul> + <li>Formats + <ul> + <li><a href="manifest.html">Manifest Files</a></li> + <li><a href="match_patterns.html">Match Patterns</a></li> + </ul> + </li> + <li><a href="permission_warnings.html">Permission Warnings</a></li> + <li><a href="api_index.html">chrome.* APIs</a></li> + <li><a href="api_other.html">Other APIs</a></li> + </ul> + </li> + <li><h2><a href="samples.html">Samples</a></h2></li> + <div class="line"> </div> + <li><h2>More</h2> + <ul> + <li><a href="http://code.google.com/chrome/webstore/docs/index.html">Chrome Web Store</a></li> + <li><a href="http://code.google.com/chrome/apps/docs/developers_guide.html">Hosted Apps</a></li> + <li><a href="themes.html">Themes</a></li> + </ul> + </li> + </ul> + </div> + <script> + initToggles(); + </script> + <div class="g-unit" id="gc-pagecontent"> + <div id="pageTitle"> + <h1 class="page_title">chrome.experimental.offscreenTabs</h1> + </div> + <!-- TABLE OF CONTENTS --> + <div id="toc"> + <h2>Contents</h2> + <ol> + <li> + <a href="#apiReference">API reference: chrome.experimental.offscreenTabs</a> + <ol> + <li> + <a href="#global-methods">Methods</a> + <ol> + <li> + <a href="#method-create">create</a> + </li><li> + <a href="#method-get">get</a> + </li><li> + <a href="#method-getAll">getAll</a> + </li><li> + <a href="#method-remove">remove</a> + </li><li> + <a href="#method-sendKeyboardEvent">sendKeyboardEvent</a> + </li><li> + <a href="#method-sendMouseEvent">sendMouseEvent</a> + </li><li> + <a href="#method-toDataUrl">toDataUrl</a> + </li><li> + <a href="#method-update">update</a> + </li> + </ol> + </li> + <li> + <a href="#global-events">Events</a> + <ol> + <li> + <a href="#event-onUpdated">onUpdated</a> + </li> + </ol> + </li> + <li> + <a href="#types">Types</a> + <ol> + <li> + <a href="#type-OffscreenTab">OffscreenTab</a> + <ol> + </ol> + </li> + </ol> + </li> + </ol> + </li> + </ol> + </div> + <!-- /TABLE OF CONTENTS --> + <!-- Standard content lead-in for experimental API pages --> + <p id="classSummary"> + For information on how to use experimental APIs, see the <a href="experimental.html">chrome.experimental.* APIs</a> page. + </p> + <!-- STATIC CONTENT PLACEHOLDER --> + <div id="static"></div> + <!-- API PAGE --> + <div class="apiPage"> + <a name="apiReference"></a> + <h2>API reference: chrome.experimental.offscreenTabs</h2> + <!-- PROPERTIES --> + <!-- /apiGroup --> + <!-- METHODS --> + <div id="methodsTemplate" class="apiGroup"> + <a name="global-methods"></a> + <h3>Methods</h3> + <!-- iterates over all functions --> + <div class="apiItem"> + <a name="method-create"></a> <!-- method-anchor --> + <h4>create</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.create</span>(<span class="null"><span>object</span> + <var><span>createProperties</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Creates a new offscreen tab.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>createProperties</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>url</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The URL to navigate the offscreen tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Note that you can't create offscreen tabs from background pages. The lifetime of the offscreen tab is tied to the context of its creator (tab, browser action, etc.).</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>width</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Width of the offscreen tab. Defaults to width of the current tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>height</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Height of the offscreen tab. Defaults to height of the current tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>OffscreenTab offscreenTab</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>offscreenTab</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <a href="experimental.offscreenTabs.html#type-OffscreenTab">OffscreenTab</a> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Details of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-get"></a> <!-- method-anchor --> + <h4>get</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.get</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="null"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Gets details about the specified offscreen tab.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>OffscreenTab offscreenTab</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>offscreenTab</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <a href="experimental.offscreenTabs.html#type-OffscreenTab">OffscreenTab</a> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Details of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-getAll"></a> <!-- method-anchor --> + <h4>getAll</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.getAll</span>(<span class="null"><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Gets an array of all offscreen tabs.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>array of OffscreenTab offscreenTabs</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>offscreenTabs</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span> + array of <span><span> + <span> + <a href="experimental.offscreenTabs.html#type-OffscreenTab">OffscreenTab</a> + </span> + </span></span> + </span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-remove"></a> <!-- method-anchor --> + <h4>remove</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.remove</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Removes an offscreen tab.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>null</span>) <span class="subdued">{...}</span>;</pre> + <dl> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-sendKeyboardEvent"></a> <!-- method-anchor --> + <h4>sendKeyboardEvent</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.sendKeyboardEvent</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="null"><span>, </span><span>any</span> + <var><span>keyboardEvent</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Dispatches a keyboard event in the offscreen tab.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>keyboardEvent</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>any</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>A JavaScript KeyboardEvent object. Supported event types: <i>keydown</i>, <i>keyup</i>, <i>keypress</i>. Note, only <i>keypress</i> produces a visible result on screen.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span></span>) <span class="subdued">{...}</span>;</pre> + <dl> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-sendMouseEvent"></a> <!-- method-anchor --> + <h4>sendMouseEvent</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.sendMouseEvent</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="null"><span>, </span><span>any</span> + <var><span>mouseEvent</span></var></span><span class="optional"><span>, </span><span>integer</span> + <var><span>x</span></var></span><span class="optional"><span>, </span><span>integer</span> + <var><span>y</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Dispatches a mouse event in the offscreen tab.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>mouseEvent</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>any</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>A JavaScript MouseEvent object. Supported event types: <i>mousedown</i>, <i>mouseup</i>, <i>click</i>, <i>mousemove</i>, <i>mousewheel</i>.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>x</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>X position of where the mouse event should be dispatched on the offscreen web page. Not required in the case of a mousewheel event.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>y</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Y position of where the mouse event should be dispatched on the offscreen web page. Not required in the case of a mousewheel event.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span></span>) <span class="subdued">{...}</span>;</pre> + <dl> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-toDataUrl"></a> <!-- method-anchor --> + <h4>toDataUrl</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.toDataUrl</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="optional"><span>, </span><span>object</span> + <var><span>options</span></var></span><span class="null"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Captures the visible area of an offscreen tab. </p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>options</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Set parameters of image capture, such as the format of the resulting image.</dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>format</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span class="enum">enumerated</span> + <span id="typeTemplate"> + <span> + <span>string</span> + <span>["jpeg", "png"]</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The format of the resulting image. Default is jpeg.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>quality</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>When format is 'jpeg', controls the quality of the resulting image. This value is ignored for PNG images. As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>string dataUrl</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>dataUrl</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>A data URL which encodes an image of the visible area of the captured offscreen tab. May be assigned to the 'src' property of an HTML Image element or WebGL texture source for display.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="method-update"></a> <!-- method-anchor --> + <h4>update</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.offscreenTabs.update</span>(<span class="null"><span>integer</span> + <var><span>offscreenTabId</span></var></span><span class="null"><span>, </span><span>object</span> + <var><span>updateProperties</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + <div class="description"> + <p>Modifies the properties of an offscreen tab. Properties that are not specified in updateProperties are not modified.</p> + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The ID of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>updateProperties</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>url</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The URL the offscreen tab is displaying.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>width</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Width of the window.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>height</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Height of the window.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>function</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + <!-- RETURNS --> + <dl> + </dl> + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span></span>) <span class="subdued">{...}</span>;</pre> + <dl> + </dl> + </div> + </div> + <!-- MIN_VERSION --> + </div> <!-- /description --> + </div> <!-- /apiItem --> + </div> <!-- /apiGroup --> + <!-- EVENTS --> + <div id="eventsTemplate" class="apiGroup"> + <a name="global-events"></a> + <h3>Events</h3> + <!-- iterates over all events --> + <div class="apiItem"> + <a name="event-onUpdated"></a> + <h4>onUpdated</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span class="subdued">chrome.experimental.offscreenTabs.</span><span>onUpdated</span><span class="subdued">.addListener</span>(function(<span>integer offscreenTabId, object changeInfo, OffscreenTab offscreenTab</span>) <span class="subdued">{...}</span><span></span>); + </div> + <div class="description"> + <p>Fires when an offscreen tab is updated. </p> + <!-- LISTENER PARAMETERS --> + <div> + <h4>Listener parameters</h4> + <dl> + <div> + <div> + <dt> + <var>offscreenTabId</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>ID of the updated offscreen tab</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>changeInfo</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Lists the changes to the state of the offscreen tab that was updated.</dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>url</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span id="typeTemplate"> + <span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The offscreen tab's URL if it has changed.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>offscreenTab</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <a href="experimental.offscreenTabs.html#type-OffscreenTab">OffscreenTab</a> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Details of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </div> + <!-- EXTRA PARAMETERS --> + <!-- LISTENER RETURN VALUE --> + <dl> + </dl> + </div> <!-- /description --> + </div> <!-- /apiItem --> + </div> <!-- /apiGroup --> + <!-- TYPES --> + <div class="apiGroup"> + <a name="types"></a> + <h3 id="types">Types</h3> + <!-- iterates over all types --> + <div class="apiItem"> + <a name="type-OffscreenTab"></a> + <h4>OffscreenTab</h4> + <div> + <dt> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>object</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>id</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>The ID of the offscreen tab. Tab IDs are unique within a browser session.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>url</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>string</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>URL of the offscreen tab.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>width</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Width of the window.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div><div> + <div> + <dt> + <var>height</var> + <em> + <!-- TYPE --> + <div style="display:inline"> + ( + <span id="typeTemplate"> + <span> + <span>integer</span> + </span> + </span> + ) + </div> + </em> + </dt> + <dd>Height of the window.</dd> + <!-- OBJECT PROPERTIES --> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> + </dl> + </dd> + <!-- OBJECT METHODS --> + <!-- OBJECT EVENT FIELDS --> + <!-- FUNCTION PARAMETERS --> + </div> + </div> <!-- /apiItem --> + </div> <!-- /apiGroup --> + </div> <!-- /apiPage --> + </div> <!-- /gc-pagecontent --> + </div> <!-- /g-section --> + </div> <!-- /codesiteContent --> + <div id="gc-footer" --=""> + <div class="text"> + <p> + Except as otherwise <a href="http://code.google.com/policies.html#restrictions">noted</a>, + the content of this page is licensed under the <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons + Attribution 3.0 License</a>, and code samples are licensed under the + <a rel="license" href="http://code.google.com/google_bsd_license.html">BSD License</a>. + </p> + <p> + ©2011 Google + </p> +<!-- begin analytics --> +<script src="https://www.google-analytics.com/urchin.js" type="text/javascript"></script> +<script src="https://www.google-analytics.com/ga.js" type="text/javascript"></script> +<script type="text/javascript"> + // chrome doc tracking + try { + var engdocs = _gat._getTracker("YT-10763712-2"); + engdocs._trackPageview(); + } catch(err) {} + // code.google.com site-wide tracking + try { + _uacct="UA-18071-1"; + _uanchor=1; + _uff=0; + urchinTracker(); + } + catch(e) {/* urchinTracker not available. */} +</script> +<!-- end analytics --> + </div> + </div> <!-- /gc-footer --> + </div> <!-- /gc-container --> +</body></html> diff --git a/chrome/common/extensions/docs/js/api_page_generator.js b/chrome/common/extensions/docs/js/api_page_generator.js index 9d06222..c54d570 100644 --- a/chrome/common/extensions/docs/js/api_page_generator.js +++ b/chrome/common/extensions/docs/js/api_page_generator.js @@ -43,6 +43,7 @@ var MODULE_SCHEMAS = [ '../api/experimental.input.virtualKeyboard.json', '../api/experimental.keybinding.json', '../api/experimental.managedMode.json', + '../api/experimental.offscreenTabs.json', '../api/experimental.processes.json', '../api/experimental.rlz.json', '../api/experimental.serial.json', diff --git a/chrome/common/extensions/docs/samples.json b/chrome/common/extensions/docs/samples.json index 4edf90b..b97b235 100644 --- a/chrome/common/extensions/docs/samples.json +++ b/chrome/common/extensions/docs/samples.json @@ -81,6 +81,15 @@ "chrome.experimental.keybinding.onCommand": "experimental.keybinding.html#event-onCommand", "chrome.experimental.managedMode.enter": "experimental.managedMode.html#method-enter", "chrome.experimental.managedMode.get": "experimental.managedMode.html#method-get", + "chrome.experimental.offscreenTabs.create": "experimental.offscreenTabs.html#method-create", + "chrome.experimental.offscreenTabs.get": "experimental.offscreenTabs.html#method-get", + "chrome.experimental.offscreenTabs.getAll": "experimental.offscreenTabs.html#method-getAll", + "chrome.experimental.offscreenTabs.onUpdated": "experimental.offscreenTabs.html#event-onUpdated", + "chrome.experimental.offscreenTabs.remove": "experimental.offscreenTabs.html#method-remove", + "chrome.experimental.offscreenTabs.sendKeyboardEvent": "experimental.offscreenTabs.html#method-sendKeyboardEvent", + "chrome.experimental.offscreenTabs.sendMouseEvent": "experimental.offscreenTabs.html#method-sendMouseEvent", + "chrome.experimental.offscreenTabs.toDataUrl": "experimental.offscreenTabs.html#method-toDataUrl", + "chrome.experimental.offscreenTabs.update": "experimental.offscreenTabs.html#method-update", "chrome.experimental.speechInput.isRecording": "experimental.speechInput.html#method-isRecording", "chrome.experimental.speechInput.onError": "experimental.speechInput.html#event-onError", "chrome.experimental.speechInput.onResult": "experimental.speechInput.html#event-onResult", diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc index 4d187bb2..24e8f3e 100644 --- a/chrome/renderer/extensions/extension_dispatcher.cc +++ b/chrome/renderer/extensions/extension_dispatcher.cc @@ -478,6 +478,8 @@ void ExtensionDispatcher::PopulateSourceMap() { source_map_.RegisterSource("devtools", IDR_DEVTOOLS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.declarative", IDR_EXPERIMENTAL_DECLARATIVE_CUSTOM_BINDINGS_JS); + source_map_.RegisterSource("experimental.offscreen", + IDR_EXPERIMENTAL_OFFSCREENTABS_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("experimental.socket", IDR_EXPERIMENTAL_SOCKET_CUSTOM_BINDINGS_JS); source_map_.RegisterSource("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS); diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index a0df32c..cc09589 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -36,6 +36,7 @@ without changes to the corresponding grd file. fb9 --> <include name="IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS" file="resources\extensions\context_menus_custom_bindings.js" type="BINDATA" /> <include name="IDR_DEVTOOLS_CUSTOM_BINDINGS_JS" file="resources\extensions\devtools_custom_bindings.js" type="BINDATA" /> <include name="IDR_EXPERIMENTAL_DECLARATIVE_CUSTOM_BINDINGS_JS" file="resources\extensions\experimental.declarative_custom_bindings.js" type="BINDATA" /> + <include name="IDR_EXPERIMENTAL_OFFSCREENTABS_CUSTOM_BINDINGS_JS" file="resources\extensions\experimental.offscreenTabs_custom_bindings.js" type="BINDATA" /> <include name="IDR_EXPERIMENTAL_SOCKET_CUSTOM_BINDINGS_JS" file="resources\extensions\experimental.socket_custom_bindings.js" type="BINDATA" /> <include name="IDR_EXTENSION_CUSTOM_BINDINGS_JS" file="resources\extensions\extension_custom_bindings.js" type="BINDATA" /> <include name="IDR_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_JS" file="resources\extensions\file_browser_handler_custom_bindings.js" type="BINDATA" /> diff --git a/chrome/renderer/resources/extensions/experimental.offscreenTabs_custom_bindings.js b/chrome/renderer/resources/extensions/experimental.offscreenTabs_custom_bindings.js new file mode 100644 index 0000000..3635a2c --- /dev/null +++ b/chrome/renderer/resources/extensions/experimental.offscreenTabs_custom_bindings.js @@ -0,0 +1,63 @@ +// Copyright (c) 2012 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. + +// Custom bindings for the experimental offscreenTabs API. + +(function() { + +native function GetChromeHidden(); + +GetChromeHidden().registerCustomHook( + 'experimental.offscreenTabs', function(api) { + var apiFunctions = api.apiFunctions; + + function maybeCopy(src, prop, dest) { + if (src[prop] !== undefined) + dest[prop] = src[prop]; + }; + + function keyboardEventFilter(e) { + var result = { + type: e.type, + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + }; + maybeCopy(e, 'keyCode', result); + maybeCopy(e, 'charCode', result); + return result; + }; + + function mouseEventFilter(e) { + var result = { + type: e.type, + ctrlKey: e.ctrlKey, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + button: e.button, + }; + maybeCopy(e, 'wheelDeltaX', result); + maybeCopy(e, 'wheelDeltaY', result); + return result; + }; + + // We are making a copy of |arr|, but applying |func| to index 1. + function validate(arr, func) { + var newArr = []; + for (var i = 0; i < arr.length; i++) + newArr.push(i == 1 && typeof(arr) == 'object' ? func(arr[i]) : arr[i]); + return newArr; + } + + apiFunctions.setUpdateArgumentsPreValidate( + 'sendKeyboardEvent', + function() { return validate(arguments, keyboardEventFilter); }); + apiFunctions.setUpdateArgumentsPreValidate( + 'sendMouseEvent', + function() { return validate(arguments, mouseEventFilter); }); +}); + +})(); diff --git a/chrome/test/data/extensions/api_test/get_views/test.js b/chrome/test/data/extensions/api_test/get_views/test.js index 3c3a5bf..342789c 100644 --- a/chrome/test/data/extensions/api_test/get_views/test.js +++ b/chrome/test/data/extensions/api_test/get_views/test.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -38,7 +38,7 @@ var tests = [ assertEq(0, chrome.extension.getViews({"type": "tab"}).length); assertEq(0, chrome.extension.getViews({"type": "infobar"}).length); assertEq(0, chrome.extension.getViews({"type": "notification"}).length); - + chrome.windows.getAll({populate: true}, function(windows) { assertEq(1, windows.length); diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/a.html b/chrome/test/data/extensions/api_test/offscreen_tabs/a.html new file mode 100644 index 0000000..872d7fc --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/a.html @@ -0,0 +1,14 @@ +<html> + <head> + <script src="tab_util.js"></script> + <script> + function keyboard(e) { + if (e.charCode == Q_KEY) + window.location = 'c.html'; + } + </script> + </head> + <body onkeypress="keyboard(event)"> + <a href="b.html" style="position:absolute; left:10px; top:10px;">b.html</a> + </body> +</head> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/b.html b/chrome/test/data/extensions/api_test/offscreen_tabs/b.html new file mode 100644 index 0000000..daf7796 --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/b.html @@ -0,0 +1,10 @@ +<html> +<body> + <a href="c.html" style="position: absolute; left: 10px; top: 110px;"> + c.html + </a> + <span style="position:absolute; top:2000px;"> + We place this way down here so the page is large enough to test scrolling. + </span> +</body> +</html> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/c.html b/chrome/test/data/extensions/api_test/offscreen_tabs/c.html new file mode 100644 index 0000000..90531a4 --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/c.html @@ -0,0 +1,2 @@ +<html> +</html> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/crud.html b/chrome/test/data/extensions/api_test/offscreen_tabs/crud.html new file mode 100644 index 0000000..8dc322c --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/crud.html @@ -0,0 +1,7 @@ +<!-- + * Copyright (c) 2012 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. +--> +<script src="tab_util.js"></script> +<script src="crud.js"></script> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/crud.js b/chrome/test/data/extensions/api_test/offscreen_tabs/crud.js new file mode 100644 index 0000000..e33f3bc --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/crud.js @@ -0,0 +1,66 @@ +// Copyright (c) 2012 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. + +var tabList = [ + { url: 'a.html', width: 200, height: 200 }, + { url: 'b.html', width: 200, height: 200 }, + { url: 'c.html', width: 1000,height: 800 } +]; + +// Holds the tab objects once they're created. +var tabs = []; + +chrome.test.runTests([ + function create() { + tabList.forEach(function(tab) { + chrome.experimental.offscreenTabs.create( + tab, pass(function(offscreenTab) { + tabs.push(offscreenTab); + assertSimilarTabs(tab, offscreenTab); + })); + }); + }, + + function getAll() { + chrome.experimental.offscreenTabs.getAll(pass(function(offscreenTabs) { + var tabSorter = function(tab1, tab2) { return tab1.id - tab2.id; }; + offscreenTabs.sort(tabSorter); + tabs.sort(tabSorter); + assertEq(tabs.length, offscreenTabs.length); + assertEq(tabs.length, tabList.length); + for (var i = 0; i < tabs.length; i++) + assertEqTabs(tabs[i], offscreenTabs[i]); + })); + }, + + function get() { + chrome.experimental.offscreenTabs.get(tabs[0].id, pass(function(tab) { + assertEqTabs(tabs[0], tab); + })); + }, + + function update() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + assertSimilarTabs(tabList[0], tab); + }); + + // Update the 2nd tab to be like the 1st one. + chrome.experimental.offscreenTabs.update( + tabs[1].id, tabList[0], pass(function() { + chrome.experimental.offscreenTabs.get(tabs[1].id, pass(function(tab) { + assertSimilarTabs(tabList[0], tab); + })); + })); + }, + + function remove() { + tabs.forEach(function(tab) { + chrome.experimental.offscreenTabs.remove(tab.id, pass(function() { + assertTabDoesNotExist(tab.id); + })); + }); + } +]); diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/display.html b/chrome/test/data/extensions/api_test/offscreen_tabs/display.html new file mode 100644 index 0000000..97e148d --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/display.html @@ -0,0 +1,7 @@ +<!-- + * Copyright (c) 2012 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. +--> +<script src="tab_util.js"></script> +<script src="display.js"></script> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/display.js b/chrome/test/data/extensions/api_test/offscreen_tabs/display.js new file mode 100644 index 0000000..1026b6a --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/display.js @@ -0,0 +1,78 @@ +// Copyright (c) 2012 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. + +var testTabs = [ + { url: 'a.html', width: 200, height: 200 }, + { url: 'b.html', width: 200, height: 200 } +]; + +var firstTabId; +var secondTabId; +var firstTabDataURL; +var secondTabDataURL; + +var DATA_URL_PREFIX = 'data:image/png;base64,'; + +chrome.test.runTests([ + function createFirstTab() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + assertSimilarTabs(testTabs[0], tab); + firstTabId = tab.id; + }); + + chrome.experimental.offscreenTabs.create( + testTabs[0], pass(function(tab) { + assertSimilarTabs(testTabs[0], tab); + })); + }, + + function createSecondTab() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + assertSimilarTabs(testTabs[1], tab); + secondTabId = tab.id; + }); + + chrome.experimental.offscreenTabs.create( + testTabs[1], pass(function(tab) { + assertSimilarTabs(testTabs[1], tab); + })); + }, + + // Capture an image of the first tab and verify that the data URL looks + // reasonably correct. + function captureFirstTab() { + chrome.experimental.offscreenTabs.toDataUrl( + firstTabId, {format: 'png'}, pass(function(dataURL) { + assertEq('string', typeof(dataURL)); + assertEq(DATA_URL_PREFIX, dataURL.substring(0, DATA_URL_PREFIX.length)); + firstTabDataURL = dataURL; + })); + }, + + // Capture an image of the second tab and verify that the data URL looks + // reasonably correct. + function captureSecondTab() { + chrome.experimental.offscreenTabs.toDataUrl( + secondTabId, {format: 'png'}, pass(function(dataURL) { + assertEq('string', typeof(dataURL)); + assertEq(DATA_URL_PREFIX, dataURL.substring(0, DATA_URL_PREFIX.length)); + secondTabDataURL = dataURL; + })); + } + + /* + This test does not work on Mac because the API always returns black images. + + // Make sure the data URLs for the two tabs are different, since pages are + // different (though the height and widths are equal). + function compareCaptures() { + assertFalse(firstTabDataURL == secondTabDataURL); + chrome.test.succeed(); + } + */ +]); diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.html b/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.html new file mode 100644 index 0000000..c3a0406 --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.html @@ -0,0 +1,7 @@ +<!-- + * Copyright (c) 2012 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. +--> +<script src="tab_util.js"></script> +<script src="keyboard_events.js"></script> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.js b/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.js new file mode 100644 index 0000000..e9b191a --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/keyboard_events.js @@ -0,0 +1,44 @@ +// Copyright (c) 2012 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. + +var testTab = { url: 'a.html', width: 200, height: 200 }; +var testTabId; + +chrome.test.runTests([ + function init() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + assertSimilarTabs(testTab, tab); + }); + + chrome.experimental.offscreenTabs.create(testTab, pass(function(tab) { + assertSimilarTabs(testTab, tab); + testTabId = tab.id; + })); + }, + + // Test that keyboard events work by sending a 'q' keypress to a.html. The + // page has a keypress handler that will navigate the page to c.html. + function keyPress() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + testTab.url = 'c.html'; + assertEq(maybeExpandURL('c.html'), changeInfo.url); + assertSimilarTabs(testTab, tab); + assertEq(tabId, tab.id); + assertEq(tabId, testTabId); + }); + + chrome.experimental.offscreenTabs.sendKeyboardEvent( + testTabId, getKeyboardEvent(Q_KEY), pass(function() { + chrome.experimental.offscreenTabs.get(testTabId, pass(function(tab) { + assertSimilarTabs(testTab, tab); + })); + })); + } + + // TODO(jstritar): Test event validation and edge cases. +]); diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/manifest.json b/chrome/test/data/extensions/api_test/offscreen_tabs/manifest.json new file mode 100644 index 0000000..b7684dc --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "chrome.experimental.offscreenTabs API test", + "description": "tests offscrene tabs API", + "version": "1", + "permissions": [ "experimental", "*://*/*" ] +} diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.html b/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.html new file mode 100644 index 0000000..8d1b8db --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.html @@ -0,0 +1,7 @@ +<!-- + * Copyright (c) 2012 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. +--> +<script src="tab_util.js"></script> +<script src="mouse_events.js"></script> diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.js b/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.js new file mode 100644 index 0000000..3cea10a --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/mouse_events.js @@ -0,0 +1,95 @@ +// Copyright (c) 2012 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. + +var testTab = { url: 'a.html', width: 200, height: 200 }; +var testTabId; + +var linkX = 11; +var linkY = 11; + +chrome.test.runTests([ + function init() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + assertSimilarTabs(testTab, tab); + }); + + chrome.experimental.offscreenTabs.create(testTab, pass(function(tab) { + assertSimilarTabs(testTab, tab); + testTabId = tab.id; + })); + }, + + // Test that mouse click events work by watching the tab navigate to b.html + // after clicking the link on a.html. + function mouseClick() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + testTab.url = 'b.html'; + assertEq(maybeExpandURL('b.html'), changeInfo.url); + assertEq(testTabId, tabId); + assertEq(testTabId, tab.id); + assertSimilarTabs(testTab, tab); + }); + + var mouseEvent = getBaseMouseEvent(); + mouseEvent.type = 'click'; + + chrome.experimental.offscreenTabs.sendMouseEvent( + testTabId, mouseEvent, linkX, linkY, pass(function() { + chrome.experimental.offscreenTabs.get(testTabId, pass(function(tab) { + assertEq(testTabId, tab.id); + assertSimilarTabs(testTab, tab); + })); + })); + }, + + // Scroll the b.html page down 100 px so that the link is at (10,10). + function mouseWheel() { + var mouseEvent = getBaseMouseEvent(); + mouseEvent.type = 'mousewheel'; + mouseEvent.wheelDeltaX = 0; + mouseEvent.wheelDeltaY = -100; + chrome.experimental.offscreenTabs.sendMouseEvent( + testTabId, mouseEvent, 0, 0, pass(function() { + chrome.experimental.offscreenTabs.get(testTabId, pass(function(tab) { + assertSimilarTabs(testTab, tab); + })); + })); + }, + + // Now that we scrolled the links into position, sending consecutive + // mousedown|up events to the link should navigate the page to c.html. + function mouseDownUp() { + chrome.test.listenOnce( + chrome.experimental.offscreenTabs.onUpdated, + function(tabId, changeInfo, tab) { + testTab.url = 'c.html'; + assertEq(testTabId, tabId); + assertEq(testTabId, tab.id); + assertSimilarTabs(testTab, tab); + }); + + var mouseEvent = getBaseMouseEvent(); + mouseEvent.type = 'mousedown'; + chrome.experimental.offscreenTabs.sendMouseEvent( + testTabId, mouseEvent, linkX, linkY, pass(function() { + chrome.experimental.offscreenTabs.get(testTabId, pass(function(tab) { + assertSimilarTabs(testTab, tab); + })); + })); + + mouseEvent.type = 'mouseup'; + chrome.experimental.offscreenTabs.sendMouseEvent( + testTabId, mouseEvent, linkX, linkY, pass(function() { + chrome.experimental.offscreenTabs.get(testTabId, pass(function(tab) { + assertSimilarTabs(testTab, tab); + })); + })); + } + + // TODO(jstritar): Test event validation and edge cases. +]); diff --git a/chrome/test/data/extensions/api_test/offscreen_tabs/tab_util.js b/chrome/test/data/extensions/api_test/offscreen_tabs/tab_util.js new file mode 100644 index 0000000..3494a7c --- /dev/null +++ b/chrome/test/data/extensions/api_test/offscreen_tabs/tab_util.js @@ -0,0 +1,53 @@ +// Copyright (c) 2012 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. + +var pass = chrome.test.callbackPass; +var fail = chrome.test.callbackFail; +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; +var assertFalse = chrome.test.assertFalse; + +var Q_KEY = 113; + +function assertEqTabs(tab1, tab2) { + assertEq(tab1.id, tab2.id); + assertSimilarTabs(tab1, tab2); +} + +function maybeExpandURL(url) { + if (url.match('chrome-extension')) + return url; + return chrome.extension.getURL(url); +} + +function assertSimilarTabs(tab1, tab2) { + assertEq(maybeExpandURL(tab1.url), maybeExpandURL(tab2.url)); + assertEq(tab1.width, tab2.width); + assertEq(tab1.height, tab2.height); +} + +function assertTabDoesNotExist(tabId) { + var expectedError = 'No offscreen tab with id: ' + tabId + '.'; + chrome.experimental.offscreenTabs.get(tabId, fail(expectedError)); +} + +function getBaseMouseEvent() { + return { + button: 0, + altKey: false, + ctrlKey: false, + shiftKey: false + }; +}; + +function getKeyboardEvent(keyCode) { + return { + type: 'keypress', + charCode: keyCode, + keyCode: keyCode, + altKey: false, + ctrlKey: false, + shiftKey: false + }; +} |