diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-21 19:56:35 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-21 19:56:35 +0000 |
commit | 45c75e6513af5c124c0c8d7ed866227a6c848e98 (patch) | |
tree | e679d0b79ffdb01523d8a934fa762f72d621418b /chrome/browser | |
parent | d7bc74ab00e36f4770425091be99b04fcf923b54 (diff) | |
download | chromium_src-45c75e6513af5c124c0c8d7ed866227a6c848e98.zip chromium_src-45c75e6513af5c124c0c8d7ed866227a6c848e98.tar.gz chromium_src-45c75e6513af5c124c0c8d7ed866227a6c848e98.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/9813014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128037 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
13 files changed, 1275 insertions, 103 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..e50fdfa --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc @@ -0,0 +1,837 @@ +// 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)) { + DictionaryValue* position = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(2, &position)); + EXTENSION_FUNCTION_VALIDATE(position->GetInteger( + keys::kMouseEventPositionXKey, &mouse_event.x)); + EXTENSION_FUNCTION_VALIDATE(position->GetInteger( + keys::kMouseEventPositionYKey, &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..a622931 --- /dev/null +++ b/chrome/browser/extensions/api/offscreen_tabs/offscreen_tabs_constants.cc @@ -0,0 +1,47 @@ +// 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 kMouseEventPositionXKey[] = "x"; +const char kMouseEventPositionYKey[] = "y"; +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..ee15df7 --- /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 kMouseEventButtonKey[]; +extern const char kMouseEventPositionXKey[]; +extern const char kMouseEventPositionYKey[]; +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 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_; |